neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

vim_spec.lua (210296B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 local Screen = require('test.functional.ui.screen')
      4 local uv = vim.uv
      5 
      6 local fmt = string.format
      7 local dedent = t.dedent
      8 local assert_alive = n.assert_alive
      9 local NIL = vim.NIL
     10 local clear, eq, neq = n.clear, t.eq, t.neq
     11 local command = n.command
     12 local command_output = n.api.nvim_command_output
     13 local exec = n.exec
     14 local exec_capture = n.exec_capture
     15 local eval = n.eval
     16 local expect = n.expect
     17 local fn = n.fn
     18 local api = n.api
     19 local matches = t.matches
     20 local pesc = vim.pesc
     21 local mkdir_p = n.mkdir_p
     22 local ok, nvim_async, feed = t.ok, n.nvim_async, n.feed
     23 local async_meths = n.async_meths
     24 local is_os = t.is_os
     25 local parse_context = n.parse_context
     26 local request = n.request
     27 local rmdir = n.rmdir
     28 local source = n.source
     29 local next_msg = n.next_msg
     30 local tmpname = t.tmpname
     31 local write_file = t.write_file
     32 local exec_lua = n.exec_lua
     33 local exc_exec = n.exc_exec
     34 local insert = n.insert
     35 local skip = t.skip
     36 
     37 local pcall_err = t.pcall_err
     38 local format_string = require('test.format_string').format_string
     39 local intchar2lua = t.intchar2lua
     40 local mergedicts_copy = t.mergedicts_copy
     41 local endswith = vim.endswith
     42 
     43 describe('API', function()
     44  before_each(clear)
     45 
     46  it('validates requests', function()
     47    -- RPC
     48    matches('Invalid method: bogus$', pcall_err(request, 'bogus'))
     49    matches('Invalid method: … の り 。…$', pcall_err(request, '… の り 。…'))
     50    matches('Invalid method: <empty>$', pcall_err(request, ''))
     51 
     52    -- Non-RPC: rpcrequest(v:servername) uses internal channel.
     53    matches(
     54      'Invalid method: … の り 。…$',
     55      pcall_err(
     56        request,
     57        'nvim_eval',
     58        [=[rpcrequest(sockconnect('pipe', v:servername, {'rpc':1}), '… の り 。…')]=]
     59      )
     60    )
     61    matches(
     62      'Invalid method: bogus$',
     63      pcall_err(
     64        request,
     65        'nvim_eval',
     66        [=[rpcrequest(sockconnect('pipe', v:servername, {'rpc':1}), 'bogus')]=]
     67      )
     68    )
     69 
     70    -- XXX: This must be the last one, else next one will fail:
     71    --      "Packer instance already working. Use another Packer ..."
     72    matches("can't serialize object of type .$", pcall_err(request, nil))
     73  end)
     74 
     75  it('handles errors in async requests', function()
     76    local error_types = api.nvim_get_api_info()[2].error_types
     77    nvim_async('bogus')
     78    eq({
     79      'notification',
     80      'nvim_error_event',
     81      { error_types.Exception.id, 'Invalid method: bogus' },
     82    }, next_msg())
     83    -- error didn't close channel.
     84    assert_alive()
     85  end)
     86 
     87  it('failed async request emits nvim_error_event', function()
     88    local error_types = api.nvim_get_api_info()[2].error_types
     89    async_meths.nvim_command('bogus')
     90    eq({
     91      'notification',
     92      'nvim_error_event',
     93      { error_types.Exception.id, 'Vim:E492: Not an editor command: bogus' },
     94    }, next_msg())
     95    -- error didn't close channel.
     96    assert_alive()
     97  end)
     98 
     99  it('input is processed first if followed immediately by non-fast events', function()
    100    api.nvim_set_current_line('ab')
    101    async_meths.nvim_input('x')
    102    async_meths.nvim_exec_lua('_G.res1 = vim.api.nvim_get_current_line()', {})
    103    async_meths.nvim_exec_lua('_G.res2 = vim.api.nvim_get_current_line()', {})
    104    eq({ 'b', 'b' }, exec_lua('return { _G.res1, _G.res2 }'))
    105    -- Also test with getchar()
    106    async_meths.nvim_command('let g:getchar = 1 | call getchar() | let g:getchar = 0')
    107    eq(1, api.nvim_get_var('getchar'))
    108    async_meths.nvim_input('x')
    109    async_meths.nvim_exec_lua('_G.res1 = vim.g.getchar', {})
    110    async_meths.nvim_exec_lua('_G.res2 = vim.g.getchar', {})
    111    eq({ 0, 0 }, exec_lua('return { _G.res1, _G.res2 }'))
    112  end)
    113 
    114  it('does not set CA_COMMAND_BUSY #7254', function()
    115    command('split')
    116    command('autocmd WinEnter * startinsert')
    117    command('wincmd w')
    118    eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
    119  end)
    120 
    121  describe('nvim_exec2', function()
    122    it('always returns table', function()
    123      -- In built version this results into `vim.empty_dict()`
    124      eq({}, api.nvim_exec2('echo "Hello"', {}))
    125      eq({}, api.nvim_exec2('echo "Hello"', { output = false }))
    126      eq({ output = 'Hello' }, api.nvim_exec2('echo "Hello"', { output = true }))
    127    end)
    128 
    129    it('default options', function()
    130      -- Should be equivalent to { output = false }
    131      api.nvim_exec2("let x0 = 'a'", {})
    132      eq('a', api.nvim_get_var('x0'))
    133    end)
    134 
    135    it('one-line input', function()
    136      api.nvim_exec2("let x1 = 'a'", { output = false })
    137      eq('a', api.nvim_get_var('x1'))
    138    end)
    139 
    140    it(':verbose set {option}?', function()
    141      api.nvim_exec2('set nowrap', { output = false })
    142      eq(
    143        { output = 'nowrap\n\tLast set from anonymous :source line 1' },
    144        api.nvim_exec2('verbose set wrap?', { output = true })
    145      )
    146 
    147      -- Using script var to force creation of a script item
    148      api.nvim_exec2(
    149        [[
    150        let s:a = 1
    151        set nowrap
    152      ]],
    153        { output = false }
    154      )
    155      eq(
    156        { output = 'nowrap\n\tLast set from anonymous :source (script id 1) line 2' },
    157        api.nvim_exec2('verbose set wrap?', { output = true })
    158      )
    159    end)
    160 
    161    it('multiline input', function()
    162      -- Heredoc + empty lines.
    163      api.nvim_exec2("let x2 = 'a'\n", { output = false })
    164      eq('a', api.nvim_get_var('x2'))
    165      api.nvim_exec2('lua <<EOF\n\n\n\ny=3\n\n\nEOF', { output = false })
    166      eq(3, api.nvim_eval("luaeval('y')"))
    167 
    168      eq({}, api.nvim_exec2('lua <<EOF\ny=3\nEOF', { output = false }))
    169      eq(3, api.nvim_eval("luaeval('y')"))
    170 
    171      -- Multiple statements
    172      api.nvim_exec2('let x1=1\nlet x2=2\nlet x3=3\n', { output = false })
    173      eq(1, api.nvim_eval('x1'))
    174      eq(2, api.nvim_eval('x2'))
    175      eq(3, api.nvim_eval('x3'))
    176 
    177      -- Functions
    178      api.nvim_exec2('function Foo()\ncall setline(1,["xxx"])\nendfunction', { output = false })
    179      eq('', api.nvim_get_current_line())
    180      api.nvim_exec2('call Foo()', { output = false })
    181      eq('xxx', api.nvim_get_current_line())
    182 
    183      -- Autocmds
    184      api.nvim_exec2('autocmd BufAdd * :let x1 = "Hello"', { output = false })
    185      command('new foo')
    186      eq('Hello', request('nvim_eval', 'g:x1'))
    187 
    188      -- Line continuations
    189      api.nvim_exec2(
    190        [[
    191        let abc = #{
    192          \ a: 1,
    193         "\ b: 2,
    194          \ c: 3
    195          \ }]],
    196        { output = false }
    197      )
    198      eq({ a = 1, c = 3 }, request('nvim_eval', 'g:abc'))
    199 
    200      -- try no spaces before continuations to catch off-by-one error
    201      api.nvim_exec2('let ab = #{\n\\a: 98,\n"\\ b: 2\n\\}', { output = false })
    202      eq({ a = 98 }, request('nvim_eval', 'g:ab'))
    203 
    204      -- Script scope (s:)
    205      eq(
    206        { output = 'ahoy! script-scoped varrrrr' },
    207        api.nvim_exec2(
    208          [[
    209          let s:pirate = 'script-scoped varrrrr'
    210          function! s:avast_ye_hades(s) abort
    211            return a:s .. ' ' .. s:pirate
    212          endfunction
    213          echo <sid>avast_ye_hades('ahoy!')
    214        ]],
    215          { output = true }
    216        )
    217      )
    218 
    219      eq(
    220        { output = "{'output': 'ahoy! script-scoped varrrrr'}" },
    221        api.nvim_exec2(
    222          [[
    223          let s:pirate = 'script-scoped varrrrr'
    224          function! Avast_ye_hades(s) abort
    225            return a:s .. ' ' .. s:pirate
    226          endfunction
    227          echo nvim_exec2('echo Avast_ye_hades(''ahoy!'')', {'output': v:true})
    228        ]],
    229          { output = true }
    230        )
    231      )
    232 
    233      matches(
    234        'Vim%(echo%):E121: Undefined variable: s:pirate$',
    235        pcall_err(
    236          request,
    237          'nvim_exec2',
    238          [[
    239          let s:pirate = 'script-scoped varrrrr'
    240          call nvim_exec2('echo s:pirate', {'output': v:true})
    241        ]],
    242          { output = false }
    243        )
    244      )
    245 
    246      -- Script items are created only on script var access
    247      eq(
    248        { output = '1\n0' },
    249        api.nvim_exec2(
    250          [[
    251          echo expand("<SID>")->empty()
    252          let s:a = 123
    253          echo expand("<SID>")->empty()
    254        ]],
    255          { output = true }
    256        )
    257      )
    258 
    259      eq(
    260        { output = '1\n0' },
    261        api.nvim_exec2(
    262          [[
    263          echo expand("<SID>")->empty()
    264          function s:a() abort
    265          endfunction
    266          echo expand("<SID>")->empty()
    267        ]],
    268          { output = true }
    269        )
    270      )
    271    end)
    272 
    273    it('non-ASCII input', function()
    274      api.nvim_exec2(
    275        [=[
    276        new
    277        exe "normal! i ax \n Ax "
    278        :%s/ax/--a1234--/g | :%s/Ax/--A1234--/g
    279      ]=],
    280        { output = false }
    281      )
    282      command('1')
    283      eq(' --a1234-- ', api.nvim_get_current_line())
    284      command('2')
    285      eq(' --A1234-- ', api.nvim_get_current_line())
    286 
    287      api.nvim_exec2(
    288        [[
    289        new
    290        call setline(1,['xxx'])
    291        call feedkeys('r')
    292        call feedkeys('ñ', 'xt')
    293      ]],
    294        { output = false }
    295      )
    296      eq('ñxx', api.nvim_get_current_line())
    297    end)
    298 
    299    it('can use :finish', function()
    300      api.nvim_exec2('let g:var = 123\nfinish\nlet g:var = 456', {})
    301      eq(123, api.nvim_get_var('var'))
    302    end)
    303 
    304    it('execution error', function()
    305      eq(
    306        'nvim_exec2(), line 1: Vim:E492: Not an editor command: bogus_command',
    307        pcall_err(request, 'nvim_exec2', 'bogus_command', {})
    308      )
    309      eq('', api.nvim_eval('v:errmsg')) -- v:errmsg was not updated.
    310      eq('', eval('v:exception'))
    311 
    312      eq(
    313        'nvim_exec2(), line 1: Vim(buffer):E86: Buffer 23487 does not exist',
    314        pcall_err(request, 'nvim_exec2', 'buffer 23487', {})
    315      )
    316      eq('', eval('v:errmsg')) -- v:errmsg was not updated.
    317      eq('', eval('v:exception'))
    318    end)
    319 
    320    it('recursion', function()
    321      local fname = tmpname()
    322      write_file(fname, 'let x1 = "set from :source file"\n')
    323      -- nvim_exec2
    324      --   :source
    325      --     nvim_exec2
    326      request('nvim_exec2', [[
    327        let x2 = substitute('foo','o','X','g')
    328        let x4 = 'should be overwritten'
    329        call nvim_exec2("source ]] .. fname .. [[\nlet x3 = substitute('foo','foo','set by recursive nvim_exec2','g')\nlet x5='overwritten'\nlet x4=x5\n", {'output': v:false})
    330      ]], { output = false })
    331      eq('set from :source file', request('nvim_get_var', 'x1'))
    332      eq('fXX', request('nvim_get_var', 'x2'))
    333      eq('set by recursive nvim_exec2', request('nvim_get_var', 'x3'))
    334      eq('overwritten', request('nvim_get_var', 'x4'))
    335      eq('overwritten', request('nvim_get_var', 'x5'))
    336      os.remove(fname)
    337    end)
    338 
    339    it('traceback', function()
    340      local fname = tmpname()
    341      write_file(fname, 'echo "hello"\n')
    342      local sourcing_fname = tmpname()
    343      write_file(sourcing_fname, 'call nvim_exec2("source ' .. fname .. '", {"output": v:false})\n')
    344      api.nvim_exec2('set verbose=2', { output = false })
    345      local traceback_output = dedent([[
    346        sourcing "nvim_exec2()"
    347        line 1: sourcing "nvim_exec2() called at nvim_exec2():1"
    348        line 1: sourcing "%s"
    349        line 1: sourcing "nvim_exec2() called at %s:1"
    350        line 1: sourcing "%s"
    351        hello
    352        finished sourcing %s
    353        continuing in nvim_exec2() called at %s:1
    354        finished sourcing nvim_exec2() called at %s:1
    355        continuing in %s
    356        finished sourcing %s
    357        continuing in nvim_exec2() called at nvim_exec2():1
    358        finished sourcing nvim_exec2() called at nvim_exec2():1
    359        continuing in nvim_exec2()
    360        finished sourcing nvim_exec2()]]):format(
    361        sourcing_fname,
    362        sourcing_fname,
    363        fname,
    364        fname,
    365        sourcing_fname,
    366        sourcing_fname,
    367        sourcing_fname,
    368        sourcing_fname
    369      )
    370      eq(
    371        { output = traceback_output },
    372        api.nvim_exec2(
    373          'call nvim_exec2("source ' .. sourcing_fname .. '", {"output": v:false})',
    374          { output = true }
    375        )
    376      )
    377      os.remove(fname)
    378      os.remove(sourcing_fname)
    379    end)
    380 
    381    it('returns output', function()
    382      eq(
    383        { output = 'this is spinal tap' },
    384        api.nvim_exec2('lua <<EOF\n\n\nprint("this is spinal tap")\n\n\nEOF', { output = true })
    385      )
    386      eq({ output = '' }, api.nvim_exec2('echo', { output = true }))
    387      eq({ output = 'foo 42' }, api.nvim_exec2('echo "foo" 42', { output = true }))
    388      -- Returns output in cmdline mode #35321
    389      feed(':')
    390      eq({ output = 'foo 42' }, api.nvim_exec2('echo "foo" 42', { output = true }))
    391    end)
    392 
    393    it('displays messages when opts.output=false', function()
    394      local screen = Screen.new(40, 8)
    395      api.nvim_exec2("echo 'hello'", { output = false })
    396      screen:expect {
    397        grid = [[
    398        ^                                        |
    399        {1:~                                       }|*6
    400        hello                                   |
    401      ]],
    402      }
    403    end)
    404 
    405    it("doesn't display messages when output=true", function()
    406      local screen = Screen.new(40, 6)
    407      api.nvim_exec2("echo 'hello'", { output = true })
    408      screen:expect {
    409        grid = [[
    410        ^                                        |
    411        {1:~                                       }|*4
    412                                                |
    413      ]],
    414      }
    415      exec([[
    416        func Print()
    417          call nvim_exec2('echo "hello"', { 'output': v:true })
    418        endfunc
    419      ]])
    420      feed([[:echon 1 | call Print() | echon 5<CR>]])
    421      screen:expect {
    422        grid = [[
    423        ^                                        |
    424        {1:~                                       }|*4
    425        15                                      |
    426      ]],
    427      }
    428    end)
    429 
    430    it('errors properly when command too recursive', function()
    431      exec_lua([[
    432        _G.success = false
    433        vim.api.nvim_create_user_command('Test', function()
    434          vim.api.nvim_exec2('Test', {})
    435          _G.success = true
    436        end, {})
    437      ]])
    438      pcall_err(command, 'Test')
    439      assert_alive()
    440      eq(false, exec_lua('return _G.success'))
    441    end)
    442 
    443    it('redir_write() message column is reset with ext_messages', function()
    444      exec_lua('vim.ui_attach(1, { ext_messages = true }, function() end)')
    445      api.nvim_exec2('hi VisualNC', { output = true })
    446      eq('VisualNC       xxx cleared', api.nvim_exec2('hi VisualNC', { output = true }).output)
    447    end)
    448  end)
    449 
    450  describe('nvim_command', function()
    451    it('works', function()
    452      local fname = tmpname()
    453      command('new')
    454      command('edit ' .. fname)
    455      command('normal itesting\napi')
    456      command('w')
    457      local f = assert(io.open(fname))
    458      if is_os('win') then
    459        eq('testing\r\napi\r\n', f:read('*a'))
    460      else
    461        eq('testing\napi\n', f:read('*a'))
    462      end
    463      f:close()
    464      os.remove(fname)
    465    end)
    466 
    467    it('Vimscript validation error: fails with specific error', function()
    468      local status, rv = pcall(command, 'bogus_command')
    469      eq(false, status) -- nvim_command() failed.
    470      eq('E492:', string.match(rv, 'E%d*:')) -- Vimscript error was returned.
    471      eq('', api.nvim_eval('v:errmsg')) -- v:errmsg was not updated.
    472      eq('', eval('v:exception'))
    473    end)
    474 
    475    it('Vimscript execution error: fails with specific error', function()
    476      local status, rv = pcall(command, 'buffer 23487')
    477      eq(false, status) -- nvim_command() failed.
    478      eq('E86: Buffer 23487 does not exist', string.match(rv, 'E%d*:.*'))
    479      eq('', eval('v:errmsg')) -- v:errmsg was not updated.
    480      eq('', eval('v:exception'))
    481    end)
    482 
    483    it('gives E493 instead of prompting on backwards range', function()
    484      command('split')
    485      eq(
    486        'Vim(windo):E493: Backwards range given: 2,1windo echo',
    487        pcall_err(command, '2,1windo echo')
    488      )
    489    end)
    490  end)
    491 
    492  describe('nvim_command_output', function()
    493    it('does not induce hit-enter prompt', function()
    494      api.nvim_ui_attach(80, 20, {})
    495      -- Induce a hit-enter prompt use nvim_input (non-blocking).
    496      command('set cmdheight=1')
    497      api.nvim_input([[:echo "hi\nhi2"<CR>]])
    498 
    499      -- Verify hit-enter prompt.
    500      eq({ mode = 'r', blocking = true }, api.nvim_get_mode())
    501      api.nvim_input([[<C-c>]])
    502 
    503      -- Verify NO hit-enter prompt.
    504      command_output([[echo "hi\nhi2"]])
    505      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
    506    end)
    507 
    508    it('captures command output', function()
    509      eq('this is\nspinal tap', command_output([[echo "this is\nspinal tap"]]))
    510      eq('no line ending!', command_output([[echon "no line ending!"]]))
    511    end)
    512 
    513    it('captures empty command output', function()
    514      eq('', command_output('echo'))
    515    end)
    516 
    517    it('captures single-char command output', function()
    518      eq('x', command_output('echo "x"'))
    519    end)
    520 
    521    it('captures multiple commands', function()
    522      eq('foo\n  1 %a   "[No Name]"                    line 1', command_output('echo "foo" | ls'))
    523    end)
    524 
    525    it('captures nested execute()', function()
    526      eq(
    527        '\nnested1\nnested2\n  1 %a   "[No Name]"                    line 1',
    528        command_output([[echo execute('echo "nested1\nnested2"') | ls]])
    529      )
    530    end)
    531 
    532    it('captures nested nvim_command_output()', function()
    533      eq(
    534        'nested1\nnested2\n  1 %a   "[No Name]"                    line 1',
    535        command_output([[echo nvim_command_output('echo "nested1\nnested2"') | ls]])
    536      )
    537    end)
    538 
    539    it('returns shell |:!| output', function()
    540      local win_lf = is_os('win') and '\r' or ''
    541      eq(':!echo foo\r\n\nfoo' .. win_lf .. '\n', command_output([[!echo foo]]))
    542    end)
    543 
    544    it('Vimscript validation error: fails with specific error', function()
    545      local status, rv = pcall(command_output, 'bogus commannnd')
    546      eq(false, status) -- nvim_command_output() failed.
    547      eq('E492: Not an editor command: bogus commannnd', string.match(rv, 'E%d*:.*'))
    548      eq('', eval('v:errmsg')) -- v:errmsg was not updated.
    549      -- Verify NO hit-enter prompt.
    550      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
    551    end)
    552 
    553    it('Vimscript execution error: fails with specific error', function()
    554      local status, rv = pcall(command_output, 'buffer 42')
    555      eq(false, status) -- nvim_command_output() failed.
    556      eq('E86: Buffer 42 does not exist', string.match(rv, 'E%d*:.*'))
    557      eq('', eval('v:errmsg')) -- v:errmsg was not updated.
    558      -- Verify NO hit-enter prompt.
    559      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
    560    end)
    561 
    562    it('does not cause heap buffer overflow with large output', function()
    563      eq(eval('string(range(1000000))'), command_output('echo range(1000000)'))
    564    end)
    565  end)
    566 
    567  describe('nvim_eval', function()
    568    it('works', function()
    569      command('let g:v1 = "a"')
    570      command('let g:v2 = [1, 2, {"v3": 3}]')
    571      eq({ v1 = 'a', v2 = { 1, 2, { v3 = 3 } } }, api.nvim_eval('g:'))
    572    end)
    573 
    574    it('handles NULL-initialized strings correctly', function()
    575      eq(1, api.nvim_eval("matcharg(1) == ['', '']"))
    576      eq({ '', '' }, api.nvim_eval('matcharg(1)'))
    577    end)
    578 
    579    it('works under deprecated name', function()
    580      eq(2, request('vim_eval', '1+1'))
    581    end)
    582 
    583    it('Vimscript error: returns error details, does NOT update v:errmsg', function()
    584      eq('Vim:E121: Undefined variable: bogus', pcall_err(request, 'nvim_eval', 'bogus expression'))
    585      eq('', eval('v:errmsg')) -- v:errmsg was not updated.
    586    end)
    587 
    588    it('can return Lua function to Lua code', function()
    589      eq(
    590        [["a string with \"double quotes\" and 'single quotes'"]],
    591        exec_lua([=[
    592          local fun = vim.api.nvim_eval([[luaeval('string.format')]])
    593          return fun('%q', [[a string with "double quotes" and 'single quotes']])
    594        ]=])
    595      )
    596    end)
    597  end)
    598 
    599  describe('nvim_call_function', function()
    600    it('works', function()
    601      api.nvim_call_function('setqflist', { { { filename = 'something', lnum = 17 } }, 'r' })
    602      eq(17, api.nvim_call_function('getqflist', {})[1].lnum)
    603      eq(17, api.nvim_call_function('eval', { 17 }))
    604      eq('foo', api.nvim_call_function('simplify', { 'this/./is//redundant/../../../foo' }))
    605    end)
    606 
    607    it('Vimscript validation error: returns specific error, does NOT update v:errmsg', function()
    608      eq(
    609        'Vim:E117: Unknown function: bogus function',
    610        pcall_err(request, 'nvim_call_function', 'bogus function', { 'arg1' })
    611      )
    612      eq(
    613        'Vim:E119: Not enough arguments for function: atan',
    614        pcall_err(request, 'nvim_call_function', 'atan', {})
    615      )
    616      eq('', eval('v:exception'))
    617      eq('', eval('v:errmsg')) -- v:errmsg was not updated.
    618    end)
    619 
    620    it('Vimscript error: returns error details, does NOT update v:errmsg', function()
    621      eq(
    622        'Vim:E808: Number or Float required',
    623        pcall_err(request, 'nvim_call_function', 'atan', { 'foo' })
    624      )
    625      eq(
    626        'Vim:Invalid channel stream "xxx"',
    627        pcall_err(request, 'nvim_call_function', 'chanclose', { 999, 'xxx' })
    628      )
    629      eq(
    630        'Vim:E900: Invalid channel id',
    631        pcall_err(request, 'nvim_call_function', 'chansend', { 999, 'foo' })
    632      )
    633      eq('', eval('v:exception'))
    634      eq('', eval('v:errmsg')) -- v:errmsg was not updated.
    635    end)
    636 
    637    it('Vimscript exception: returns exception details, does NOT update v:errmsg', function()
    638      source([[
    639        function! Foo() abort
    640          throw 'wtf'
    641        endfunction
    642      ]])
    643      eq('function Foo, line 1: wtf', pcall_err(request, 'nvim_call_function', 'Foo', {}))
    644      eq('', eval('v:exception'))
    645      eq('', eval('v:errmsg')) -- v:errmsg was not updated.
    646    end)
    647 
    648    it('validation', function()
    649      -- stylua: ignore
    650      local too_many_args = { 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x' }
    651      source([[
    652        function! Foo(...) abort
    653          echo a:000
    654        endfunction
    655      ]])
    656      -- E740
    657      eq(
    658        'Function called with too many arguments',
    659        pcall_err(request, 'nvim_call_function', 'Foo', too_many_args)
    660      )
    661    end)
    662 
    663    it('can return Lua function to Lua code', function()
    664      eq(
    665        [["a string with \"double quotes\" and 'single quotes'"]],
    666        exec_lua([=[
    667          local fun = vim.api.nvim_call_function('luaeval', { 'string.format' })
    668          return fun('%q', [[a string with "double quotes" and 'single quotes']])
    669        ]=])
    670      )
    671    end)
    672  end)
    673 
    674  describe('nvim_call_dict_function', function()
    675    it('invokes Vimscript dict function', function()
    676      source([[
    677        function! F(name) dict
    678          return self.greeting.', '.a:name.'!'
    679        endfunction
    680        let g:test_dict_fn = { 'greeting':'Hello', 'F':function('F') }
    681 
    682        let g:test_dict_fn2 = { 'greeting':'Hi' }
    683        function g:test_dict_fn2.F2(name)
    684          return self.greeting.', '.a:name.' ...'
    685        endfunction
    686      ]])
    687 
    688      -- :help Dictionary-function
    689      eq('Hello, World!', api.nvim_call_dict_function('g:test_dict_fn', 'F', { 'World' }))
    690      -- Funcref is sent as NIL over RPC.
    691      eq({ greeting = 'Hello', F = NIL }, api.nvim_get_var('test_dict_fn'))
    692 
    693      -- :help numbered-function
    694      eq('Hi, Moon ...', api.nvim_call_dict_function('g:test_dict_fn2', 'F2', { 'Moon' }))
    695      -- Funcref is sent as NIL over RPC.
    696      eq({ greeting = 'Hi', F2 = NIL }, api.nvim_get_var('test_dict_fn2'))
    697 
    698      -- Function specified via RPC dict.
    699      source('function! G() dict\n  return "@".(self.result)."@"\nendfunction')
    700      eq('@it works@', api.nvim_call_dict_function({ result = 'it works', G = 'G' }, 'G', {}))
    701    end)
    702 
    703    it('validation', function()
    704      command('let g:d={"baz":"zub","meep":[]}')
    705      eq(
    706        'Not found: bogus',
    707        pcall_err(request, 'nvim_call_dict_function', 'g:d', 'bogus', { 1, 2 })
    708      )
    709      eq(
    710        'Not a function: baz',
    711        pcall_err(request, 'nvim_call_dict_function', 'g:d', 'baz', { 1, 2 })
    712      )
    713      eq(
    714        'Not a function: meep',
    715        pcall_err(request, 'nvim_call_dict_function', 'g:d', 'meep', { 1, 2 })
    716      )
    717      eq(
    718        'Vim:E117: Unknown function: f',
    719        pcall_err(request, 'nvim_call_dict_function', { f = '' }, 'f', { 1, 2 })
    720      )
    721      eq(
    722        'Not a function: f',
    723        pcall_err(request, 'nvim_call_dict_function', "{ 'f': '' }", 'f', { 1, 2 })
    724      )
    725      eq(
    726        'dict argument type must be String or Dict',
    727        pcall_err(request, 'nvim_call_dict_function', 42, 'f', { 1, 2 })
    728      )
    729      eq(
    730        'Vim:E121: Undefined variable: foo',
    731        pcall_err(request, 'nvim_call_dict_function', 'foo', 'f', { 1, 2 })
    732      )
    733      eq('dict not found', pcall_err(request, 'nvim_call_dict_function', '42', 'f', { 1, 2 }))
    734      eq(
    735        'Invalid (empty) function name',
    736        pcall_err(request, 'nvim_call_dict_function', "{ 'f': '' }", '', { 1, 2 })
    737      )
    738    end)
    739  end)
    740 
    741  describe('nvim_set_current_dir', function()
    742    local start_dir
    743    local test_dir = 'Xtest_set_current_dir'
    744 
    745    before_each(function()
    746      fn.mkdir(test_dir)
    747      start_dir = fn.getcwd()
    748    end)
    749 
    750    after_each(function()
    751      n.rmdir(test_dir)
    752    end)
    753 
    754    it('works', function()
    755      api.nvim_set_current_dir(test_dir)
    756      eq(start_dir .. n.get_pathsep() .. test_dir, fn.getcwd())
    757    end)
    758 
    759    it('sets previous directory', function()
    760      api.nvim_set_current_dir(test_dir)
    761      command('cd -')
    762      eq(start_dir, fn.getcwd())
    763    end)
    764  end)
    765 
    766  describe('nvim_exec_lua', function()
    767    it('works', function()
    768      api.nvim_exec_lua('vim.api.nvim_set_var("test", 3)', {})
    769      eq(3, api.nvim_get_var('test'))
    770 
    771      eq(17, api.nvim_exec_lua('a, b = ...\nreturn a + b', { 10, 7 }))
    772 
    773      eq(NIL, api.nvim_exec_lua('function xx(a,b)\nreturn a..b\nend', {}))
    774      eq('xy', api.nvim_exec_lua('return xx(...)', { 'x', 'y' }))
    775 
    776      -- Deprecated name: nvim_execute_lua.
    777      eq('xy', api.nvim_execute_lua('return xx(...)', { 'x', 'y' }))
    778    end)
    779 
    780    it('reports errors', function()
    781      eq([['=' expected near '+']], pcall_err(api.nvim_exec_lua, 'a+*b', {}))
    782      eq([[unexpected symbol near '1']], pcall_err(api.nvim_exec_lua, '1+2', {}))
    783      eq([[unexpected symbol]], pcall_err(api.nvim_exec_lua, 'aa=bb\0', {}))
    784      eq(
    785        [[attempt to call global 'bork' (a nil value)]],
    786        pcall_err(api.nvim_exec_lua, 'bork()', {})
    787      )
    788      eq('did\nthe\nfail', pcall_err(api.nvim_exec_lua, 'error("did\\nthe\\nfail")', {}))
    789    end)
    790 
    791    it('uses native float values', function()
    792      eq(2.5, api.nvim_exec_lua('return select(1, ...)', { 2.5 }))
    793      eq('2.5', api.nvim_exec_lua('return vim.inspect(...)', { 2.5 }))
    794 
    795      -- "special" float values are still accepted as return values.
    796      eq(2.5, api.nvim_exec_lua("return vim.api.nvim_eval('2.5')", {}))
    797      eq(
    798        '{\n  [false] = 2.5,\n  [true] = 3\n}',
    799        api.nvim_exec_lua("return vim.inspect(vim.api.nvim_eval('2.5'))", {})
    800      )
    801    end)
    802  end)
    803 
    804  describe('nvim_input', function()
    805    it('Vimscript error: does NOT fail, updates v:errmsg', function()
    806      local status, _ = pcall(api.nvim_input, ':call bogus_fn()<CR>')
    807      local v_errnum = string.match(api.nvim_eval('v:errmsg'), 'E%d*:')
    808      eq(true, status) -- nvim_input() did not fail.
    809      eq('E117:', v_errnum) -- v:errmsg was updated.
    810    end)
    811 
    812    it('does not crash even if trans_special result is largest #11788, #12287', function()
    813      command("call nvim_input('<M-'.nr2char(0x40000000).'>')")
    814      eq(1, eval('1'))
    815    end)
    816  end)
    817 
    818  describe('nvim_paste', function()
    819    it('validation', function()
    820      eq("Invalid 'phase': -2", pcall_err(request, 'nvim_paste', 'foo', true, -2))
    821      eq("Invalid 'phase': 4", pcall_err(request, 'nvim_paste', 'foo', true, 4))
    822    end)
    823    local function run_streamed_paste_tests()
    824      it('stream: multiple chunks form one undo-block', function()
    825        api.nvim_paste('1/chunk 1 (start)\n', true, 1)
    826        api.nvim_paste('1/chunk 2 (end)\n', true, 3)
    827        local expected1 = [[
    828          1/chunk 1 (start)
    829          1/chunk 2 (end)
    830          ]]
    831        expect(expected1)
    832        api.nvim_paste('2/chunk 1 (start)\n', true, 1)
    833        api.nvim_paste('2/chunk 2\n', true, 2)
    834        expect([[
    835          1/chunk 1 (start)
    836          1/chunk 2 (end)
    837          2/chunk 1 (start)
    838          2/chunk 2
    839          ]])
    840        api.nvim_paste('2/chunk 3\n', true, 2)
    841        api.nvim_paste('2/chunk 4 (end)\n', true, 3)
    842        expect([[
    843          1/chunk 1 (start)
    844          1/chunk 2 (end)
    845          2/chunk 1 (start)
    846          2/chunk 2
    847          2/chunk 3
    848          2/chunk 4 (end)
    849          ]])
    850        feed('u') -- Undo.
    851        expect(expected1)
    852      end)
    853      it("stream: multiple chunks sets correct '[ mark", function()
    854        -- Pastes single chunk
    855        api.nvim_paste('aaaaaa\n', true, -1)
    856        eq({ 0, 1, 1, 0 }, fn.getpos("'["))
    857        -- Pastes an empty chunk
    858        api.nvim_paste('', true, -1)
    859        eq({ 0, 2, 1, 0 }, fn.getpos("'["))
    860        -- Pastes some chunks on empty line
    861        api.nvim_paste('1/chunk 1 (start)\n', true, 1)
    862        eq({ 0, 2, 1, 0 }, fn.getpos("'["))
    863        api.nvim_paste('1/chunk 2\n', true, 2)
    864        eq({ 0, 2, 1, 0 }, fn.getpos("'["))
    865        api.nvim_paste('1/chunk 3 (end)\n', true, 3)
    866        eq({ 0, 2, 1, 0 }, fn.getpos("'["))
    867        -- Pastes some chunks on non-empty line
    868        api.nvim_paste('aaaaaa', true, -1)
    869        eq({ 0, 5, 1, 0 }, fn.getpos("'["))
    870        api.nvim_paste('bbbbbb', true, 1)
    871        eq({ 0, 5, 7, 0 }, fn.getpos("'["))
    872        api.nvim_paste('cccccc', true, 2)
    873        eq({ 0, 5, 7, 0 }, fn.getpos("'["))
    874        api.nvim_paste('dddddd\n', true, 3)
    875        eq({ 0, 5, 7, 0 }, fn.getpos("'["))
    876        -- Pastes some empty chunks between non-empty chunks
    877        api.nvim_paste('', true, 1)
    878        eq({ 0, 5, 7, 0 }, fn.getpos("'["))
    879        api.nvim_paste('a', true, 2)
    880        eq({ 0, 6, 1, 0 }, fn.getpos("'["))
    881        api.nvim_paste('', true, 2)
    882        eq({ 0, 6, 1, 0 }, fn.getpos("'["))
    883        api.nvim_paste('a', true, 3)
    884        eq({ 0, 6, 1, 0 }, fn.getpos("'["))
    885      end)
    886      it('stream: Insert mode', function()
    887        -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
    888        feed('afoo<Esc>u')
    889        feed('i')
    890        api.nvim_paste('aaaaaa', false, 1)
    891        api.nvim_paste('bbbbbb', false, 2)
    892        api.nvim_paste('cccccc', false, 2)
    893        api.nvim_paste('dddddd', false, 3)
    894        expect('aaaaaabbbbbbccccccdddddd')
    895        feed('<Esc>u')
    896        expect('')
    897      end)
    898      describe('stream: Normal mode', function()
    899        describe('on empty line', function()
    900          before_each(function()
    901            -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
    902            feed('afoo<Esc>u')
    903          end)
    904          after_each(function()
    905            feed('u')
    906            expect('')
    907          end)
    908          it('pasting one line', function()
    909            api.nvim_paste('aaaaaa', false, 1)
    910            api.nvim_paste('bbbbbb', false, 2)
    911            api.nvim_paste('cccccc', false, 2)
    912            api.nvim_paste('dddddd', false, 3)
    913            expect('aaaaaabbbbbbccccccdddddd')
    914          end)
    915          it('pasting multiple lines', function()
    916            api.nvim_paste('aaaaaa\n', false, 1)
    917            api.nvim_paste('bbbbbb\n', false, 2)
    918            api.nvim_paste('cccccc\n', false, 2)
    919            api.nvim_paste('dddddd', false, 3)
    920            expect([[
    921            aaaaaa
    922            bbbbbb
    923            cccccc
    924            dddddd]])
    925          end)
    926        end)
    927        describe('not at the end of a line', function()
    928          before_each(function()
    929            feed('i||<Esc>')
    930            -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
    931            feed('afoo<Esc>u')
    932            feed('0')
    933          end)
    934          after_each(function()
    935            feed('u')
    936            expect('||')
    937          end)
    938          it('pasting one line', function()
    939            api.nvim_paste('aaaaaa', false, 1)
    940            api.nvim_paste('bbbbbb', false, 2)
    941            api.nvim_paste('cccccc', false, 2)
    942            api.nvim_paste('dddddd', false, 3)
    943            expect('|aaaaaabbbbbbccccccdddddd|')
    944          end)
    945          it('pasting multiple lines', function()
    946            api.nvim_paste('aaaaaa\n', false, 1)
    947            api.nvim_paste('bbbbbb\n', false, 2)
    948            api.nvim_paste('cccccc\n', false, 2)
    949            api.nvim_paste('dddddd', false, 3)
    950            expect([[
    951            |aaaaaa
    952            bbbbbb
    953            cccccc
    954            dddddd|]])
    955          end)
    956        end)
    957        describe('at the end of a line', function()
    958          before_each(function()
    959            feed('i||<Esc>')
    960            -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
    961            feed('afoo<Esc>u')
    962            feed('2|')
    963          end)
    964          after_each(function()
    965            feed('u')
    966            expect('||')
    967          end)
    968          it('pasting one line', function()
    969            api.nvim_paste('aaaaaa', false, 1)
    970            api.nvim_paste('bbbbbb', false, 2)
    971            api.nvim_paste('cccccc', false, 2)
    972            api.nvim_paste('dddddd', false, 3)
    973            expect('||aaaaaabbbbbbccccccdddddd')
    974          end)
    975          it('pasting multiple lines', function()
    976            api.nvim_paste('aaaaaa\n', false, 1)
    977            api.nvim_paste('bbbbbb\n', false, 2)
    978            api.nvim_paste('cccccc\n', false, 2)
    979            api.nvim_paste('dddddd', false, 3)
    980            expect([[
    981              ||aaaaaa
    982              bbbbbb
    983              cccccc
    984              dddddd]])
    985          end)
    986        end)
    987      end)
    988      describe('stream: Visual mode', function()
    989        describe('neither end at the end of a line', function()
    990          before_each(function()
    991            feed('i|xxx<CR>xxx|<Esc>')
    992            -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
    993            feed('afoo<Esc>u')
    994            feed('3|vhk')
    995          end)
    996          after_each(function()
    997            feed('u')
    998            expect([[
    999            |xxx
   1000            xxx|]])
   1001          end)
   1002          it('with non-empty chunks', function()
   1003            api.nvim_paste('aaaaaa', false, 1)
   1004            api.nvim_paste('bbbbbb', false, 2)
   1005            api.nvim_paste('cccccc', false, 2)
   1006            api.nvim_paste('dddddd', false, 3)
   1007            expect('|aaaaaabbbbbbccccccdddddd|')
   1008          end)
   1009          it('with empty first chunk', function()
   1010            api.nvim_paste('', false, 1)
   1011            api.nvim_paste('bbbbbb', false, 2)
   1012            api.nvim_paste('cccccc', false, 2)
   1013            api.nvim_paste('dddddd', false, 3)
   1014            expect('|bbbbbbccccccdddddd|')
   1015          end)
   1016          it('with all chunks empty', function()
   1017            api.nvim_paste('', false, 1)
   1018            api.nvim_paste('', false, 2)
   1019            api.nvim_paste('', false, 2)
   1020            api.nvim_paste('', false, 3)
   1021            expect('||')
   1022          end)
   1023        end)
   1024        describe('cursor at the end of a line', function()
   1025          before_each(function()
   1026            feed('i||xxx<CR>xxx<Esc>')
   1027            -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
   1028            feed('afoo<Esc>u')
   1029            feed('3|vko')
   1030          end)
   1031          after_each(function()
   1032            feed('u')
   1033            expect([[
   1034              ||xxx
   1035              xxx]])
   1036          end)
   1037          it('with non-empty chunks', function()
   1038            api.nvim_paste('aaaaaa', false, 1)
   1039            api.nvim_paste('bbbbbb', false, 2)
   1040            api.nvim_paste('cccccc', false, 2)
   1041            api.nvim_paste('dddddd', false, 3)
   1042            expect('||aaaaaabbbbbbccccccdddddd')
   1043          end)
   1044          it('with empty first chunk', function()
   1045            api.nvim_paste('', false, 1)
   1046            api.nvim_paste('bbbbbb', false, 2)
   1047            api.nvim_paste('cccccc', false, 2)
   1048            api.nvim_paste('dddddd', false, 3)
   1049            expect('||bbbbbbccccccdddddd')
   1050          end)
   1051        end)
   1052        describe('other end at the end of a line', function()
   1053          before_each(function()
   1054            feed('i||xxx<CR>xxx<Esc>')
   1055            -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
   1056            feed('afoo<Esc>u')
   1057            feed('3|vk')
   1058          end)
   1059          after_each(function()
   1060            feed('u')
   1061            expect([[
   1062              ||xxx
   1063              xxx]])
   1064          end)
   1065          it('with non-empty chunks', function()
   1066            api.nvim_paste('aaaaaa', false, 1)
   1067            api.nvim_paste('bbbbbb', false, 2)
   1068            api.nvim_paste('cccccc', false, 2)
   1069            api.nvim_paste('dddddd', false, 3)
   1070            expect('||aaaaaabbbbbbccccccdddddd')
   1071          end)
   1072          it('with empty first chunk', function()
   1073            api.nvim_paste('', false, 1)
   1074            api.nvim_paste('bbbbbb', false, 2)
   1075            api.nvim_paste('cccccc', false, 2)
   1076            api.nvim_paste('dddddd', false, 3)
   1077            expect('||bbbbbbccccccdddddd')
   1078          end)
   1079        end)
   1080      end)
   1081      describe('stream: linewise Visual mode', function()
   1082        before_each(function()
   1083          feed('i123456789<CR>987654321<CR>123456789<Esc>')
   1084          -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
   1085          feed('afoo<Esc>u')
   1086        end)
   1087        after_each(function()
   1088          feed('u')
   1089          expect([[
   1090            123456789
   1091            987654321
   1092            123456789]])
   1093        end)
   1094        describe('selecting the start of a file', function()
   1095          before_each(function()
   1096            feed('ggV')
   1097          end)
   1098          it('pasting text without final new line', function()
   1099            api.nvim_paste('aaaaaa\n', false, 1)
   1100            api.nvim_paste('bbbbbb\n', false, 2)
   1101            api.nvim_paste('cccccc\n', false, 2)
   1102            api.nvim_paste('dddddd', false, 3)
   1103            expect([[
   1104              aaaaaa
   1105              bbbbbb
   1106              cccccc
   1107              dddddd987654321
   1108              123456789]])
   1109          end)
   1110          it('pasting text with final new line', function()
   1111            api.nvim_paste('aaaaaa\n', false, 1)
   1112            api.nvim_paste('bbbbbb\n', false, 2)
   1113            api.nvim_paste('cccccc\n', false, 2)
   1114            api.nvim_paste('dddddd\n', false, 3)
   1115            expect([[
   1116              aaaaaa
   1117              bbbbbb
   1118              cccccc
   1119              dddddd
   1120              987654321
   1121              123456789]])
   1122          end)
   1123        end)
   1124        describe('selecting the middle of a file', function()
   1125          before_each(function()
   1126            feed('2ggV')
   1127          end)
   1128          it('pasting text without final new line', function()
   1129            api.nvim_paste('aaaaaa\n', false, 1)
   1130            api.nvim_paste('bbbbbb\n', false, 2)
   1131            api.nvim_paste('cccccc\n', false, 2)
   1132            api.nvim_paste('dddddd', false, 3)
   1133            expect([[
   1134              123456789
   1135              aaaaaa
   1136              bbbbbb
   1137              cccccc
   1138              dddddd123456789]])
   1139          end)
   1140          it('pasting text with final new line', function()
   1141            api.nvim_paste('aaaaaa\n', false, 1)
   1142            api.nvim_paste('bbbbbb\n', false, 2)
   1143            api.nvim_paste('cccccc\n', false, 2)
   1144            api.nvim_paste('dddddd\n', false, 3)
   1145            expect([[
   1146              123456789
   1147              aaaaaa
   1148              bbbbbb
   1149              cccccc
   1150              dddddd
   1151              123456789]])
   1152          end)
   1153        end)
   1154        describe('selecting the end of a file', function()
   1155          before_each(function()
   1156            feed('3ggV')
   1157          end)
   1158          it('pasting text without final new line', function()
   1159            api.nvim_paste('aaaaaa\n', false, 1)
   1160            api.nvim_paste('bbbbbb\n', false, 2)
   1161            api.nvim_paste('cccccc\n', false, 2)
   1162            api.nvim_paste('dddddd', false, 3)
   1163            expect([[
   1164              123456789
   1165              987654321
   1166              aaaaaa
   1167              bbbbbb
   1168              cccccc
   1169              dddddd]])
   1170          end)
   1171          it('pasting text with final new line', function()
   1172            api.nvim_paste('aaaaaa\n', false, 1)
   1173            api.nvim_paste('bbbbbb\n', false, 2)
   1174            api.nvim_paste('cccccc\n', false, 2)
   1175            api.nvim_paste('dddddd\n', false, 3)
   1176            expect([[
   1177              123456789
   1178              987654321
   1179              aaaaaa
   1180              bbbbbb
   1181              cccccc
   1182              dddddd
   1183              ]])
   1184          end)
   1185        end)
   1186        describe('selecting the whole file', function()
   1187          before_each(function()
   1188            feed('ggVG')
   1189          end)
   1190          it('pasting text without final new line', function()
   1191            api.nvim_paste('aaaaaa\n', false, 1)
   1192            api.nvim_paste('bbbbbb\n', false, 2)
   1193            api.nvim_paste('cccccc\n', false, 2)
   1194            api.nvim_paste('dddddd', false, 3)
   1195            expect([[
   1196              aaaaaa
   1197              bbbbbb
   1198              cccccc
   1199              dddddd]])
   1200          end)
   1201          it('pasting text with final new line', function()
   1202            api.nvim_paste('aaaaaa\n', false, 1)
   1203            api.nvim_paste('bbbbbb\n', false, 2)
   1204            api.nvim_paste('cccccc\n', false, 2)
   1205            api.nvim_paste('dddddd\n', false, 3)
   1206            expect([[
   1207              aaaaaa
   1208              bbbbbb
   1209              cccccc
   1210              dddddd
   1211              ]])
   1212          end)
   1213        end)
   1214      end)
   1215    end
   1216    describe('without virtualedit,', function()
   1217      run_streamed_paste_tests()
   1218    end)
   1219    describe('with virtualedit=onemore,', function()
   1220      before_each(function()
   1221        command('set virtualedit=onemore')
   1222      end)
   1223      run_streamed_paste_tests()
   1224    end)
   1225    it('non-streaming', function()
   1226      -- With final "\n".
   1227      api.nvim_paste('line 1\nline 2\nline 3\n', true, -1)
   1228      expect([[
   1229        line 1
   1230        line 2
   1231        line 3
   1232        ]])
   1233      eq({ 0, 4, 1, 0 }, fn.getpos('.')) -- Cursor follows the paste.
   1234      eq(false, api.nvim_get_option_value('paste', {}))
   1235      command('%delete _')
   1236      -- Without final "\n".
   1237      api.nvim_paste('line 1\nline 2\nline 3', true, -1)
   1238      expect([[
   1239        line 1
   1240        line 2
   1241        line 3]])
   1242      eq({ 0, 3, 6, 0 }, fn.getpos('.'))
   1243      command('%delete _')
   1244      -- CRLF #10872
   1245      api.nvim_paste('line 1\r\nline 2\r\nline 3\r\n', true, -1)
   1246      expect([[
   1247        line 1
   1248        line 2
   1249        line 3
   1250        ]])
   1251      eq({ 0, 4, 1, 0 }, fn.getpos('.'))
   1252      command('%delete _')
   1253      -- CRLF without final "\n".
   1254      api.nvim_paste('line 1\r\nline 2\r\nline 3\r', true, -1)
   1255      expect([[
   1256        line 1
   1257        line 2
   1258        line 3
   1259        ]])
   1260      eq({ 0, 4, 1, 0 }, fn.getpos('.'))
   1261      command('%delete _')
   1262      -- CRLF without final "\r\n".
   1263      api.nvim_paste('line 1\r\nline 2\r\nline 3', true, -1)
   1264      expect([[
   1265        line 1
   1266        line 2
   1267        line 3]])
   1268      eq({ 0, 3, 6, 0 }, fn.getpos('.'))
   1269      command('%delete _')
   1270      -- Various other junk.
   1271      api.nvim_paste('line 1\r\n\r\rline 2\nline 3\rline 4\r', true, -1)
   1272      expect('line 1\n\n\nline 2\nline 3\nline 4\n')
   1273      eq({ 0, 7, 1, 0 }, fn.getpos('.'))
   1274      eq(false, api.nvim_get_option_value('paste', {}))
   1275    end)
   1276    it('Replace-mode', function()
   1277      -- Within single line
   1278      api.nvim_put({ 'aabbccdd', 'eeffgghh', 'iijjkkll' }, 'c', true, false)
   1279      command('normal l')
   1280      command('startreplace')
   1281      api.nvim_paste('123456', true, -1)
   1282      expect([[
   1283      a123456d
   1284      eeffgghh
   1285      iijjkkll]])
   1286      command('%delete _')
   1287      -- Across lines
   1288      api.nvim_put({ 'aabbccdd', 'eeffgghh', 'iijjkkll' }, 'c', true, false)
   1289      command('normal l')
   1290      command('startreplace')
   1291      api.nvim_paste('123\n456', true, -1)
   1292      expect([[
   1293      a123
   1294      456d
   1295      eeffgghh
   1296      iijjkkll]])
   1297    end)
   1298    it('when searching in Visual mode', function()
   1299      feed('v/')
   1300      api.nvim_paste('aabbccdd', true, -1)
   1301      eq('aabbccdd', fn.getcmdline())
   1302      expect('')
   1303    end)
   1304    it('mappings are disabled in Cmdline mode', function()
   1305      command('cnoremap a b')
   1306      feed(':')
   1307      api.nvim_paste('a', true, -1)
   1308      eq('a', fn.getcmdline())
   1309    end)
   1310    it('pasted text is saved in cmdline history when <CR> comes from mapping #20957', function()
   1311      command('cnoremap <CR> <CR>')
   1312      feed(':')
   1313      api.nvim_paste('echo', true, -1)
   1314      eq('', fn.histget(':'))
   1315      feed('<CR>')
   1316      eq('echo', fn.histget(':'))
   1317    end)
   1318    it('pasting with empty last chunk in Cmdline mode', function()
   1319      local screen = Screen.new(20, 4)
   1320      feed(':')
   1321      api.nvim_paste('Foo', true, 1)
   1322      api.nvim_paste('', true, 3)
   1323      screen:expect([[
   1324                            |
   1325        {1:~                   }|*2
   1326        :Foo^                |
   1327      ]])
   1328    end)
   1329    it('pasting text with control characters in Cmdline mode', function()
   1330      local screen = Screen.new(20, 4)
   1331      feed(':')
   1332      api.nvim_paste('normal! \023\022\006\027', true, -1)
   1333      screen:expect([[
   1334                            |
   1335        {1:~                   }|*2
   1336        :normal! {18:^W^V^F^[}^   |
   1337      ]])
   1338    end)
   1339    it('crlf=false does not break lines at CR, CRLF', function()
   1340      api.nvim_paste('line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1)
   1341      local expected = 'line 1\r\n\r\rline 2\nline 3\rline 4\r'
   1342      expect(expected)
   1343      eq({ 0, 3, 14, 0 }, fn.getpos('.'))
   1344      feed('u') -- Undo.
   1345      expect('')
   1346      feed('.') -- Dot-repeat.
   1347      expect(expected)
   1348    end)
   1349    describe('repeating a paste via redo/recording', function()
   1350      -- Test with indent and control chars and multibyte chars containing 0x80 bytes
   1351      local text = dedent(([[
   1352      foo
   1353        bar
   1354          baz
   1355      !!!%s!!!%s!!!%s!!!
   1356      最…倒…倀…
   1357      ]]):format('\0', '\2\3\6\21\22\23\24\27', '\127'))
   1358      before_each(function()
   1359        api.nvim_set_option_value('autoindent', true, {})
   1360      end)
   1361      local function test_paste_repeat_normal_insert(is_insert)
   1362        feed('qr' .. (is_insert and 'i' or ''))
   1363        eq('r', fn.reg_recording())
   1364        api.nvim_paste(text, true, -1)
   1365        feed(is_insert and '<Esc>' or '')
   1366        expect(text)
   1367        feed('.')
   1368        expect(text:rep(2))
   1369        feed('q')
   1370        eq('', fn.reg_recording())
   1371        feed('3.')
   1372        expect(text:rep(5))
   1373        feed('2@r')
   1374        expect(text:rep(9))
   1375      end
   1376      it('works in Normal mode', function()
   1377        test_paste_repeat_normal_insert(false)
   1378      end)
   1379      it('works in Insert mode', function()
   1380        test_paste_repeat_normal_insert(true)
   1381      end)
   1382      local function test_paste_repeat_visual_select(is_select)
   1383        insert(('xxx\n'):rep(5))
   1384        feed('ggqr' .. (is_select and 'gH' or 'V'))
   1385        api.nvim_paste(text, true, -1)
   1386        feed('q')
   1387        expect(text .. ('xxx\n'):rep(4))
   1388        feed('2@r')
   1389        expect(text:rep(3) .. ('xxx\n'):rep(2))
   1390      end
   1391      it('works in Visual mode (recording only)', function()
   1392        test_paste_repeat_visual_select(false)
   1393      end)
   1394      it('works in Select mode (recording only)', function()
   1395        test_paste_repeat_visual_select(true)
   1396      end)
   1397    end)
   1398    it('in a mapping recorded in a macro', function()
   1399      command([[nnoremap <F2> <Cmd>call nvim_paste('foo', v:false, -1)<CR>]])
   1400      feed('qr<F2>$q')
   1401      expect('foo')
   1402      feed('@r') -- repeating a macro containing the mapping should only paste once
   1403      expect('foofoo')
   1404    end)
   1405    local function test_paste_cancel_error(is_error)
   1406      before_each(function()
   1407        exec_lua(([[
   1408          vim.paste = (function(overridden)
   1409            return function(lines, phase)
   1410              for i, line in ipairs(lines) do
   1411                if line == 'CANCEL' then
   1412                  %s
   1413                end
   1414              end
   1415              return overridden(lines, phase)
   1416            end
   1417          end)(vim.paste)
   1418        ]]):format(is_error and 'error("fake fail")' or 'return false'))
   1419      end)
   1420      local function check_paste_cancel_error(data, crlf, phase)
   1421        if is_error then
   1422          eq('fake fail', pcall_err(api.nvim_paste, data, crlf, phase))
   1423        else
   1424          eq(false, api.nvim_paste(data, crlf, phase))
   1425        end
   1426      end
   1427      it('in phase -1', function()
   1428        feed('A')
   1429        check_paste_cancel_error('CANCEL', true, -1)
   1430        feed('<Esc>')
   1431        expect('')
   1432        feed('.')
   1433        expect('')
   1434      end)
   1435      it('in phase 1', function()
   1436        feed('A')
   1437        check_paste_cancel_error('CANCEL', true, 1)
   1438        feed('<Esc>')
   1439        expect('')
   1440        feed('.')
   1441        expect('')
   1442      end)
   1443      it('in phase 2', function()
   1444        feed('A')
   1445        eq(true, api.nvim_paste('aaa', true, 1))
   1446        expect('aaa')
   1447        check_paste_cancel_error('CANCEL', true, 2)
   1448        feed('<Esc>')
   1449        expect('aaa')
   1450        feed('.')
   1451        expect('aaaaaa')
   1452      end)
   1453      it('in phase 3', function()
   1454        feed('A')
   1455        eq(true, api.nvim_paste('aaa', true, 1))
   1456        expect('aaa')
   1457        eq(true, api.nvim_paste('bbb', true, 2))
   1458        expect('aaabbb')
   1459        check_paste_cancel_error('CANCEL', true, 3)
   1460        feed('<Esc>')
   1461        expect('aaabbb')
   1462        feed('.')
   1463        expect('aaabbbaaabbb')
   1464      end)
   1465    end
   1466    describe('vim.paste() cancel', function()
   1467      test_paste_cancel_error(false)
   1468    end)
   1469    describe('vim.paste() error', function()
   1470      test_paste_cancel_error(true)
   1471    end)
   1472  end)
   1473 
   1474  describe('nvim_put', function()
   1475    it('validation', function()
   1476      eq(
   1477        "Invalid 'line': expected String, got Integer",
   1478        pcall_err(request, 'nvim_put', { 42 }, 'l', false, false)
   1479      )
   1480      eq("Invalid 'type': 'x'", pcall_err(request, 'nvim_put', { 'foo' }, 'x', false, false))
   1481    end)
   1482    it("fails if 'nomodifiable'", function()
   1483      command('set nomodifiable')
   1484      eq(
   1485        [[Vim:E21: Cannot make changes, 'modifiable' is off]],
   1486        pcall_err(request, 'nvim_put', { 'a', 'b' }, 'l', true, true)
   1487      )
   1488    end)
   1489    it('inserts text', function()
   1490      -- linewise
   1491      api.nvim_put({ 'line 1', 'line 2', 'line 3' }, 'l', true, true)
   1492      expect([[
   1493 
   1494        line 1
   1495        line 2
   1496        line 3]])
   1497      eq({ 0, 4, 1, 0 }, fn.getpos('.'))
   1498      command('%delete _')
   1499      -- charwise
   1500      api.nvim_put({ 'line 1', 'line 2', 'line 3' }, 'c', true, false)
   1501      expect([[
   1502        line 1
   1503        line 2
   1504        line 3]])
   1505      eq({ 0, 1, 1, 0 }, fn.getpos('.')) -- follow=false
   1506      -- blockwise
   1507      api.nvim_put({ 'AA', 'BB' }, 'b', true, true)
   1508      expect([[
   1509        lAAine 1
   1510        lBBine 2
   1511        line 3]])
   1512      eq({ 0, 2, 4, 0 }, fn.getpos('.'))
   1513      command('%delete _')
   1514      -- Empty lines list.
   1515      api.nvim_put({}, 'c', true, true)
   1516      eq({ 0, 1, 1, 0 }, fn.getpos('.'))
   1517      expect([[]])
   1518      -- Single empty line.
   1519      api.nvim_put({ '' }, 'c', true, true)
   1520      eq({ 0, 1, 1, 0 }, fn.getpos('.'))
   1521      expect([[
   1522      ]])
   1523      api.nvim_put({ 'AB' }, 'c', true, true)
   1524      -- after=false, follow=true
   1525      api.nvim_put({ 'line 1', 'line 2' }, 'c', false, true)
   1526      expect([[
   1527        Aline 1
   1528        line 2B]])
   1529      eq({ 0, 2, 7, 0 }, fn.getpos('.'))
   1530      command('%delete _')
   1531      api.nvim_put({ 'AB' }, 'c', true, true)
   1532      -- after=false, follow=false
   1533      api.nvim_put({ 'line 1', 'line 2' }, 'c', false, false)
   1534      expect([[
   1535        Aline 1
   1536        line 2B]])
   1537      eq({ 0, 1, 2, 0 }, fn.getpos('.'))
   1538      eq('', api.nvim_eval('v:errmsg'))
   1539    end)
   1540 
   1541    it('detects charwise/linewise text (empty {type})', function()
   1542      -- linewise (final item is empty string)
   1543      api.nvim_put({ 'line 1', 'line 2', 'line 3', '' }, '', true, true)
   1544      expect([[
   1545 
   1546        line 1
   1547        line 2
   1548        line 3]])
   1549      eq({ 0, 4, 1, 0 }, fn.getpos('.'))
   1550      command('%delete _')
   1551      -- charwise (final item is non-empty)
   1552      api.nvim_put({ 'line 1', 'line 2', 'line 3' }, '', true, true)
   1553      expect([[
   1554        line 1
   1555        line 2
   1556        line 3]])
   1557      eq({ 0, 3, 6, 0 }, fn.getpos('.'))
   1558    end)
   1559 
   1560    it('allows block width', function()
   1561      -- behave consistently with setreg(); support "\022{NUM}" return by getregtype()
   1562      api.nvim_put({ 'line 1', 'line 2', 'line 3' }, 'l', false, false)
   1563      expect([[
   1564        line 1
   1565        line 2
   1566        line 3
   1567        ]])
   1568 
   1569      -- larger width create spaces
   1570      api.nvim_put({ 'a', 'bc' }, 'b3', false, false)
   1571      expect([[
   1572        a  line 1
   1573        bc line 2
   1574        line 3
   1575        ]])
   1576      -- smaller width is ignored
   1577      api.nvim_put({ 'xxx', 'yyy' }, '\0221', false, true)
   1578      expect([[
   1579        xxxa  line 1
   1580        yyybc line 2
   1581        line 3
   1582        ]])
   1583      eq("Invalid 'type': 'bx'", pcall_err(api.nvim_put, { 'xxx', 'yyy' }, 'bx', false, true))
   1584      eq("Invalid 'type': 'b3x'", pcall_err(api.nvim_put, { 'xxx', 'yyy' }, 'b3x', false, true))
   1585    end)
   1586 
   1587    it('computes block width correctly when not specified #35034', function()
   1588      api.nvim_put({ 'line 1', 'line 2', 'line 3' }, 'l', false, false)
   1589      -- block width should be 4
   1590      api.nvim_put({ 'あい', 'xxx', 'xx' }, 'b', false, false)
   1591      expect([[
   1592        あいline 1
   1593        xxx line 2
   1594        xx  line 3
   1595        ]])
   1596    end)
   1597  end)
   1598 
   1599  describe('nvim_strwidth', function()
   1600    it('works', function()
   1601      eq(3, api.nvim_strwidth('abc'))
   1602      -- 6 + (neovim)
   1603      -- 19 * 2 (each japanese character occupies two cells)
   1604      eq(44, api.nvim_strwidth('neovimのデザインかなりまともなのになってる。'))
   1605    end)
   1606 
   1607    it('cannot handle NULs', function()
   1608      eq(0, api.nvim_strwidth('\0abc'))
   1609    end)
   1610 
   1611    it('can handle emoji with variant selectors and ZWJ', function()
   1612      local selector = '❤️'
   1613      eq(2, fn.strchars(selector))
   1614      eq(1, fn.strcharlen(selector))
   1615      eq(2, api.nvim_strwidth(selector))
   1616 
   1617      local no_selector = '❤'
   1618      eq(1, fn.strchars(no_selector))
   1619      eq(1, fn.strcharlen(no_selector))
   1620      eq(1, api.nvim_strwidth(no_selector))
   1621 
   1622      local selector_zwj_selector = '🏳️‍⚧️'
   1623      eq(5, fn.strchars(selector_zwj_selector))
   1624      eq(1, fn.strcharlen(selector_zwj_selector))
   1625      eq(2, api.nvim_strwidth(selector_zwj_selector))
   1626 
   1627      local emoji_zwj_emoji = '🧑‍🌾'
   1628      eq(3, fn.strchars(emoji_zwj_emoji))
   1629      eq(1, fn.strcharlen(emoji_zwj_emoji))
   1630      eq(2, api.nvim_strwidth(emoji_zwj_emoji))
   1631    end)
   1632  end)
   1633 
   1634  describe('nvim_get_current_line, nvim_set_current_line', function()
   1635    it('works', function()
   1636      eq('', api.nvim_get_current_line())
   1637      api.nvim_set_current_line('abc')
   1638      eq('abc', api.nvim_get_current_line())
   1639    end)
   1640  end)
   1641 
   1642  describe('set/get/del variables', function()
   1643    it('validation', function()
   1644      eq('Key not found: bogus', pcall_err(api.nvim_get_var, 'bogus'))
   1645      eq('Key not found: bogus', pcall_err(api.nvim_del_var, 'bogus'))
   1646    end)
   1647 
   1648    it('nvim_get_var, nvim_set_var, nvim_del_var', function()
   1649      api.nvim_set_var('lua', { 1, 2, { ['3'] = 1 } })
   1650      eq({ 1, 2, { ['3'] = 1 } }, api.nvim_get_var('lua'))
   1651      eq({ 1, 2, { ['3'] = 1 } }, api.nvim_eval('g:lua'))
   1652      eq(1, fn.exists('g:lua'))
   1653      api.nvim_del_var('lua')
   1654      eq(0, fn.exists('g:lua'))
   1655      eq('Key not found: lua', pcall_err(api.nvim_del_var, 'lua'))
   1656      api.nvim_set_var('lua', 1)
   1657 
   1658      -- Empty keys are allowed in Vim dicts (and msgpack).
   1659      api.nvim_set_var('dict_empty_key', { [''] = 'empty key' })
   1660      eq({ [''] = 'empty key' }, api.nvim_get_var('dict_empty_key'))
   1661 
   1662      -- Set locked g: var.
   1663      command('lockvar lua')
   1664      eq('Key is locked: lua', pcall_err(api.nvim_del_var, 'lua'))
   1665      eq('Key is locked: lua', pcall_err(api.nvim_set_var, 'lua', 1))
   1666 
   1667      exec([[
   1668        function Test()
   1669        endfunction
   1670        function s:Test()
   1671        endfunction
   1672        let g:Unknown_func = function('Test')
   1673        let g:Unknown_script_func = function('s:Test')
   1674      ]])
   1675      eq(NIL, api.nvim_get_var('Unknown_func'))
   1676      eq(NIL, api.nvim_get_var('Unknown_script_func'))
   1677 
   1678      -- Check if autoload works properly
   1679      local pathsep = n.get_pathsep()
   1680      local xhome = 'Xhome_api'
   1681      local xconfig = xhome .. pathsep .. 'Xconfig'
   1682      local xdata = xhome .. pathsep .. 'Xdata'
   1683      local autoload_folder = table.concat({ xconfig, 'nvim', 'autoload' }, pathsep)
   1684      local autoload_file = table.concat({ autoload_folder, 'testload.vim' }, pathsep)
   1685      mkdir_p(autoload_folder)
   1686      write_file(autoload_file, [[let testload#value = 2]])
   1687 
   1688      clear { args_rm = { '-u' }, env = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata } }
   1689      eq(2, api.nvim_get_var('testload#value'))
   1690      rmdir(xhome)
   1691    end)
   1692 
   1693    it('nvim_get_vvar, nvim_set_vvar', function()
   1694      eq('Key is read-only: count', pcall_err(request, 'nvim_set_vvar', 'count', 42))
   1695      eq('Dict is locked', pcall_err(request, 'nvim_set_vvar', 'nosuchvar', 42))
   1696      api.nvim_set_vvar('errmsg', 'set by API')
   1697      eq('set by API', api.nvim_get_vvar('errmsg'))
   1698      api.nvim_set_vvar('completed_item', { word = 'a', user_data = vim.empty_dict() })
   1699      eq({}, api.nvim_get_vvar('completed_item')['user_data'])
   1700      api.nvim_set_vvar('errmsg', 42)
   1701      eq('42', eval('v:errmsg'))
   1702      api.nvim_set_vvar('oldfiles', { 'one', 'two' })
   1703      eq({ 'one', 'two' }, eval('v:oldfiles'))
   1704      api.nvim_set_vvar('oldfiles', {})
   1705      eq({}, eval('v:oldfiles'))
   1706      eq(
   1707        'Setting v:oldfiles to value with wrong type',
   1708        pcall_err(api.nvim_set_vvar, 'oldfiles', 'a')
   1709      )
   1710      eq({}, eval('v:oldfiles'))
   1711 
   1712      feed('i foo foo foo<Esc>0/foo<CR>')
   1713      eq({ 1, 1 }, api.nvim_win_get_cursor(0))
   1714      eq(1, eval('v:searchforward'))
   1715      feed('n')
   1716      eq({ 1, 5 }, api.nvim_win_get_cursor(0))
   1717      api.nvim_set_vvar('searchforward', 0)
   1718      eq(0, eval('v:searchforward'))
   1719      feed('n')
   1720      eq({ 1, 1 }, api.nvim_win_get_cursor(0))
   1721      api.nvim_set_vvar('searchforward', 1)
   1722      eq(1, eval('v:searchforward'))
   1723      feed('n')
   1724      eq({ 1, 5 }, api.nvim_win_get_cursor(0))
   1725 
   1726      local screen = Screen.new(60, 3)
   1727      eq(1, eval('v:hlsearch'))
   1728      screen:expect {
   1729        grid = [[
   1730         {10:foo} {10:^foo} {10:foo}                                                |
   1731        {1:~                                                           }|
   1732                                                                    |
   1733      ]],
   1734      }
   1735      api.nvim_set_vvar('hlsearch', 0)
   1736      eq(0, eval('v:hlsearch'))
   1737      screen:expect {
   1738        grid = [[
   1739         foo ^foo foo                                                |
   1740        {1:~                                                           }|
   1741                                                                    |
   1742      ]],
   1743      }
   1744      api.nvim_set_vvar('hlsearch', 1)
   1745      eq(1, eval('v:hlsearch'))
   1746      screen:expect {
   1747        grid = [[
   1748         {10:foo} {10:^foo} {10:foo}                                                |
   1749        {1:~                                                           }|
   1750                                                                    |
   1751      ]],
   1752      }
   1753    end)
   1754 
   1755    it('vim_set_var returns the old value', function()
   1756      local val1 = { 1, 2, { ['3'] = 1 } }
   1757      local val2 = { 4, 7 }
   1758      eq(NIL, request('vim_set_var', 'lua', val1))
   1759      eq(val1, request('vim_set_var', 'lua', val2))
   1760    end)
   1761 
   1762    it('vim_del_var returns the old value', function()
   1763      local val1 = { 1, 2, { ['3'] = 1 } }
   1764      local val2 = { 4, 7 }
   1765      eq(NIL, request('vim_set_var', 'lua', val1))
   1766      eq(val1, request('vim_set_var', 'lua', val2))
   1767      eq(val2, request('vim_del_var', 'lua'))
   1768    end)
   1769 
   1770    it('preserves values with NULs in them', function()
   1771      api.nvim_set_var('xxx', 'ab\0cd')
   1772      eq('ab\000cd', api.nvim_get_var('xxx'))
   1773    end)
   1774  end)
   1775 
   1776  describe('nvim_get_option_value, nvim_set_option_value', function()
   1777    it('works', function()
   1778      ok(api.nvim_get_option_value('equalalways', {}))
   1779      api.nvim_set_option_value('equalalways', false, {})
   1780      ok(not api.nvim_get_option_value('equalalways', {}))
   1781    end)
   1782 
   1783    it('works to get global value of local options', function()
   1784      eq(false, api.nvim_get_option_value('lisp', {}))
   1785      eq(8, api.nvim_get_option_value('shiftwidth', {}))
   1786    end)
   1787 
   1788    it('works to set global value of local options', function()
   1789      api.nvim_set_option_value('lisp', true, { scope = 'global' })
   1790      eq(true, api.nvim_get_option_value('lisp', { scope = 'global' }))
   1791      eq(false, api.nvim_get_option_value('lisp', {}))
   1792      eq(nil, command_output('setglobal lisp?'):match('nolisp'))
   1793      eq('nolisp', command_output('setlocal lisp?'):match('nolisp'))
   1794      api.nvim_set_option_value('shiftwidth', 20, { scope = 'global' })
   1795      eq('20', command_output('setglobal shiftwidth?'):match('%d+'))
   1796      eq('8', command_output('setlocal shiftwidth?'):match('%d+'))
   1797    end)
   1798 
   1799    it('updates where the option was last set from', function()
   1800      api.nvim_set_option_value('equalalways', false, {})
   1801      local status, rv = pcall(command_output, 'verbose set equalalways?')
   1802      eq(true, status)
   1803      matches('noequalalways\n' .. '\tLast set from API client %(channel id %d+%)', rv)
   1804 
   1805      api.nvim_exec_lua('vim.api.nvim_set_option_value("equalalways", true, {})', {})
   1806      status, rv = pcall(command_output, 'verbose set equalalways?')
   1807      eq(true, status)
   1808      eq('  equalalways\n\tLast set from Lua (run Nvim with -V1 for more details)', rv)
   1809    end)
   1810 
   1811    it('updates whether the option has ever been set #25025', function()
   1812      eq(false, api.nvim_get_option_info2('autochdir', {}).was_set)
   1813      api.nvim_set_option_value('autochdir', true, {})
   1814      eq(true, api.nvim_get_option_info2('autochdir', {}).was_set)
   1815 
   1816      eq(false, api.nvim_get_option_info2('cmdwinheight', {}).was_set)
   1817      api.nvim_set_option_value('cmdwinheight', 10, {})
   1818      eq(true, api.nvim_get_option_info2('cmdwinheight', {}).was_set)
   1819 
   1820      eq(false, api.nvim_get_option_info2('debug', {}).was_set)
   1821      api.nvim_set_option_value('debug', 'beep', {})
   1822      eq(true, api.nvim_get_option_info2('debug', {}).was_set)
   1823    end)
   1824 
   1825    it('validation', function()
   1826      eq("Unknown option 'foobar'", pcall_err(api.nvim_set_option_value, 'foobar', 'baz', {}))
   1827      eq(
   1828        "Unknown option 'foobar'",
   1829        pcall_err(api.nvim_set_option_value, 'foobar', 'baz', { win = api.nvim_get_current_win() })
   1830      )
   1831      eq(
   1832        "Invalid 'scope': expected 'local' or 'global'",
   1833        pcall_err(api.nvim_get_option_value, 'scrolloff', { scope = 'bogus' })
   1834      )
   1835      eq(
   1836        "Invalid 'scope': expected 'local' or 'global'",
   1837        pcall_err(api.nvim_set_option_value, 'scrolloff', 1, { scope = 'bogus' })
   1838      )
   1839      eq(
   1840        "Invalid 'scope': expected String, got Integer",
   1841        pcall_err(api.nvim_get_option_value, 'scrolloff', { scope = 42 })
   1842      )
   1843      eq(
   1844        "Invalid 'value': expected valid option type, got Array",
   1845        pcall_err(api.nvim_set_option_value, 'scrolloff', {}, {})
   1846      )
   1847      eq(
   1848        "Invalid value for option 'scrolloff': expected number, got boolean true",
   1849        pcall_err(api.nvim_set_option_value, 'scrolloff', true, {})
   1850      )
   1851      eq(
   1852        'Invalid value for option \'scrolloff\': expected number, got string "wrong"',
   1853        pcall_err(api.nvim_set_option_value, 'scrolloff', 'wrong', {})
   1854      )
   1855    end)
   1856 
   1857    it('can get local values when global value is set', function()
   1858      eq(0, api.nvim_get_option_value('scrolloff', {}))
   1859      eq(-1, api.nvim_get_option_value('scrolloff', { scope = 'local' }))
   1860    end)
   1861 
   1862    it('can set global and local values', function()
   1863      api.nvim_set_option_value('makeprg', 'hello', {})
   1864      eq('hello', api.nvim_get_option_value('makeprg', {}))
   1865      eq('', api.nvim_get_option_value('makeprg', { scope = 'local' }))
   1866      api.nvim_set_option_value('makeprg', 'world', { scope = 'local' })
   1867      eq('world', api.nvim_get_option_value('makeprg', { scope = 'local' }))
   1868      api.nvim_set_option_value('makeprg', 'goodbye', { scope = 'global' })
   1869      eq('goodbye', api.nvim_get_option_value('makeprg', { scope = 'global' }))
   1870      api.nvim_set_option_value('makeprg', 'hello', {})
   1871      eq('hello', api.nvim_get_option_value('makeprg', { scope = 'global' }))
   1872      eq('hello', api.nvim_get_option_value('makeprg', {}))
   1873      eq('', api.nvim_get_option_value('makeprg', { scope = 'local' }))
   1874    end)
   1875 
   1876    it('clears the local value of an option with nil', function()
   1877      -- Set global value
   1878      api.nvim_set_option_value('shiftwidth', 42, {})
   1879      eq(42, api.nvim_get_option_value('shiftwidth', {}))
   1880 
   1881      -- Set local value
   1882      api.nvim_set_option_value('shiftwidth', 8, { scope = 'local' })
   1883      eq(8, api.nvim_get_option_value('shiftwidth', {}))
   1884      eq(8, api.nvim_get_option_value('shiftwidth', { scope = 'local' }))
   1885      eq(42, api.nvim_get_option_value('shiftwidth', { scope = 'global' }))
   1886 
   1887      -- Clear value without scope
   1888      api.nvim_set_option_value('shiftwidth', NIL, {})
   1889      eq(42, api.nvim_get_option_value('shiftwidth', {}))
   1890      eq(42, api.nvim_get_option_value('shiftwidth', { scope = 'local' }))
   1891 
   1892      -- Clear value with explicit scope
   1893      api.nvim_set_option_value('shiftwidth', 8, { scope = 'local' })
   1894      api.nvim_set_option_value('shiftwidth', NIL, { scope = 'local' })
   1895      eq(42, api.nvim_get_option_value('shiftwidth', {}))
   1896      eq(42, api.nvim_get_option_value('shiftwidth', { scope = 'local' }))
   1897 
   1898      -- Now try with options with a special "local is unset" value (e.g. 'undolevels')
   1899      api.nvim_set_option_value('undolevels', 1000, {})
   1900      api.nvim_set_option_value('undolevels', 1200, { scope = 'local' })
   1901      eq(1200, api.nvim_get_option_value('undolevels', { scope = 'local' }))
   1902      api.nvim_set_option_value('undolevels', NIL, { scope = 'local' })
   1903      eq(-123456, api.nvim_get_option_value('undolevels', { scope = 'local' }))
   1904      eq(1000, api.nvim_get_option_value('undolevels', {}))
   1905 
   1906      api.nvim_set_option_value('autoread', true, {})
   1907      api.nvim_set_option_value('autoread', false, { scope = 'local' })
   1908      eq(false, api.nvim_get_option_value('autoread', { scope = 'local' }))
   1909      api.nvim_set_option_value('autoread', NIL, { scope = 'local' })
   1910      eq(NIL, api.nvim_get_option_value('autoread', { scope = 'local' }))
   1911      eq(true, api.nvim_get_option_value('autoread', {}))
   1912    end)
   1913 
   1914    it('set window options', function()
   1915      api.nvim_set_option_value('colorcolumn', '4,3', {})
   1916      eq('4,3', api.nvim_get_option_value('colorcolumn', { scope = 'local' }))
   1917      command('set modified hidden')
   1918      command('enew') -- edit new buffer, window option is preserved
   1919      eq('4,3', api.nvim_get_option_value('colorcolumn', { scope = 'local' }))
   1920    end)
   1921 
   1922    it('set local window options', function()
   1923      api.nvim_set_option_value('colorcolumn', '4,3', { win = 0, scope = 'local' })
   1924      eq('4,3', api.nvim_get_option_value('colorcolumn', { win = 0, scope = 'local' }))
   1925      command('set modified hidden')
   1926      command('enew') -- edit new buffer, window option is reset
   1927      eq('', api.nvim_get_option_value('colorcolumn', { win = 0, scope = 'local' }))
   1928    end)
   1929 
   1930    it('get buffer or window-local options', function()
   1931      command('new')
   1932      local buf = api.nvim_get_current_buf()
   1933      api.nvim_set_option_value('tagfunc', 'foobar', { buf = buf })
   1934      eq('foobar', api.nvim_get_option_value('tagfunc', { buf = buf }))
   1935 
   1936      local win = api.nvim_get_current_win()
   1937      api.nvim_set_option_value('number', true, { win = win })
   1938      eq(true, api.nvim_get_option_value('number', { win = win }))
   1939    end)
   1940 
   1941    it('getting current buffer option does not adjust cursor #19381', function()
   1942      command('new')
   1943      local buf = api.nvim_get_current_buf()
   1944      print(vim.inspect(api.nvim_get_current_buf()))
   1945      local win = api.nvim_get_current_win()
   1946      insert('some text')
   1947      feed('0v$')
   1948      eq({ 1, 9 }, api.nvim_win_get_cursor(win))
   1949      api.nvim_get_option_value('filetype', { buf = buf })
   1950      eq({ 1, 9 }, api.nvim_win_get_cursor(win))
   1951    end)
   1952 
   1953    it('can get default option values for filetypes', function()
   1954      command('filetype plugin on')
   1955      for ft, opts in pairs {
   1956        lua = { commentstring = '-- %s' },
   1957        vim = { commentstring = '"%s' },
   1958        man = { tagfunc = "v:lua.require'man'.goto_tag" },
   1959        xml = { formatexpr = 'xmlformat#Format()' },
   1960      } do
   1961        for option, value in pairs(opts) do
   1962          eq(value, api.nvim_get_option_value(option, { filetype = ft }))
   1963        end
   1964      end
   1965 
   1966      command 'au FileType lua setlocal commentstring=NEW\\ %s'
   1967 
   1968      eq('NEW %s', api.nvim_get_option_value('commentstring', { filetype = 'lua' }))
   1969    end)
   1970 
   1971    it('errors for bad FileType autocmds', function()
   1972      command 'au FileType lua setlocal commentstring=BAD'
   1973      eq(
   1974        [[FileType Autocommands for "lua": Vim(setlocal):E537: 'commentstring' must be empty or contain %s: commentstring=BAD]],
   1975        pcall_err(api.nvim_get_option_value, 'commentstring', { filetype = 'lua' })
   1976      )
   1977    end)
   1978 
   1979    it("value of 'modified' is always false for scratch buffers", function()
   1980      api.nvim_set_current_buf(api.nvim_create_buf(true, true))
   1981      insert([[
   1982        foo
   1983        bar
   1984        baz
   1985      ]])
   1986      eq(false, api.nvim_get_option_value('modified', {}))
   1987    end)
   1988 
   1989    it('errors if autocmds wipe the dummy buffer', function()
   1990      -- Wipe the dummy buffer. This will throw E813, but the buffer will still be wiped; check that
   1991      -- such errors from setting the filetype have priority.
   1992      command 'autocmd FileType * ++once bwipeout!'
   1993      eq(
   1994        'FileType Autocommands for "*": Vim(bwipeout):E813: Cannot close autocmd window',
   1995        pcall_err(api.nvim_get_option_value, 'formatexpr', { filetype = 'lua' })
   1996      )
   1997 
   1998      -- Silence E813 to check that the error for wiping the dummy buffer is set.
   1999      command 'autocmd FileType * ++once silent! bwipeout!'
   2000      eq(
   2001        'Internal buffer was deleted',
   2002        pcall_err(api.nvim_get_option_value, 'formatexpr', { filetype = 'lua' })
   2003      )
   2004    end)
   2005 
   2006    it('does not crash if autocmds open dummy buffer in other windows', function()
   2007      exec [[
   2008        autocmd FileType * ++once let g:dummy_buf = bufnr() | split
   2009 
   2010        " Autocommands should be blocked while Nvim attempts to wipe the buffer.
   2011        let g:wipe_events = []
   2012        autocmd WinClosed * if winbufnr(expand('<amatch>')) == g:dummy_buf
   2013                         \| let g:wipe_events += ['WinClosed']
   2014                         \| endif
   2015        autocmd BufWipeout * if expand('<abuf>') == g:dummy_buf
   2016                         \| let g:wipe_events += ['BufWipeout']
   2017                         \| endif
   2018      ]]
   2019      api.nvim_get_option_value('formatexpr', { filetype = 'lua' })
   2020      eq(0, eval('bufexists(g:dummy_buf)'))
   2021      eq({}, eval('win_findbuf(g:dummy_buf)'))
   2022      eq({}, eval('g:wipe_events'))
   2023 
   2024      -- Be an ABSOLUTE nuisance and make it the only window to prevent it from wiping.
   2025      -- Do it this way to avoid E813 from :only trying to close the autocmd window.
   2026      command('autocmd FileType * ++once let g:dummy_buf = bufnr() | split | wincmd w | quit')
   2027      api.nvim_get_option_value('formatexpr', { filetype = 'lua' })
   2028      eq(1, eval('bufexists(g:dummy_buf)'))
   2029 
   2030      -- Ensure the buffer does not remain as a dummy by checking that we can switch to it.
   2031      local old_win = api.nvim_get_current_win()
   2032      command('execute g:dummy_buf "sbuffer"')
   2033      eq(eval('g:dummy_buf'), api.nvim_get_current_buf())
   2034      neq(old_win, api.nvim_get_current_win())
   2035      eq({}, eval('g:wipe_events'))
   2036    end)
   2037 
   2038    it('does not crash if dummy buffer wiped after autocommands', function()
   2039      -- Autocommands are blocked while Nvim attempts to wipe the buffer, but check something like
   2040      -- &bufhidden = "wipe" causing a premature wipe doesn't crash.
   2041      command('autocmd FileType * ++once setlocal bufhidden=wipe | split')
   2042      api.nvim_get_option_value('formatexpr', { filetype = 'lua' })
   2043      assert_alive()
   2044    end)
   2045 
   2046    it('sets dummy buffer options without side-effects', function()
   2047      exec [[
   2048        let g:events = []
   2049        autocmd OptionSet * let g:events += [expand("<amatch>")]
   2050        autocmd FileType * ++once let g:bufhidden = &l:bufhidden
   2051                               \| let g:buftype = &l:buftype
   2052                               \| let g:swapfile = &l:swapfile
   2053                               \| let g:modeline = &l:modeline
   2054                               \| let g:bufloaded = bufloaded(bufnr())
   2055      ]]
   2056      api.nvim_get_option_value('formatexpr', { filetype = 'lua' })
   2057      eq({}, eval('g:events'))
   2058      eq('hide', eval('g:bufhidden'))
   2059      eq('nofile', eval('g:buftype'))
   2060      eq(0, eval('g:swapfile'))
   2061      eq(0, eval('g:modeline'))
   2062      eq(1, eval('g:bufloaded'))
   2063    end)
   2064  end)
   2065 
   2066  describe('nvim_{get,set}_current_buf, nvim_list_bufs', function()
   2067    it('works', function()
   2068      eq(1, #api.nvim_list_bufs())
   2069      eq(api.nvim_list_bufs()[1], api.nvim_get_current_buf())
   2070      command('new')
   2071      eq(2, #api.nvim_list_bufs())
   2072      eq(api.nvim_list_bufs()[2], api.nvim_get_current_buf())
   2073      api.nvim_set_current_buf(api.nvim_list_bufs()[1])
   2074      eq(api.nvim_list_bufs()[1], api.nvim_get_current_buf())
   2075    end)
   2076  end)
   2077 
   2078  describe('nvim_{get,set}_current_win, nvim_list_wins', function()
   2079    it('works', function()
   2080      eq(1, #api.nvim_list_wins())
   2081      eq(api.nvim_list_wins()[1], api.nvim_get_current_win())
   2082      command('vsplit')
   2083      command('split')
   2084      eq(3, #api.nvim_list_wins())
   2085      eq(api.nvim_list_wins()[1], api.nvim_get_current_win())
   2086      api.nvim_set_current_win(api.nvim_list_wins()[2])
   2087      eq(api.nvim_list_wins()[2], api.nvim_get_current_win())
   2088    end)
   2089 
   2090    it('resets Visual mode when switching to different buffer #37072', function()
   2091      command('new | wincmd w')
   2092      api.nvim_buf_set_lines(0, 0, -1, true, { 'a', 'b' })
   2093      api.nvim_win_set_cursor(0, { 2, 0 })
   2094      feed('<C-q>')
   2095      eq({ mode = '\022', blocking = false }, api.nvim_get_mode())
   2096      api.nvim_set_current_win(fn.win_getid(fn.winnr('#')))
   2097      eq(true, pcall(command, 'redraw'))
   2098    end)
   2099 
   2100    it('failure modes', function()
   2101      n.command('split')
   2102 
   2103      eq('Invalid window id: 9999', pcall_err(api.nvim_set_current_win, 9999))
   2104 
   2105      -- XXX: force nvim_set_current_win to fail somehow.
   2106      n.command("au WinLeave * throw 'foo'")
   2107      eq('WinLeave Autocommands for "*": foo', pcall_err(api.nvim_set_current_win, 1000))
   2108    end)
   2109  end)
   2110 
   2111  describe('nvim_{get,set}_current_tabpage, nvim_list_tabpages', function()
   2112    it('works', function()
   2113      eq(1, #api.nvim_list_tabpages())
   2114      eq(api.nvim_list_tabpages()[1], api.nvim_get_current_tabpage())
   2115      command('tabnew')
   2116      eq(2, #api.nvim_list_tabpages())
   2117      eq(2, #api.nvim_list_wins())
   2118      eq(api.nvim_list_wins()[2], api.nvim_get_current_win())
   2119      eq(api.nvim_list_tabpages()[2], api.nvim_get_current_tabpage())
   2120      api.nvim_set_current_win(api.nvim_list_wins()[1])
   2121      -- Switching window also switches tabpages if necessary
   2122      eq(api.nvim_list_tabpages()[1], api.nvim_get_current_tabpage())
   2123      eq(api.nvim_list_wins()[1], api.nvim_get_current_win())
   2124      api.nvim_set_current_tabpage(api.nvim_list_tabpages()[2])
   2125      eq(api.nvim_list_tabpages()[2], api.nvim_get_current_tabpage())
   2126      eq(api.nvim_list_wins()[2], api.nvim_get_current_win())
   2127    end)
   2128 
   2129    it('failure modes', function()
   2130      n.command('tabnew')
   2131 
   2132      eq('Invalid tabpage id: 999', pcall_err(api.nvim_set_current_tabpage, 999))
   2133 
   2134      -- XXX: force nvim_set_current_tabpage to fail somehow.
   2135      n.command("au TabLeave * throw 'foo'")
   2136      eq('TabLeave Autocommands for "*": foo', pcall_err(api.nvim_set_current_tabpage, 1))
   2137    end)
   2138  end)
   2139 
   2140  describe('nvim_get_mode', function()
   2141    it('during normal-mode `g` returns blocking=true', function()
   2142      api.nvim_input('o') -- add a line
   2143      eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
   2144      api.nvim_input([[<C-\><C-N>]])
   2145      eq(2, api.nvim_eval("line('.')"))
   2146      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2147 
   2148      api.nvim_input('g')
   2149      eq({ mode = 'n', blocking = true }, api.nvim_get_mode())
   2150 
   2151      api.nvim_input('k') -- complete the operator
   2152      eq(1, api.nvim_eval("line('.')")) -- verify the completed operator
   2153      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2154    end)
   2155 
   2156    it('returns the correct result multiple consecutive times', function()
   2157      for _ = 1, 5 do
   2158        eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2159      end
   2160      api.nvim_input('g')
   2161      for _ = 1, 4 do
   2162        eq({ mode = 'n', blocking = true }, api.nvim_get_mode())
   2163      end
   2164      api.nvim_input('g')
   2165      for _ = 1, 7 do
   2166        eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2167      end
   2168    end)
   2169 
   2170    it('during normal-mode CTRL-W, returns blocking=true', function()
   2171      api.nvim_input('<C-W>')
   2172      eq({ mode = 'n', blocking = true }, api.nvim_get_mode())
   2173 
   2174      api.nvim_input('s') -- complete the operator
   2175      eq(2, api.nvim_eval("winnr('$')")) -- verify the completed operator
   2176      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2177    end)
   2178 
   2179    it('during press-enter prompt without UI returns blocking=false', function()
   2180      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2181      command("echom 'msg1'")
   2182      command("echom 'msg2'")
   2183      command("echom 'msg3'")
   2184      command("echom 'msg4'")
   2185      command("echom 'msg5'")
   2186      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2187      api.nvim_input(':messages<CR>')
   2188      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2189    end)
   2190 
   2191    it('during press-enter prompt returns blocking=true', function()
   2192      api.nvim_ui_attach(80, 20, {})
   2193      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2194      command("echom 'msg1'")
   2195      command("echom 'msg2'")
   2196      command("echom 'msg3'")
   2197      command("echom 'msg4'")
   2198      command("echom 'msg5'")
   2199      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2200      api.nvim_input(':messages<CR>')
   2201      eq({ mode = 'r', blocking = true }, api.nvim_get_mode())
   2202    end)
   2203 
   2204    it('during getchar() returns blocking=false', function()
   2205      api.nvim_input(':let g:test_input = nr2char(getchar())<CR>')
   2206      -- Events are enabled during getchar(), RPC calls are *not* blocked. #5384
   2207      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2208      eq(0, api.nvim_eval("exists('g:test_input')"))
   2209      api.nvim_input('J')
   2210      eq('J', api.nvim_eval('g:test_input'))
   2211      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2212    end)
   2213 
   2214    -- TODO: bug #6247#issuecomment-286403810
   2215    it('batched with input', function()
   2216      api.nvim_ui_attach(80, 20, {})
   2217      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2218      command("echom 'msg1'")
   2219      command("echom 'msg2'")
   2220      command("echom 'msg3'")
   2221      command("echom 'msg4'")
   2222      command("echom 'msg5'")
   2223 
   2224      local req = {
   2225        { 'nvim_get_mode', {} },
   2226        { 'nvim_input', { ':messages<CR>' } },
   2227        { 'nvim_get_mode', {} },
   2228        { 'nvim_eval', { '1' } },
   2229      }
   2230      eq({
   2231        {
   2232          { mode = 'n', blocking = false },
   2233          13,
   2234          { mode = 'n', blocking = false }, -- TODO: should be blocked=true ?
   2235          1,
   2236        },
   2237        NIL,
   2238      }, api.nvim_call_atomic(req))
   2239      eq({ mode = 'r', blocking = true }, api.nvim_get_mode())
   2240    end)
   2241    it('during insert-mode map-pending, returns blocking=true #6166', function()
   2242      command('inoremap xx foo')
   2243      api.nvim_input('ix')
   2244      eq({ mode = 'i', blocking = true }, api.nvim_get_mode())
   2245    end)
   2246    it('during normal-mode gU, returns blocking=false #6166', function()
   2247      api.nvim_input('gu')
   2248      eq({ mode = 'no', blocking = false }, api.nvim_get_mode())
   2249    end)
   2250 
   2251    it("at '-- More --' prompt returns blocking=true #11899", function()
   2252      command('set more')
   2253      feed(':digraphs<cr>')
   2254      eq({ mode = 'rm', blocking = true }, api.nvim_get_mode())
   2255    end)
   2256 
   2257    it('after <Nop> mapping returns blocking=false #17257', function()
   2258      command('nnoremap <F2> <Nop>')
   2259      feed('<F2>')
   2260      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2261    end)
   2262 
   2263    it('after empty string <expr> mapping returns blocking=false #17257', function()
   2264      command('nnoremap <expr> <F2> ""')
   2265      feed('<F2>')
   2266      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2267    end)
   2268 
   2269    it('returns "c" during number prompt', function()
   2270      feed('ifoo<Esc>z=')
   2271      eq({ mode = 'c', blocking = false }, api.nvim_get_mode())
   2272    end)
   2273  end)
   2274 
   2275  describe('RPC (K_EVENT)', function()
   2276    it('does not complete ("interrupt") normal-mode operator-pending #6166', function()
   2277      n.insert([[
   2278        FIRST LINE
   2279        SECOND LINE]])
   2280      api.nvim_input('gg')
   2281      api.nvim_input('gu')
   2282      -- Make any RPC request (can be non-async: op-pending does not block).
   2283      api.nvim_get_current_buf()
   2284      -- Buffer should not change.
   2285      expect([[
   2286        FIRST LINE
   2287        SECOND LINE]])
   2288      -- Now send input to complete the operator.
   2289      api.nvim_input('j')
   2290      expect([[
   2291        first line
   2292        second line]])
   2293    end)
   2294 
   2295    it('does not complete ("interrupt") `d` #3732', function()
   2296      local screen = Screen.new(20, 4)
   2297      command('set listchars=eol:$')
   2298      command('set list')
   2299      feed('ia<cr>b<cr>c<cr><Esc>kkk')
   2300      feed('d')
   2301      -- Make any RPC request (can be non-async: op-pending does not block).
   2302      api.nvim_get_current_buf()
   2303      screen:expect([[
   2304       ^a{1:$}                  |
   2305       b{1:$}                  |
   2306       c{1:$}                  |
   2307                           |
   2308      ]])
   2309    end)
   2310 
   2311    it('does not complete ("interrupt") normal-mode map-pending #6166', function()
   2312      command("nnoremap dd :let g:foo='it worked...'<CR>")
   2313      n.insert([[
   2314        FIRST LINE
   2315        SECOND LINE]])
   2316      api.nvim_input('gg')
   2317      api.nvim_input('d')
   2318      -- Make any RPC request (must be async, because map-pending blocks).
   2319      api.nvim_get_api_info()
   2320      -- Send input to complete the mapping.
   2321      api.nvim_input('d')
   2322      expect([[
   2323        FIRST LINE
   2324        SECOND LINE]])
   2325      eq('it worked...', n.eval('g:foo'))
   2326    end)
   2327 
   2328    it('does not complete ("interrupt") insert-mode map-pending #6166', function()
   2329      command('inoremap xx foo')
   2330      command('set timeoutlen=9999')
   2331      n.insert([[
   2332        FIRST LINE
   2333        SECOND LINE]])
   2334      api.nvim_input('ix')
   2335      -- Make any RPC request (must be async, because map-pending blocks).
   2336      api.nvim_get_api_info()
   2337      -- Send input to complete the mapping.
   2338      api.nvim_input('x')
   2339      expect([[
   2340        FIRST LINE
   2341        SECOND LINfooE]])
   2342    end)
   2343 
   2344    it('does not interrupt Insert mode i_CTRL-O #10035', function()
   2345      feed('iHello World<c-o>')
   2346      eq({ mode = 'niI', blocking = false }, api.nvim_get_mode()) -- fast event
   2347      eq(2, eval('1+1')) -- causes K_EVENT key
   2348      eq({ mode = 'niI', blocking = false }, api.nvim_get_mode()) -- still in ctrl-o mode
   2349      feed('dd')
   2350      eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) -- left ctrl-o mode
   2351      expect('') -- executed the command
   2352    end)
   2353 
   2354    it('does not interrupt Select mode v_CTRL-O #15688', function()
   2355      feed('iHello World<esc>gh<c-o>')
   2356      eq({ mode = 'vs', blocking = false }, api.nvim_get_mode()) -- fast event
   2357      eq({ mode = 'vs', blocking = false }, api.nvim_get_mode()) -- again #15288
   2358      eq(2, eval('1+1')) -- causes K_EVENT key
   2359      eq({ mode = 'vs', blocking = false }, api.nvim_get_mode()) -- still in ctrl-o mode
   2360      feed('^')
   2361      eq({ mode = 's', blocking = false }, api.nvim_get_mode()) -- left ctrl-o mode
   2362      feed('h')
   2363      eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) -- entered insert mode
   2364      expect('h') -- selection is the whole line and is replaced
   2365    end)
   2366 
   2367    it('does not interrupt Insert mode i_0_CTRL-D #13997', function()
   2368      command('set timeoutlen=9999')
   2369      feed('i<Tab><Tab>a0')
   2370      eq(2, eval('1+1')) -- causes K_EVENT key
   2371      feed('<C-D>')
   2372      expect('a') -- recognized i_0_CTRL-D
   2373    end)
   2374 
   2375    it("does not interrupt with 'digraph'", function()
   2376      command('set digraph')
   2377      feed('i,')
   2378      eq(2, eval('1+1')) -- causes K_EVENT key
   2379      feed('<BS>')
   2380      eq(2, eval('1+1')) -- causes K_EVENT key
   2381      feed('.')
   2382      expect('…') -- digraph ",." worked
   2383      feed('<Esc>')
   2384      feed(':,')
   2385      eq(2, eval('1+1')) -- causes K_EVENT key
   2386      feed('<BS>')
   2387      eq(2, eval('1+1')) -- causes K_EVENT key
   2388      feed('.')
   2389      eq('…', fn.getcmdline()) -- digraph ",." worked
   2390    end)
   2391  end)
   2392 
   2393  describe('nvim_get_context', function()
   2394    it('validation', function()
   2395      eq("Invalid key: 'blah'", pcall_err(api.nvim_get_context, { blah = {} }))
   2396      eq(
   2397        "Invalid 'types': expected Array, got Integer",
   2398        pcall_err(api.nvim_get_context, { types = 42 })
   2399      )
   2400      eq(
   2401        "Invalid 'type': 'zub'",
   2402        pcall_err(api.nvim_get_context, { types = { 'jumps', 'zub', 'zam' } })
   2403      )
   2404    end)
   2405    it('returns map of current editor state', function()
   2406      local opts = { types = { 'regs', 'jumps', 'bufs', 'gvars' } }
   2407      eq({}, parse_context(api.nvim_get_context({})))
   2408 
   2409      feed('i1<cr>2<cr>3<c-[>ddddddqahjklquuu')
   2410      feed('gg')
   2411      feed('G')
   2412      command('edit! BUF1')
   2413      command('edit BUF2')
   2414      api.nvim_set_var('one', 1)
   2415      api.nvim_set_var('Two', 2)
   2416      api.nvim_set_var('THREE', 3)
   2417 
   2418      local expected_ctx = {
   2419        ['regs'] = {
   2420          { ['rt'] = 1, ['rc'] = { '1' }, ['n'] = 49, ['ru'] = true },
   2421          { ['rt'] = 1, ['rc'] = { '2' }, ['n'] = 50 },
   2422          { ['rt'] = 1, ['rc'] = { '3' }, ['n'] = 51 },
   2423          { ['rc'] = { 'hjkl' }, ['n'] = 97 },
   2424        },
   2425 
   2426        ['jumps'] = eval((([[
   2427        filter(map(add(
   2428        getjumplist()[0], { 'bufnr': bufnr('%'), 'lnum': getcurpos()[1] }),
   2429        'filter(
   2430        { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum },
   2431        { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)')
   2432        ]]):gsub('\n', ''))),
   2433 
   2434        ['bufs'] = eval([[
   2435        filter(map(getbufinfo(), '{ "f": v:val.name }'), '!empty(v:val.f)')
   2436        ]]),
   2437 
   2438        ['gvars'] = { { 'one', 1 }, { 'Two', 2 }, { 'THREE', 3 } },
   2439      }
   2440 
   2441      eq(expected_ctx, parse_context(api.nvim_get_context(opts)))
   2442      eq(expected_ctx, parse_context(api.nvim_get_context({})))
   2443      eq(expected_ctx, parse_context(api.nvim_get_context({ types = {} })))
   2444    end)
   2445  end)
   2446 
   2447  describe('nvim_load_context', function()
   2448    it('sets current editor state to given context dict', function()
   2449      local opts = { types = { 'regs', 'jumps', 'bufs', 'gvars' } }
   2450      eq({}, parse_context(api.nvim_get_context(opts)))
   2451 
   2452      api.nvim_set_var('one', 1)
   2453      api.nvim_set_var('Two', 2)
   2454      api.nvim_set_var('THREE', 3)
   2455      local ctx = api.nvim_get_context(opts)
   2456      api.nvim_set_var('one', 'a')
   2457      api.nvim_set_var('Two', 'b')
   2458      api.nvim_set_var('THREE', 'c')
   2459      eq({ 'a', 'b', 'c' }, eval('[g:one, g:Two, g:THREE]'))
   2460      api.nvim_load_context(ctx)
   2461      eq({ 1, 2, 3 }, eval('[g:one, g:Two, g:THREE]'))
   2462    end)
   2463 
   2464    it('errors when context dict is invalid', function()
   2465      eq(
   2466        'E474: Failed to convert list to msgpack string buffer',
   2467        pcall_err(api.nvim_load_context, { regs = { {} }, jumps = { {} } })
   2468      )
   2469      eq(
   2470        'E474: Failed to convert list to msgpack string buffer',
   2471        pcall_err(api.nvim_load_context, { regs = { { [''] = '' } } })
   2472      )
   2473    end)
   2474  end)
   2475 
   2476  describe('nvim_replace_termcodes', function()
   2477    it('escapes K_SPECIAL as K_SPECIAL KS_SPECIAL KE_FILLER', function()
   2478      eq('\128\254X', n.api.nvim_replace_termcodes('\128', true, true, true))
   2479    end)
   2480 
   2481    it('leaves non-K_SPECIAL string unchanged', function()
   2482      eq('abc', n.api.nvim_replace_termcodes('abc', true, true, true))
   2483    end)
   2484 
   2485    it('converts <expressions>', function()
   2486      eq('\\', n.api.nvim_replace_termcodes('<Leader>', true, true, true))
   2487    end)
   2488 
   2489    it('converts <LeftMouse> to K_SPECIAL KS_EXTRA KE_LEFTMOUSE', function()
   2490      -- K_SPECIAL KS_EXTRA KE_LEFTMOUSE
   2491      -- 0x80      0xfd     0x2c
   2492      -- 128       253      44
   2493      eq('\128\253\44', n.api.nvim_replace_termcodes('<LeftMouse>', true, true, true))
   2494    end)
   2495 
   2496    it('converts keycodes', function()
   2497      eq('\nx\27x\rx<x', n.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, true))
   2498    end)
   2499 
   2500    it('does not convert keycodes if special=false', function()
   2501      eq(
   2502        '<NL>x<Esc>x<CR>x<lt>x',
   2503        n.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, false)
   2504      )
   2505    end)
   2506 
   2507    it('does not crash when transforming an empty string', function()
   2508      -- Actually does not test anything, because current code will use NULL for
   2509      -- an empty string.
   2510      --
   2511      -- Problem here is that if String argument has .data in allocated memory
   2512      -- then `return str` in vim_replace_termcodes body will make Neovim free
   2513      -- `str.data` twice: once when freeing arguments, then when freeing return
   2514      -- value.
   2515      eq('', api.nvim_replace_termcodes('', true, true, true))
   2516    end)
   2517 
   2518    -- Not exactly the case, as nvim_replace_termcodes() escapes K_SPECIAL in Unicode
   2519    it('translates the result of keytrans() on string with 0x80 byte back', function()
   2520      local s = 'ff\128\253\097tt'
   2521      eq(s, api.nvim_replace_termcodes(fn.keytrans(s), true, true, true))
   2522    end)
   2523  end)
   2524 
   2525  describe('nvim_feedkeys', function()
   2526    it('K_SPECIAL escaping', function()
   2527      local function on_setup()
   2528        -- notice the special char(…) \xe2\80\xa6
   2529        api.nvim_feedkeys(':let x1="…"\n', '', true)
   2530 
   2531        -- Both nvim_replace_termcodes and nvim_feedkeys escape \x80
   2532        local inp = n.api.nvim_replace_termcodes(':let x2="…"<CR>', true, true, true)
   2533        api.nvim_feedkeys(inp, '', true) -- escape_ks=true
   2534 
   2535        -- nvim_feedkeys with K_SPECIAL escaping disabled
   2536        inp = n.api.nvim_replace_termcodes(':let x3="…"<CR>', true, true, true)
   2537        api.nvim_feedkeys(inp, '', false) -- escape_ks=false
   2538 
   2539        n.stop()
   2540      end
   2541 
   2542      -- spin the loop a bit
   2543      n.run(nil, nil, on_setup)
   2544 
   2545      eq('…', api.nvim_get_var('x1'))
   2546      -- Because of the double escaping this is neq
   2547      neq('…', api.nvim_get_var('x2'))
   2548      eq('…', api.nvim_get_var('x3'))
   2549    end)
   2550  end)
   2551 
   2552  describe('nvim_out_write', function()
   2553    local screen
   2554 
   2555    before_each(function()
   2556      screen = Screen.new(40, 8)
   2557    end)
   2558 
   2559    it('prints long messages correctly #20534', function()
   2560      exec([[
   2561        set more
   2562        redir => g:out
   2563          silent! call nvim_out_write('a')
   2564          silent! call nvim_out_write('a')
   2565          silent! call nvim_out_write('a')
   2566          silent! call nvim_out_write("\n")
   2567          silent! call nvim_out_write('a')
   2568          silent! call nvim_out_write('a')
   2569          silent! call nvim_out_write(repeat('a', 5000) .. "\n")
   2570          silent! call nvim_out_write('a')
   2571          silent! call nvim_out_write('a')
   2572          silent! call nvim_out_write('a')
   2573          silent! call nvim_out_write("\n")
   2574        redir END
   2575      ]])
   2576      eq('\naaa\n' .. ('a'):rep(5002) .. '\naaa', api.nvim_get_var('out'))
   2577    end)
   2578 
   2579    it('blank line in message', function()
   2580      feed([[:call nvim_out_write("\na\n")<CR>]])
   2581      screen:expect {
   2582        grid = [[
   2583                                                |
   2584        {1:~                                       }|*3
   2585        {3:                                        }|
   2586                                                |
   2587        a                                       |
   2588        {6:Press ENTER or type command to continue}^ |
   2589      ]],
   2590      }
   2591      feed('<CR>')
   2592      feed([[:call nvim_out_write("b\n\nc\n")<CR>]])
   2593      screen:expect {
   2594        grid = [[
   2595                                                |
   2596        {1:~                                       }|*2
   2597        {3:                                        }|
   2598        b                                       |
   2599                                                |
   2600        c                                       |
   2601        {6:Press ENTER or type command to continue}^ |
   2602      ]],
   2603      }
   2604    end)
   2605 
   2606    it('NUL bytes in message', function()
   2607      feed([[:lua vim.api.nvim_out_write('aaa\0bbb\0\0ccc\nddd\0\0\0eee\n')<CR>]])
   2608      screen:expect {
   2609        grid = [[
   2610                                                |
   2611        {1:~                                       }|*3
   2612        {3:                                        }|
   2613        aaa{18:^@}bbb{18:^@^@}ccc                         |
   2614        ddd{18:^@^@^@}eee                            |
   2615        {6:Press ENTER or type command to continue}^ |
   2616      ]],
   2617      }
   2618    end)
   2619  end)
   2620 
   2621  describe('nvim_err_write', function()
   2622    local screen
   2623 
   2624    before_each(function()
   2625      screen = Screen.new(40, 8)
   2626    end)
   2627 
   2628    it('can show one line', function()
   2629      async_meths.nvim_err_write('has bork\n')
   2630      screen:expect([[
   2631        ^                                        |
   2632        {1:~                                       }|*6
   2633        {9:has bork}                                |
   2634      ]])
   2635    end)
   2636 
   2637    it('shows return prompt when more than &cmdheight lines', function()
   2638      async_meths.nvim_err_write('something happened\nvery bad\n')
   2639      screen:expect([[
   2640                                                |
   2641        {1:~                                       }|*3
   2642        {3:                                        }|
   2643        {9:something happened}                      |
   2644        {9:very bad}                                |
   2645        {6:Press ENTER or type command to continue}^ |
   2646      ]])
   2647    end)
   2648 
   2649    it('shows return prompt after all lines are shown', function()
   2650      async_meths.nvim_err_write('FAILURE\nERROR\nEXCEPTION\nTRACEBACK\n')
   2651      screen:expect([[
   2652                                                |
   2653        {1:~                                       }|
   2654        {3:                                        }|
   2655        {9:FAILURE}                                 |
   2656        {9:ERROR}                                   |
   2657        {9:EXCEPTION}                               |
   2658        {9:TRACEBACK}                               |
   2659        {6:Press ENTER or type command to continue}^ |
   2660      ]])
   2661    end)
   2662 
   2663    it('handles multiple calls', function()
   2664      -- without linebreak text is joined to one line
   2665      async_meths.nvim_err_write('very ')
   2666      async_meths.nvim_err_write('fail\n')
   2667      screen:expect([[
   2668        ^                                        |
   2669        {1:~                                       }|*6
   2670        {9:very fail}                               |
   2671      ]])
   2672      n.poke_eventloop()
   2673 
   2674      -- shows up to &cmdheight lines
   2675      async_meths.nvim_err_write('more fail\ntoo fail\n')
   2676      screen:expect([[
   2677                                                |
   2678        {1:~                                       }|*3
   2679        {3:                                        }|
   2680        {9:more fail}                               |
   2681        {9:too fail}                                |
   2682        {6:Press ENTER or type command to continue}^ |
   2683      ]])
   2684      feed('<cr>') -- exit the press ENTER screen
   2685    end)
   2686 
   2687    it('NUL bytes in message', function()
   2688      async_meths.nvim_err_write('aaa\0bbb\0\0ccc\nddd\0\0\0eee\n')
   2689      screen:expect {
   2690        grid = [[
   2691                                                |
   2692        {1:~                                       }|*3
   2693        {3:                                        }|
   2694        {9:aaa^@bbb^@^@ccc}                         |
   2695        {9:ddd^@^@^@eee}                            |
   2696        {6:Press ENTER or type command to continue}^ |
   2697      ]],
   2698      }
   2699    end)
   2700  end)
   2701 
   2702  describe('nvim_err_writeln', function()
   2703    local screen
   2704 
   2705    before_each(function()
   2706      screen = Screen.new(40, 8)
   2707    end)
   2708 
   2709    it('shows only one return prompt after all lines are shown', function()
   2710      async_meths.nvim_err_writeln('FAILURE\nERROR\nEXCEPTION\nTRACEBACK')
   2711      screen:expect([[
   2712                                                |
   2713        {1:~                                       }|
   2714        {3:                                        }|
   2715        {9:FAILURE}                                 |
   2716        {9:ERROR}                                   |
   2717        {9:EXCEPTION}                               |
   2718        {9:TRACEBACK}                               |
   2719        {6:Press ENTER or type command to continue}^ |
   2720      ]])
   2721      feed('<CR>')
   2722      screen:expect([[
   2723        ^                                        |
   2724        {1:~                                       }|*6
   2725                                                |
   2726      ]])
   2727    end)
   2728  end)
   2729 
   2730  describe('nvim_list_chans, nvim_get_chan_info', function()
   2731    before_each(function()
   2732      command('autocmd ChanOpen * let g:opened_event = deepcopy(v:event)')
   2733      command('autocmd ChanInfo * let g:info_event = deepcopy(v:event)')
   2734    end)
   2735    local testinfo = {
   2736      stream = 'stdio',
   2737      id = 1,
   2738      mode = 'rpc',
   2739      client = {},
   2740    }
   2741    local stderr = {
   2742      stream = 'stderr',
   2743      id = 2,
   2744      mode = 'bytes',
   2745    }
   2746 
   2747    it('returns {} for invalid channel', function()
   2748      eq({}, api.nvim_get_chan_info(-1))
   2749      -- more preallocated numbers might be added, try something high
   2750      eq({}, api.nvim_get_chan_info(10))
   2751    end)
   2752 
   2753    it('stream=stdio channel', function()
   2754      eq({ [1] = testinfo, [2] = stderr }, api.nvim_list_chans())
   2755      -- 0 should return current channel
   2756      eq(testinfo, api.nvim_get_chan_info(0))
   2757      eq(testinfo, api.nvim_get_chan_info(1))
   2758      eq(stderr, api.nvim_get_chan_info(2))
   2759 
   2760      api.nvim_set_client_info(
   2761        'functionaltests',
   2762        { major = 0, minor = 3, patch = 17 },
   2763        'ui',
   2764        { do_stuff = { n_args = { 2, 3 } } },
   2765        { license = 'Apache2' }
   2766      )
   2767      local info = {
   2768        stream = 'stdio',
   2769        id = 1,
   2770        mode = 'rpc',
   2771        client = {
   2772          name = 'functionaltests',
   2773          version = { major = 0, minor = 3, patch = 17 },
   2774          type = 'ui',
   2775          methods = { do_stuff = { n_args = { 2, 3 } } },
   2776          attributes = { license = 'Apache2' },
   2777        },
   2778      }
   2779      eq({ info = info }, api.nvim_get_var('info_event'))
   2780      eq({ [1] = info, [2] = stderr }, api.nvim_list_chans())
   2781      eq(info, api.nvim_get_chan_info(1))
   2782    end)
   2783 
   2784    it('stream=job channel', function()
   2785      eq(3, eval("jobstart(['cat'], {'rpc': v:true})"))
   2786      local catpath = eval('exepath("cat")')
   2787      local info = {
   2788        stream = 'job',
   2789        id = 3,
   2790        argv = { catpath },
   2791        mode = 'rpc',
   2792        client = {},
   2793      }
   2794      eq({ info = info }, api.nvim_get_var('opened_event'))
   2795      eq({ [1] = testinfo, [2] = stderr, [3] = info }, api.nvim_list_chans())
   2796      eq(info, api.nvim_get_chan_info(3))
   2797      eval(
   2798        'rpcrequest(3, "nvim_set_client_info", "amazing-cat", {}, "remote",'
   2799          .. '{"nvim_command":{"n_args":1}},' -- and so on
   2800          .. '{"description":"The Amazing Cat"})'
   2801      )
   2802      info = {
   2803        stream = 'job',
   2804        id = 3,
   2805        argv = { catpath },
   2806        mode = 'rpc',
   2807        client = {
   2808          name = 'amazing-cat',
   2809          version = { major = 0 },
   2810          type = 'remote',
   2811          methods = { nvim_command = { n_args = 1 } },
   2812          attributes = { description = 'The Amazing Cat' },
   2813        },
   2814      }
   2815      eq({ info = info }, api.nvim_get_var('info_event'))
   2816      eq({ [1] = testinfo, [2] = stderr, [3] = info }, api.nvim_list_chans())
   2817 
   2818      eq(
   2819        "Vim:Invoking 'nvim_set_current_buf' on channel 3 (amazing-cat):\nWrong type for argument 1 when calling nvim_set_current_buf, expecting Buffer",
   2820        pcall_err(eval, 'rpcrequest(3, "nvim_set_current_buf", -1)')
   2821      )
   2822      eq(info, eval('rpcrequest(3, "nvim_get_chan_info", 0)'))
   2823    end)
   2824 
   2825    it('stream=job :terminal channel', function()
   2826      command(':terminal')
   2827      eq(1, api.nvim_get_current_buf())
   2828      eq(3, api.nvim_get_option_value('channel', { buf = 1 }))
   2829 
   2830      local info = {
   2831        stream = 'job',
   2832        id = 3,
   2833        argv = { eval('exepath(&shell)') },
   2834        mode = 'terminal',
   2835        buffer = 1,
   2836        pty = '?',
   2837      }
   2838      local event = api.nvim_get_var('opened_event')
   2839      if not is_os('win') then
   2840        info.pty = event.info.pty
   2841        neq(nil, string.match(info.pty, '^/dev/'))
   2842      end
   2843      eq({ info = info }, event)
   2844      info.buffer = 1
   2845      eq({ [1] = testinfo, [2] = stderr, [3] = info }, api.nvim_list_chans())
   2846      eq(info, api.nvim_get_chan_info(3))
   2847 
   2848      -- :terminal with args + running process.
   2849      command('enew')
   2850      local progpath_esc = eval('shellescape(v:progpath)')
   2851      fn.jobstart(('%s -u NONE -i NONE'):format(progpath_esc), {
   2852        term = true,
   2853        env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
   2854      })
   2855      eq(-1, eval('jobwait([&channel], 0)[0]')) -- Running?
   2856      local expected2 = {
   2857        stream = 'job',
   2858        id = 4,
   2859        argv = (is_os('win') and {
   2860          eval('&shell'),
   2861          '/s',
   2862          '/c',
   2863          fmt('"%s -u NONE -i NONE"', progpath_esc),
   2864        } or {
   2865          eval('&shell'),
   2866          eval('&shellcmdflag'),
   2867          fmt('%s -u NONE -i NONE', progpath_esc),
   2868        }),
   2869        mode = 'terminal',
   2870        buffer = 2,
   2871        pty = '?',
   2872      }
   2873      local actual2 = eval('nvim_get_chan_info(&channel)')
   2874      expected2.pty = actual2.pty
   2875      eq(expected2, actual2)
   2876 
   2877      -- :terminal with args + stopped process.
   2878      eq(1, eval('jobstop(&channel)'))
   2879      eval('jobwait([&channel], 1000)') -- Wait.
   2880      expected2.pty = (is_os('win') and '?' or '') -- pty stream was closed.
   2881      eq(expected2, eval('nvim_get_chan_info(&channel)'))
   2882    end)
   2883  end)
   2884 
   2885  describe('nvim_call_atomic', function()
   2886    it('works', function()
   2887      api.nvim_buf_set_lines(0, 0, -1, true, { 'first' })
   2888      local req = {
   2889        { 'nvim_get_current_line', {} },
   2890        { 'nvim_set_current_line', { 'second' } },
   2891      }
   2892      eq({ { 'first', NIL }, NIL }, api.nvim_call_atomic(req))
   2893      eq({ 'second' }, api.nvim_buf_get_lines(0, 0, -1, true))
   2894    end)
   2895 
   2896    it('allows multiple return values', function()
   2897      local req = {
   2898        { 'nvim_set_var', { 'avar', true } },
   2899        { 'nvim_set_var', { 'bvar', 'string' } },
   2900        { 'nvim_get_var', { 'avar' } },
   2901        { 'nvim_get_var', { 'bvar' } },
   2902      }
   2903      eq({ { NIL, NIL, true, 'string' }, NIL }, api.nvim_call_atomic(req))
   2904    end)
   2905 
   2906    it('is aborted by errors in call', function()
   2907      local error_types = api.nvim_get_api_info()[2].error_types
   2908      local req = {
   2909        { 'nvim_set_var', { 'one', 1 } },
   2910        { 'nvim_buf_set_lines', {} },
   2911        { 'nvim_set_var', { 'two', 2 } },
   2912      }
   2913      eq({
   2914        { NIL },
   2915        {
   2916          1,
   2917          error_types.Exception.id,
   2918          'Wrong number of arguments: expecting 5 but got 0',
   2919        },
   2920      }, api.nvim_call_atomic(req))
   2921      eq(1, api.nvim_get_var('one'))
   2922      eq(false, pcall(api.nvim_get_var, 'two'))
   2923 
   2924      -- still returns all previous successful calls
   2925      req = {
   2926        { 'nvim_set_var', { 'avar', 5 } },
   2927        { 'nvim_set_var', { 'bvar', 'string' } },
   2928        { 'nvim_get_var', { 'avar' } },
   2929        { 'nvim_buf_get_lines', { 0, 10, 20, true } },
   2930        { 'nvim_get_var', { 'bvar' } },
   2931      }
   2932      eq(
   2933        { { NIL, NIL, 5 }, { 3, error_types.Validation.id, 'Index out of bounds' } },
   2934        api.nvim_call_atomic(req)
   2935      )
   2936 
   2937      req = {
   2938        { 'i_am_not_a_method', { 'xx' } },
   2939        { 'nvim_set_var', { 'avar', 10 } },
   2940      }
   2941      eq(
   2942        { {}, { 0, error_types.Exception.id, 'Invalid method: i_am_not_a_method' } },
   2943        api.nvim_call_atomic(req)
   2944      )
   2945      eq(5, api.nvim_get_var('avar'))
   2946    end)
   2947 
   2948    it('validation', function()
   2949      local req = {
   2950        { 'nvim_set_var', { 'avar', 1 } },
   2951        { 'nvim_set_var' },
   2952        { 'nvim_set_var', { 'avar', 2 } },
   2953      }
   2954      eq("Invalid 'calls' item: expected 2-item Array", pcall_err(api.nvim_call_atomic, req))
   2955      -- call before was done, but not after
   2956      eq(1, api.nvim_get_var('avar'))
   2957 
   2958      req = {
   2959        { 'nvim_set_var', { 'bvar', { 2, 3 } } },
   2960        12,
   2961      }
   2962      eq("Invalid 'calls' item: expected Array, got Integer", pcall_err(api.nvim_call_atomic, req))
   2963      eq({ 2, 3 }, api.nvim_get_var('bvar'))
   2964 
   2965      req = {
   2966        { 'nvim_set_current_line', 'little line' },
   2967        { 'nvim_set_var', { 'avar', 3 } },
   2968      }
   2969      eq('Invalid call args: expected Array, got String', pcall_err(api.nvim_call_atomic, req))
   2970      -- call before was done, but not after
   2971      eq(1, api.nvim_get_var('avar'))
   2972      eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, true))
   2973    end)
   2974  end)
   2975 
   2976  describe('nvim_list_runtime_paths', function()
   2977    local test_dir = 'Xtest_list_runtime_paths'
   2978 
   2979    setup(function()
   2980      local pathsep = n.get_pathsep()
   2981      mkdir_p(test_dir .. pathsep .. 'a')
   2982      mkdir_p(test_dir .. pathsep .. 'b')
   2983    end)
   2984    teardown(function()
   2985      rmdir(test_dir)
   2986    end)
   2987    before_each(function()
   2988      api.nvim_set_current_dir(test_dir)
   2989    end)
   2990 
   2991    it('returns nothing with empty &runtimepath', function()
   2992      api.nvim_set_option_value('runtimepath', '', {})
   2993      eq({}, api.nvim_list_runtime_paths())
   2994    end)
   2995    it('returns single runtimepath', function()
   2996      api.nvim_set_option_value('runtimepath', 'a', {})
   2997      eq({ 'a' }, api.nvim_list_runtime_paths())
   2998    end)
   2999    it('returns two runtimepaths', function()
   3000      api.nvim_set_option_value('runtimepath', 'a,b', {})
   3001      eq({ 'a', 'b' }, api.nvim_list_runtime_paths())
   3002    end)
   3003    it('returns empty strings when appropriate', function()
   3004      api.nvim_set_option_value('runtimepath', 'a,,b', {})
   3005      eq({ 'a', '', 'b' }, api.nvim_list_runtime_paths())
   3006      api.nvim_set_option_value('runtimepath', ',a,b', {})
   3007      eq({ '', 'a', 'b' }, api.nvim_list_runtime_paths())
   3008      -- Trailing "," is ignored. Use ",," if you really really want CWD.
   3009      api.nvim_set_option_value('runtimepath', 'a,b,', {})
   3010      eq({ 'a', 'b' }, api.nvim_list_runtime_paths())
   3011      api.nvim_set_option_value('runtimepath', 'a,b,,', {})
   3012      eq({ 'a', 'b', '' }, api.nvim_list_runtime_paths())
   3013    end)
   3014    it('truncates too long paths', function()
   3015      local long_path = ('/a'):rep(8192)
   3016      api.nvim_set_option_value('runtimepath', long_path, {})
   3017      local paths_list = api.nvim_list_runtime_paths()
   3018      eq({}, paths_list)
   3019    end)
   3020  end)
   3021 
   3022  it('can throw exceptions', function()
   3023    local status, err = pcall(api.nvim_get_option_value, 'invalid-option', {})
   3024    eq(false, status)
   3025    matches("Unknown option 'invalid%-option'", err)
   3026  end)
   3027 
   3028  it('does not truncate error message <1 MB #5984', function()
   3029    local very_long_name = 'A' .. ('x'):rep(10000) .. 'Z'
   3030    local status, err = pcall(api.nvim_get_option_value, very_long_name, {})
   3031    eq(false, status)
   3032    eq(very_long_name, err:match('Ax+Z?'))
   3033  end)
   3034 
   3035  it('does not leak memory on incorrect argument types', function()
   3036    local status, err = pcall(api.nvim_set_current_dir, { 'not', 'a', 'dir' })
   3037    eq(false, status)
   3038    matches(': Wrong type for argument 1 when calling nvim_set_current_dir, expecting String', err)
   3039  end)
   3040 
   3041  describe('nvim_parse_expression', function()
   3042    before_each(function()
   3043      api.nvim_set_option_value('isident', '', {})
   3044    end)
   3045 
   3046    local function simplify_east_api_node(line, east_api_node)
   3047      if east_api_node == NIL then
   3048        return nil
   3049      end
   3050      if east_api_node.children then
   3051        for k, v in pairs(east_api_node.children) do
   3052          east_api_node.children[k] = simplify_east_api_node(line, v)
   3053        end
   3054      end
   3055      local typ = east_api_node.type
   3056      if typ == 'Register' then
   3057        typ = typ .. ('(name=%s)'):format(tostring(intchar2lua(east_api_node.name)))
   3058        east_api_node.name = nil
   3059      elseif typ == 'PlainIdentifier' then
   3060        typ = typ
   3061          .. ('(scope=%s,ident=%s)'):format(
   3062            tostring(intchar2lua(east_api_node.scope)),
   3063            east_api_node.ident
   3064          )
   3065        east_api_node.scope = nil
   3066        east_api_node.ident = nil
   3067      elseif typ == 'PlainKey' then
   3068        typ = typ .. ('(key=%s)'):format(east_api_node.ident)
   3069        east_api_node.ident = nil
   3070      elseif typ == 'Comparison' then
   3071        typ = typ
   3072          .. ('(type=%s,inv=%u,ccs=%s)'):format(
   3073            east_api_node.cmp_type,
   3074            east_api_node.invert and 1 or 0,
   3075            east_api_node.ccs_strategy
   3076          )
   3077        east_api_node.ccs_strategy = nil
   3078        east_api_node.cmp_type = nil
   3079        east_api_node.invert = nil
   3080      elseif typ == 'Integer' then
   3081        typ = typ .. ('(val=%u)'):format(east_api_node.ivalue)
   3082        east_api_node.ivalue = nil
   3083      elseif typ == 'Float' then
   3084        typ = typ .. format_string('(val=%e)', east_api_node.fvalue)
   3085        east_api_node.fvalue = nil
   3086      elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then
   3087        typ = format_string('%s(val=%q)', typ, east_api_node.svalue)
   3088        east_api_node.svalue = nil
   3089      elseif typ == 'Option' then
   3090        typ = ('%s(scope=%s,ident=%s)'):format(
   3091          typ,
   3092          tostring(intchar2lua(east_api_node.scope)),
   3093          east_api_node.ident
   3094        )
   3095        east_api_node.ident = nil
   3096        east_api_node.scope = nil
   3097      elseif typ == 'Environment' then
   3098        typ = ('%s(ident=%s)'):format(typ, east_api_node.ident)
   3099        east_api_node.ident = nil
   3100      elseif typ == 'Assignment' then
   3101        local aug = east_api_node.augmentation
   3102        if aug == '' then
   3103          aug = 'Plain'
   3104        end
   3105        typ = ('%s(%s)'):format(typ, aug)
   3106        east_api_node.augmentation = nil
   3107      end
   3108      typ = ('%s:%u:%u:%s'):format(
   3109        typ,
   3110        east_api_node.start[1],
   3111        east_api_node.start[2],
   3112        line:sub(east_api_node.start[2] + 1, east_api_node.start[2] + 1 + east_api_node.len - 1)
   3113      )
   3114      assert(east_api_node.start[2] + east_api_node.len - 1 <= #line)
   3115      for k, _ in pairs(east_api_node.start) do
   3116        assert(({ true, true })[k])
   3117      end
   3118      east_api_node.start = nil
   3119      east_api_node.type = nil
   3120      east_api_node.len = nil
   3121      local can_simplify = true
   3122      for _, _ in pairs(east_api_node) do
   3123        if can_simplify then
   3124          can_simplify = false
   3125        end
   3126      end
   3127      if can_simplify then
   3128        return typ
   3129      else
   3130        east_api_node[1] = typ
   3131        return east_api_node
   3132      end
   3133    end
   3134    local function simplify_east_api(line, east_api)
   3135      if east_api.error then
   3136        east_api.err = east_api.error
   3137        east_api.error = nil
   3138        east_api.err.msg = east_api.err.message
   3139        east_api.err.message = nil
   3140      end
   3141      if east_api.ast then
   3142        east_api.ast = { simplify_east_api_node(line, east_api.ast) }
   3143        if #east_api.ast == 0 then
   3144          east_api.ast = nil
   3145        end
   3146      end
   3147      if east_api.len == #line then
   3148        east_api.len = nil
   3149      end
   3150      return east_api
   3151    end
   3152    local function simplify_east_hl(line, east_hl)
   3153      for i, v in ipairs(east_hl) do
   3154        east_hl[i] = ('%s:%u:%u:%s'):format(v[4], v[1], v[2], line:sub(v[2] + 1, v[3]))
   3155      end
   3156      return east_hl
   3157    end
   3158    local FLAGS_TO_STR = {
   3159      [0] = '',
   3160      [1] = 'm',
   3161      [2] = 'E',
   3162      [3] = 'mE',
   3163      [4] = 'l',
   3164      [5] = 'lm',
   3165      [6] = 'lE',
   3166      [7] = 'lmE',
   3167    }
   3168    local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs, nz_flags_exps)
   3169      if type(str) ~= 'string' then
   3170        return
   3171      end
   3172      local zflags = opts.flags[1]
   3173      nz_flags_exps = nz_flags_exps or {}
   3174      for _, flags in ipairs(opts.flags) do
   3175        local err, msg = pcall(function()
   3176          local east_api = api.nvim_parse_expression(str, FLAGS_TO_STR[flags], true)
   3177          local east_hl = east_api.highlight
   3178          east_api.highlight = nil
   3179          local ast = simplify_east_api(str, east_api)
   3180          local hls = simplify_east_hl(str, east_hl)
   3181          local exps = {
   3182            ast = exp_ast,
   3183            hl_fs = exp_highlighting_fs,
   3184          }
   3185          local add_exps = nz_flags_exps[flags]
   3186          if not add_exps and flags == 3 + zflags then
   3187            add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags]
   3188          end
   3189          if add_exps then
   3190            if add_exps.ast then
   3191              exps.ast = mergedicts_copy(exps.ast, add_exps.ast)
   3192            end
   3193            if add_exps.hl_fs then
   3194              exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs)
   3195            end
   3196          end
   3197          eq(exps.ast, ast)
   3198          if exp_highlighting_fs then
   3199            local exp_highlighting = {}
   3200            local next_col = 0
   3201            for i, h in ipairs(exps.hl_fs) do
   3202              exp_highlighting[i], next_col = h(next_col)
   3203            end
   3204            eq(exp_highlighting, hls)
   3205          end
   3206        end)
   3207        if not err then
   3208          if type(msg) == 'table' then
   3209            local merr, new_msg = pcall(format_string, 'table error:\n%s\n\n(%r)', msg.message, msg)
   3210            if merr then
   3211              msg = new_msg
   3212            else
   3213              msg = format_string('table error without .message:\n(%r)', msg)
   3214            end
   3215          elseif type(msg) ~= 'string' then
   3216            msg = format_string('non-string non-table error:\n%r', msg)
   3217          end
   3218          error(
   3219            format_string(
   3220              'Error while processing test (%r, %s):\n%s',
   3221              str,
   3222              FLAGS_TO_STR[flags],
   3223              msg
   3224            )
   3225          )
   3226        end
   3227      end
   3228    end
   3229    local function hl(group, str, shift)
   3230      return function(next_col)
   3231        local col = next_col + (shift or 0)
   3232        return (('%s:%u:%u:%s'):format('Nvim' .. group, 0, col, str)), (col + #str)
   3233      end
   3234    end
   3235    local function fmtn(typ, args, rest)
   3236      if
   3237        typ == 'UnknownFigure'
   3238        or typ == 'DictLiteral'
   3239        or typ == 'CurlyBracesIdentifier'
   3240        or typ == 'Lambda'
   3241      then
   3242        return ('%s%s'):format(typ, rest)
   3243      elseif typ == 'DoubleQuotedString' or typ == 'SingleQuotedString' then
   3244        if args:sub(-4) == 'NULL' then
   3245          args = args:sub(1, -5) .. '""'
   3246        end
   3247        return ('%s(%s)%s'):format(typ, args, rest)
   3248      end
   3249    end
   3250 
   3251    it('does not crash parsing invalid VimL expression', function()
   3252      api.nvim_input(':<C-r>=')
   3253      api.nvim_input('1bork/') -- #29648
   3254      assert_alive()
   3255      api.nvim_input('<C-u>];')
   3256      assert_alive()
   3257      api.nvim_parse_expression('a{b}', '', false)
   3258      assert_alive()
   3259    end)
   3260 
   3261    require('test.unit.viml.expressions.parser_tests')(it, _check_parsing, hl, fmtn)
   3262  end)
   3263 
   3264  describe('nvim_list_uis', function()
   3265    it('returns empty if --headless', function()
   3266      -- Test runner defaults to --headless.
   3267      eq({}, api.nvim_list_uis())
   3268    end)
   3269    it('returns attached UIs', function()
   3270      local screen = Screen.new(20, 4, { override = true })
   3271      local expected = {
   3272        {
   3273          chan = 1,
   3274          ext_cmdline = false,
   3275          ext_hlstate = false,
   3276          ext_linegrid = screen._options.ext_linegrid or false,
   3277          ext_messages = false,
   3278          ext_multigrid = false,
   3279          ext_popupmenu = false,
   3280          ext_tabline = false,
   3281          ext_termcolors = false,
   3282          ext_wildmenu = false,
   3283          height = 4,
   3284          override = true,
   3285          rgb = true,
   3286          stdin_tty = false,
   3287          stdout_tty = false,
   3288          term_background = '',
   3289          term_colors = 0,
   3290          term_name = '',
   3291          width = 20,
   3292        },
   3293      }
   3294 
   3295      eq(expected, api.nvim_list_uis())
   3296 
   3297      screen:detach()
   3298      screen = Screen.new(44, 99, { rgb = false }) -- luacheck: ignore
   3299      expected[1].rgb = false
   3300      expected[1].override = false
   3301      expected[1].width = 44
   3302      expected[1].height = 99
   3303      eq(expected, api.nvim_list_uis())
   3304    end)
   3305  end)
   3306 
   3307  describe('nvim_create_namespace', function()
   3308    it('works', function()
   3309      local orig = api.nvim_get_namespaces()
   3310      local base = vim.iter(orig):fold(0, function(acc, _, v)
   3311        return math.max(acc, v)
   3312      end)
   3313      eq(base + 1, api.nvim_create_namespace('ns-1'))
   3314      eq(base + 2, api.nvim_create_namespace('ns-2'))
   3315      eq(base + 1, api.nvim_create_namespace('ns-1'))
   3316 
   3317      local expected = vim.tbl_extend('error', orig, {
   3318        ['ns-1'] = base + 1,
   3319        ['ns-2'] = base + 2,
   3320      })
   3321 
   3322      eq(expected, api.nvim_get_namespaces())
   3323      eq(base + 3, api.nvim_create_namespace(''))
   3324      eq(base + 4, api.nvim_create_namespace(''))
   3325      eq(expected, api.nvim_get_namespaces())
   3326    end)
   3327  end)
   3328 
   3329  describe('nvim_create_buf', function()
   3330    it('works', function()
   3331      eq(2, api.nvim_create_buf(true, false))
   3332      eq(3, api.nvim_create_buf(false, false))
   3333      eq(
   3334        '  1 %a   "[No Name]"                    line 1\n'
   3335          .. '  2  h   "[No Name]"                    line 0',
   3336        command_output('ls')
   3337      )
   3338      -- current buffer didn't change
   3339      eq(1, api.nvim_get_current_buf())
   3340 
   3341      local screen = Screen.new(20, 4)
   3342      api.nvim_buf_set_lines(2, 0, -1, true, { 'some text' })
   3343      api.nvim_set_current_buf(2)
   3344      screen:expect(
   3345        [[
   3346        ^some text           |
   3347        {1:~                   }|*2
   3348                            |
   3349      ]],
   3350        {
   3351          [1] = { bold = true, foreground = Screen.colors.Blue1 },
   3352        }
   3353      )
   3354    end)
   3355 
   3356    it('can change buftype before visiting', function()
   3357      api.nvim_set_option_value('hidden', false, {})
   3358      eq(2, api.nvim_create_buf(true, false))
   3359      api.nvim_set_option_value('buftype', 'nofile', { buf = 2 })
   3360      api.nvim_buf_set_lines(2, 0, -1, true, { 'test text' })
   3361      command('split | buffer 2')
   3362      eq(2, api.nvim_get_current_buf())
   3363      -- if the buf_set_option("buftype") didn't work, this would error out.
   3364      command('close')
   3365      eq(1, api.nvim_get_current_buf())
   3366    end)
   3367 
   3368    it('does not trigger BufEnter, BufWinEnter', function()
   3369      command('let g:fired = v:false')
   3370      command('au BufEnter,BufWinEnter * let g:fired = v:true')
   3371 
   3372      eq(2, api.nvim_create_buf(true, false))
   3373      api.nvim_buf_set_lines(2, 0, -1, true, { 'test', 'text' })
   3374 
   3375      eq(false, eval('g:fired'))
   3376    end)
   3377 
   3378    it('TextChanged and TextChangedI do not trigger without changes', function()
   3379      local buf = api.nvim_create_buf(true, false)
   3380      command([[let g:changed = '']])
   3381      api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, {
   3382        buffer = buf,
   3383        command = 'let g:changed ..= mode()',
   3384      })
   3385      api.nvim_set_current_buf(buf)
   3386      feed('i')
   3387      eq('', api.nvim_get_var('changed'))
   3388    end)
   3389 
   3390    it('scratch-buffer', function()
   3391      eq(2, api.nvim_create_buf(false, true))
   3392      eq(3, api.nvim_create_buf(true, true))
   3393      eq(4, api.nvim_create_buf(true, true))
   3394      local scratch_bufs = { 2, 3, 4 }
   3395      eq(
   3396        '  1 %a   "[No Name]"                    line 1\n'
   3397          .. '  3  h   "[Scratch]"                    line 0\n'
   3398          .. '  4  h   "[Scratch]"                    line 0',
   3399        exec_capture('ls')
   3400      )
   3401      -- current buffer didn't change
   3402      eq(1, api.nvim_get_current_buf())
   3403 
   3404      local screen = Screen.new(20, 4)
   3405 
   3406      --
   3407      -- Editing a scratch-buffer does NOT change its properties.
   3408      --
   3409      local edited_buf = 2
   3410      api.nvim_buf_set_lines(edited_buf, 0, -1, true, { 'some text' })
   3411      for _, b in ipairs(scratch_bufs) do
   3412        eq('nofile', api.nvim_get_option_value('buftype', { buf = b }))
   3413        eq('hide', api.nvim_get_option_value('bufhidden', { buf = b }))
   3414        eq(false, api.nvim_get_option_value('swapfile', { buf = b }))
   3415        eq(false, api.nvim_get_option_value('modeline', { buf = b }))
   3416      end
   3417 
   3418      --
   3419      -- Visiting a scratch-buffer DOES NOT change its properties.
   3420      --
   3421      api.nvim_set_current_buf(edited_buf)
   3422      screen:expect([[
   3423        ^some text           |
   3424        {1:~                   }|*2
   3425                            |
   3426      ]])
   3427      eq('nofile', api.nvim_get_option_value('buftype', { buf = edited_buf }))
   3428      eq('hide', api.nvim_get_option_value('bufhidden', { buf = edited_buf }))
   3429      eq(false, api.nvim_get_option_value('swapfile', { buf = edited_buf }))
   3430      eq(false, api.nvim_get_option_value('modeline', { buf = edited_buf }))
   3431 
   3432      -- Scratch buffer can be wiped without error.
   3433      command('bwipe')
   3434      screen:expect([[
   3435        ^                    |
   3436        {1:~                   }|*2
   3437                            |
   3438      ]])
   3439    end)
   3440 
   3441    it('does not cause heap-use-after-free on exit while setting options', function()
   3442      command('au OptionSet * q')
   3443      command('silent! call nvim_create_buf(0, 1)')
   3444      -- nowadays this works because we don't execute any spurious autocmds at all #24824
   3445      assert_alive()
   3446    end)
   3447 
   3448    it('no memory leak when autocommands load the buffer immediately', function()
   3449      exec([[
   3450        autocmd BufNew * ++once call bufload(expand("<abuf>")->str2nr())
   3451                             \| let loaded = bufloaded(expand("<abuf>")->str2nr())
   3452      ]])
   3453      api.nvim_create_buf(false, true)
   3454      eq(1, eval('g:loaded'))
   3455    end)
   3456 
   3457    it('creating scratch buffer where autocommands set &swapfile works', function()
   3458      exec([[
   3459        autocmd BufNew * ++once execute expand("<abuf>") "buffer"
   3460                             \| file foobar
   3461                             \| setlocal swapfile
   3462      ]])
   3463      local new_buf = api.nvim_create_buf(false, true)
   3464      neq('', fn.swapname(new_buf))
   3465    end)
   3466 
   3467    it('fires expected autocommands', function()
   3468      exec([=[
   3469        " Append the &buftype to check autocommands trigger *after* the buffer was configured to be
   3470        " scratch, if applicable.
   3471        autocmd BufNew * let fired += [["BufNew", expand("<abuf>")->str2nr(),
   3472                                      \ getbufvar(expand("<abuf>")->str2nr(), "&buftype")]]
   3473        autocmd BufAdd * let fired += [["BufAdd", expand("<abuf>")->str2nr(),
   3474                                      \ getbufvar(expand("<abuf>")->str2nr(), "&buftype")]]
   3475 
   3476        " Don't want to see OptionSet; buffer options set from passing true for "scratch", etc.
   3477        " should be configured invisibly, and before autocommands.
   3478        autocmd OptionSet * let fired += [["OptionSet", expand("<amatch>")]]
   3479 
   3480        let fired = []
   3481      ]=])
   3482      local new_buf = api.nvim_create_buf(false, false)
   3483      eq({ { 'BufNew', new_buf, '' } }, eval('g:fired'))
   3484 
   3485      command('let fired = []')
   3486      new_buf = api.nvim_create_buf(false, true)
   3487      eq({ { 'BufNew', new_buf, 'nofile' } }, eval('g:fired'))
   3488 
   3489      command('let fired = []')
   3490      new_buf = api.nvim_create_buf(true, false)
   3491      eq({ { 'BufNew', new_buf, '' }, { 'BufAdd', new_buf, '' } }, eval('g:fired'))
   3492 
   3493      command('let fired = []')
   3494      new_buf = api.nvim_create_buf(true, true)
   3495      eq({ { 'BufNew', new_buf, 'nofile' }, { 'BufAdd', new_buf, 'nofile' } }, eval('g:fired'))
   3496    end)
   3497  end)
   3498 
   3499  describe('nvim_get_runtime_file', function()
   3500    local p = t.fix_slashes
   3501    it('can find files', function()
   3502      eq({}, api.nvim_get_runtime_file('bork.borkbork', false))
   3503      eq({}, api.nvim_get_runtime_file('bork.borkbork', true))
   3504      eq(1, #api.nvim_get_runtime_file('autoload/msgpack.vim', false))
   3505      eq(1, #api.nvim_get_runtime_file('autoload/msgpack.vim', true))
   3506      local val = api.nvim_get_runtime_file('autoload/remote/*.vim', true)
   3507      eq(2, #val)
   3508      if endswith(val[1], 'define.vim') then
   3509        ok(endswith(p(val[1]), 'autoload/remote/define.vim'))
   3510        ok(endswith(p(val[2]), 'autoload/remote/host.vim'))
   3511      else
   3512        ok(endswith(p(val[1]), 'autoload/remote/host.vim'))
   3513        ok(endswith(p(val[2]), 'autoload/remote/define.vim'))
   3514      end
   3515      val = api.nvim_get_runtime_file('autoload/remote/*.vim', false)
   3516      eq(1, #val)
   3517      ok(
   3518        endswith(p(val[1]), 'autoload/remote/define.vim')
   3519          or endswith(p(val[1]), 'autoload/remote/host.vim')
   3520      )
   3521 
   3522      val = api.nvim_get_runtime_file('lua', true)
   3523      eq(1, #val)
   3524      ok(endswith(p(val[1]), 'lua'))
   3525 
   3526      val = api.nvim_get_runtime_file('lua/vim', true)
   3527      eq(1, #val)
   3528      ok(endswith(p(val[1]), 'lua/vim'))
   3529    end)
   3530 
   3531    it('can find directories', function()
   3532      local val = api.nvim_get_runtime_file('lua/', true)
   3533      eq(1, #val)
   3534      ok(endswith(p(val[1]), 'lua/'))
   3535 
   3536      val = api.nvim_get_runtime_file('lua/vim/', true)
   3537      eq(1, #val)
   3538      ok(endswith(p(val[1]), 'lua/vim/'))
   3539 
   3540      eq({}, api.nvim_get_runtime_file('foobarlang/', true))
   3541    end)
   3542    it('can handle bad patterns', function()
   3543      skip(is_os('win'))
   3544 
   3545      eq('Vim:E220: Missing }.', pcall_err(api.nvim_get_runtime_file, '{', false))
   3546 
   3547      eq(
   3548        'Vim(echo):E5555: API call: Vim:E220: Missing }.',
   3549        exc_exec("echo nvim_get_runtime_file('{', v:false)")
   3550      )
   3551    end)
   3552    it('preserves order of runtimepath', function()
   3553      local vimruntime = fn.getenv('VIMRUNTIME')
   3554      local rtp = string.format('%s/syntax,%s/ftplugin', vimruntime, vimruntime)
   3555      api.nvim_set_option_value('runtimepath', rtp, {})
   3556 
   3557      local val = api.nvim_get_runtime_file('vim.vim', true)
   3558      eq(2, #val)
   3559      eq(p(val[1]), vimruntime .. '/syntax/vim.vim')
   3560      eq(p(val[2]), vimruntime .. '/ftplugin/vim.vim')
   3561    end)
   3562  end)
   3563 
   3564  describe('nvim_get_all_options_info', function()
   3565    it('should have key value pairs of option names', function()
   3566      local options_info = api.nvim_get_all_options_info()
   3567      neq(nil, options_info.listchars)
   3568      neq(nil, options_info.tabstop)
   3569 
   3570      eq(api.nvim_get_option_info 'winhighlight', options_info.winhighlight)
   3571    end)
   3572 
   3573    it('should not crash when echoed', function()
   3574      api.nvim_exec2('echo nvim_get_all_options_info()', { output = true })
   3575    end)
   3576  end)
   3577 
   3578  describe('nvim_get_option_info', function()
   3579    it('should error for unknown options', function()
   3580      eq("Invalid option (not found): 'bogus'", pcall_err(api.nvim_get_option_info, 'bogus'))
   3581    end)
   3582 
   3583    it('should return the same options for short and long name', function()
   3584      eq(api.nvim_get_option_info 'winhl', api.nvim_get_option_info 'winhighlight')
   3585    end)
   3586 
   3587    it('should have information about window options', function()
   3588      eq({
   3589        allows_duplicates = false,
   3590        commalist = true,
   3591        default = '',
   3592        flaglist = false,
   3593        global_local = false,
   3594        last_set_chan = 0,
   3595        last_set_linenr = 0,
   3596        last_set_sid = 0,
   3597        name = 'winhighlight',
   3598        scope = 'win',
   3599        shortname = 'winhl',
   3600        type = 'string',
   3601        was_set = false,
   3602      }, api.nvim_get_option_info 'winhl')
   3603    end)
   3604 
   3605    it('should have information about buffer options', function()
   3606      eq({
   3607        allows_duplicates = true,
   3608        commalist = false,
   3609        default = '',
   3610        flaglist = false,
   3611        global_local = false,
   3612        last_set_chan = 0,
   3613        last_set_linenr = 0,
   3614        last_set_sid = 0,
   3615        name = 'filetype',
   3616        scope = 'buf',
   3617        shortname = 'ft',
   3618        type = 'string',
   3619        was_set = false,
   3620      }, api.nvim_get_option_info 'filetype')
   3621    end)
   3622 
   3623    it('should have information about global options', function()
   3624      -- precondition: the option was changed from its default
   3625      -- in test setup.
   3626      eq(false, api.nvim_get_option_value('showcmd', {}))
   3627 
   3628      eq({
   3629        allows_duplicates = true,
   3630        commalist = false,
   3631        default = true,
   3632        flaglist = false,
   3633        global_local = false,
   3634        last_set_chan = 0,
   3635        last_set_linenr = 0,
   3636        last_set_sid = -2,
   3637        name = 'showcmd',
   3638        scope = 'global',
   3639        shortname = 'sc',
   3640        type = 'boolean',
   3641        was_set = true,
   3642      }, api.nvim_get_option_info 'showcmd')
   3643 
   3644      api.nvim_set_option_value('showcmd', true, {})
   3645 
   3646      eq({
   3647        allows_duplicates = true,
   3648        commalist = false,
   3649        default = true,
   3650        flaglist = false,
   3651        global_local = false,
   3652        last_set_chan = 1,
   3653        last_set_linenr = 0,
   3654        last_set_sid = -9,
   3655        name = 'showcmd',
   3656        scope = 'global',
   3657        shortname = 'sc',
   3658        type = 'boolean',
   3659        was_set = true,
   3660      }, api.nvim_get_option_info 'showcmd')
   3661    end)
   3662  end)
   3663 
   3664  describe('nvim_get_option_info2', function()
   3665    local fname
   3666    local bufs
   3667    local wins
   3668 
   3669    before_each(function()
   3670      fname = tmpname()
   3671      write_file(
   3672        fname,
   3673        [[
   3674        setglobal dictionary=mydict " 1, global-local (buffer)
   3675        setlocal  formatprg=myprg   " 2, global-local (buffer)
   3676        setglobal equalprg=prg1     " 3, global-local (buffer)
   3677        setlocal  equalprg=prg2     " 4, global-local (buffer)
   3678        setglobal fillchars=stl:x   " 5, global-local (window)
   3679        setlocal  listchars=eol:c   " 6, global-local (window)
   3680        setglobal showbreak=aaa     " 7, global-local (window)
   3681        setlocal  showbreak=bbb     " 8, global-local (window)
   3682        setglobal completeopt=menu  " 9, global
   3683      ]]
   3684      )
   3685 
   3686      exec_lua 'vim.cmd.vsplit()'
   3687      api.nvim_create_buf(false, false)
   3688 
   3689      bufs = api.nvim_list_bufs()
   3690      wins = api.nvim_list_wins()
   3691 
   3692      api.nvim_win_set_buf(wins[1], bufs[1])
   3693      api.nvim_win_set_buf(wins[2], bufs[2])
   3694 
   3695      api.nvim_set_current_win(wins[2])
   3696      api.nvim_exec('source ' .. fname, false)
   3697 
   3698      api.nvim_set_current_win(wins[1])
   3699    end)
   3700 
   3701    after_each(function()
   3702      os.remove(fname)
   3703    end)
   3704 
   3705    it('should return option information', function()
   3706      eq(api.nvim_get_option_info('dictionary'), api.nvim_get_option_info2('dictionary', {})) -- buffer
   3707      eq(api.nvim_get_option_info('fillchars'), api.nvim_get_option_info2('fillchars', {})) -- window
   3708      eq(api.nvim_get_option_info('completeopt'), api.nvim_get_option_info2('completeopt', {})) -- global
   3709    end)
   3710 
   3711    describe('last set', function()
   3712      -- stylua: ignore
   3713      local tests = {
   3714        {desc="(buf option, global requested, global set) points to global",   linenr=1, sid=1, args={'dictionary', {scope='global'}}},
   3715        {desc="(buf option, global requested, local set) is not set",          linenr=0, sid=0, args={'formatprg',  {scope='global'}}},
   3716        {desc="(buf option, global requested, both set) points to global",     linenr=3, sid=1, args={'equalprg',   {scope='global'}}},
   3717        {desc="(buf option, local requested, global set) is not set",          linenr=0, sid=0, args={'dictionary', {scope='local'}}},
   3718        {desc="(buf option, local requested, local set) points to local",      linenr=2, sid=1, args={'formatprg',  {scope='local'}}},
   3719        {desc="(buf option, local requested, both set) points to local",       linenr=4, sid=1, args={'equalprg',   {scope='local'}}},
   3720        {desc="(buf option, fallback requested, global set) points to global", linenr=1, sid=1, args={'dictionary', {}}},
   3721        {desc="(buf option, fallback requested, local set) points to local",   linenr=2, sid=1, args={'formatprg',  {}}},
   3722        {desc="(buf option, fallback requested, both set) points to local",    linenr=4, sid=1, args={'equalprg',   {}}},
   3723        {desc="(win option, global requested, global set) points to global",   linenr=5, sid=1, args={'fillchars', {scope='global'}}},
   3724        {desc="(win option, global requested, local set) is not set",          linenr=0, sid=0, args={'listchars', {scope='global'}}},
   3725        {desc="(win option, global requested, both set) points to global",     linenr=7, sid=1, args={'showbreak', {scope='global'}}},
   3726        {desc="(win option, local requested, global set) is not set",          linenr=0, sid=0, args={'fillchars', {scope='local'}}},
   3727        {desc="(win option, local requested, local set) points to local",      linenr=6, sid=1, args={'listchars', {scope='local'}}},
   3728        {desc="(win option, local requested, both set) points to local",       linenr=8, sid=1, args={'showbreak', {scope='local'}}},
   3729        {desc="(win option, fallback requested, global set) points to global", linenr=5, sid=1, args={'fillchars', {}}},
   3730        {desc="(win option, fallback requested, local set) points to local",   linenr=6, sid=1, args={'listchars', {}}},
   3731        {desc="(win option, fallback requested, both set) points to local",    linenr=8, sid=1, args={'showbreak', {}}},
   3732        {desc="(global option, global requested) points to global",            linenr=9, sid=1, args={'completeopt', {scope='global'}}},
   3733        {desc="(global option, local requested) is not set",                   linenr=0, sid=0, args={'completeopt', {scope='local'}}},
   3734        {desc="(global option, fallback requested) points to global",          linenr=9, sid=1, args={'completeopt', {}}},
   3735      }
   3736 
   3737      for _, test in pairs(tests) do
   3738        it(test.desc, function()
   3739          -- Switch to the target buffer/window so that curbuf/curwin are used.
   3740          api.nvim_set_current_win(wins[2])
   3741          local info = api.nvim_get_option_info2(unpack(test.args))
   3742          eq(test.linenr, info.last_set_linenr)
   3743          eq(test.sid, info.last_set_sid)
   3744        end)
   3745      end
   3746 
   3747      it('is provided for cross-buffer requests', function()
   3748        local info = api.nvim_get_option_info2('formatprg', { buf = bufs[2] })
   3749        eq(2, info.last_set_linenr)
   3750        eq(1, info.last_set_sid)
   3751      end)
   3752 
   3753      it('is provided for cross-window requests', function()
   3754        local info = api.nvim_get_option_info2('listchars', { win = wins[2] })
   3755        eq(6, info.last_set_linenr)
   3756        eq(1, info.last_set_sid)
   3757      end)
   3758    end)
   3759  end)
   3760 
   3761  describe('nvim_echo', function()
   3762    local screen
   3763 
   3764    before_each(function()
   3765      screen = Screen.new(40, 8)
   3766      command('highlight Statement gui=bold guifg=Brown')
   3767      command('highlight Special guifg=SlateBlue')
   3768    end)
   3769 
   3770    it('validation', function()
   3771      eq("Invalid 'chunk': expected Array, got String", pcall_err(api.nvim_echo, { 'msg' }, 1, {}))
   3772      eq(
   3773        'Invalid chunk: expected Array with 1 or 2 Strings',
   3774        pcall_err(api.nvim_echo, { { '', '', '' } }, 1, {})
   3775      )
   3776      eq('Invalid hl_group: text highlight', pcall_err(api.nvim_echo, { { '', false } }, 1, {}))
   3777    end)
   3778 
   3779    it('should clear cmdline message before echo', function()
   3780      feed(':call nvim_echo([["msg"]], v:false, {})<CR>')
   3781      screen:expect {
   3782        grid = [[
   3783        ^                                        |
   3784        {1:~                                       }|*6
   3785        msg                                     |
   3786      ]],
   3787      }
   3788    end)
   3789 
   3790    it('can show highlighted line', function()
   3791      async_meths.nvim_echo(
   3792        { { 'msg_a' }, { 'msg_b', 'Statement' }, { 'msg_c', 'Special' } },
   3793        true,
   3794        {}
   3795      )
   3796      screen:expect {
   3797        grid = [[
   3798        ^                                        |
   3799        {1:~                                       }|*6
   3800        msg_a{15:msg_b}{16:msg_c}                         |
   3801      ]],
   3802      }
   3803      async_meths.nvim_echo({
   3804        { 'msg_d' },
   3805        { 'msg_e', api.nvim_get_hl_id_by_name('Statement') },
   3806        { 'msg_f', api.nvim_get_hl_id_by_name('Special') },
   3807      }, true, {})
   3808      screen:expect {
   3809        grid = [[
   3810        ^                                        |
   3811        {1:~                                       }|*6
   3812        msg_d{15:msg_e}{16:msg_f}                         |
   3813      ]],
   3814      }
   3815    end)
   3816 
   3817    it('can show highlighted multiline', function()
   3818      async_meths.nvim_echo({ { 'msg_a\nmsg_a', 'Statement' }, { 'msg_b', 'Special' } }, true, {})
   3819      screen:expect {
   3820        grid = [[
   3821                                                |
   3822        {1:~                                       }|*3
   3823        {3:                                        }|
   3824        {15:msg_a}                                   |
   3825        {15:msg_a}{16:msg_b}                              |
   3826        {6:Press ENTER or type command to continue}^ |
   3827      ]],
   3828      }
   3829    end)
   3830 
   3831    it('can save message history', function()
   3832      command('set cmdheight=2') -- suppress Press ENTER
   3833      api.nvim_echo({ { 'msg\nmsg' }, { 'msg' } }, true, {})
   3834      eq('msg\nmsgmsg', exec_capture('messages'))
   3835    end)
   3836 
   3837    it('can disable saving message history', function()
   3838      command('set cmdheight=2') -- suppress Press ENTER
   3839      async_meths.nvim_echo({ { 'msg\nmsg' }, { 'msg' } }, false, {})
   3840      eq('', exec_capture('messages'))
   3841    end)
   3842 
   3843    it('can print error message', function()
   3844      async_meths.nvim_echo({ { 'Error\nMessage' } }, false, { err = true })
   3845      screen:expect([[
   3846                                                |
   3847        {1:~                                       }|*3
   3848        {3:                                        }|
   3849        {9:Error}                                   |
   3850        {9:Message}                                 |
   3851        {6:Press ENTER or type command to continue}^ |
   3852      ]])
   3853      feed(':messages<CR>')
   3854      screen:expect([[
   3855        ^                                        |
   3856        {1:~                                       }|*6
   3857                                                |
   3858      ]])
   3859      async_meths.nvim_echo({ { 'Error' }, { 'Message', 'Special' } }, false, { err = true })
   3860      screen:expect([[
   3861        ^                                        |
   3862        {1:~                                       }|*6
   3863        {9:Error}{16:Message}                            |
   3864      ]])
   3865    end)
   3866 
   3867    it('increments message ID', function()
   3868      eq(1, api.nvim_echo({ { 'foo' } }, false, {}))
   3869      eq(4, api.nvim_echo({ { 'foo' } }, false, { id = 4 }))
   3870      eq(5, api.nvim_echo({ { 'foo' } }, false, {}))
   3871    end)
   3872  end)
   3873 
   3874  describe('nvim_open_term', function()
   3875    local screen
   3876 
   3877    before_each(function()
   3878      screen = Screen.new(100, 35)
   3879      screen:add_extra_attr_ids {
   3880        [100] = { background = tonumber('0xffff40'), bg_indexed = true },
   3881        [101] = {
   3882          background = Screen.colors.LightMagenta,
   3883          foreground = tonumber('0x00e000'),
   3884          fg_indexed = true,
   3885        },
   3886        [102] = { background = Screen.colors.LightMagenta, reverse = true },
   3887        [103] = { background = Screen.colors.LightMagenta, bold = true, reverse = true },
   3888        [104] = { fg_indexed = true, foreground = tonumber('0xe00000') },
   3889        [105] = { fg_indexed = true, foreground = tonumber('0xe0e000') },
   3890      }
   3891    end)
   3892 
   3893    it('can batch process sequences', function()
   3894      local b = api.nvim_create_buf(true, true)
   3895      api.nvim_open_win(
   3896        b,
   3897        false,
   3898        { width = 79, height = 31, row = 1, col = 1, relative = 'editor' }
   3899      )
   3900      local term = api.nvim_open_term(b, {})
   3901 
   3902      api.nvim_chan_send(term, io.open('test/functional/fixtures/smile2.cat', 'r'):read('*a'))
   3903      screen:expect {
   3904        grid = [[
   3905        ^                                                                                                    |
   3906        {1:~}{4::smile                                                                         }{1:                    }|
   3907        {1:~}{4:                            }{100:oooo$$$$$$$$$$$$oooo}{4:                               }{1:                    }|
   3908        {1:~}{4:                        }{100:oo$$$$$$$$$$$$$$$$$$$$$$$$o}{4:                            }{1:                    }|
   3909        {1:~}{4:                     }{100:oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{4:         }{100:o$}{4:   }{100:$$}{4: }{100:o$}{4:      }{1:                    }|
   3910        {1:~}{4:     }{100:o}{4: }{100:$}{4: }{100:oo}{4:        }{100:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{4:       }{100:$$}{4: }{100:$$}{4: }{100:$$o$}{4:     }{1:                    }|
   3911        {1:~}{4:  }{100:oo}{4: }{100:$}{4: }{100:$}{4: "}{100:$}{4:      }{100:o$$$$$$$$$}{4:    }{100:$$$$$$$$$$$$$}{4:    }{100:$$$$$$$$$o}{4:       }{100:$$$o$$o$}{4:      }{1:                    }|
   3912        {1:~}{4:  "}{100:$$$$$$o$}{4:     }{100:o$$$$$$$$$}{4:      }{100:$$$$$$$$$$$}{4:      }{100:$$$$$$$$$$o}{4:    }{100:$$$$$$$$}{4:       }{1:                    }|
   3913        {1:~}{4:    }{100:$$$$$$$}{4:    }{100:$$$$$$$$$$$}{4:      }{100:$$$$$$$$$$$}{4:      }{100:$$$$$$$$$$$$$$$$$$$$$$$}{4:       }{1:                    }|
   3914        {1:~}{4:    }{100:$$$$$$$$$$$$$$$$$$$$$$$}{4:    }{100:$$$$$$$$$$$$$}{4:    }{100:$$$$$$$$$$$$$$}{4:  """}{100:$$$}{4:         }{1:                    }|
   3915        {1:~}{4:     "}{100:$$$}{4:""""}{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4:     "}{100:$$$}{4:        }{1:                    }|
   3916        {1:~}{4:      }{100:$$$}{4:   }{100:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4:     "}{100:$$$o}{4:      }{1:                    }|
   3917        {1:~}{4:     }{100:o$$}{4:"   }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4:       }{100:$$$o}{4:     }{1:                    }|
   3918        {1:~}{4:     }{100:$$$}{4:    }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4:" "}{100:$$$$$$ooooo$$$$o}{4:   }{1:                    }|
   3919        {1:~}{4:    }{100:o$$$oooo$$$$$}{4:  }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4:   }{100:o$$$$$$$$$$$$$$$$$}{4:  }{1:                    }|
   3920        {1:~}{4:    }{100:$$$$$$$$}{4:"}{100:$$$$}{4:   }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4:     }{100:$$$$}{4:""""""""        }{1:                    }|
   3921        {1:~}{4:   """"       }{100:$$$$}{4:    "}{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4:"      }{100:o$$$}{4:                 }{1:                    }|
   3922        {1:~}{4:              "}{100:$$$o}{4:     """}{100:$$$$$$$$$$$$$$$$$$}{4:"}{100:$$}{4:"         }{100:$$$}{4:                  }{1:                    }|
   3923        {1:~}{4:                }{100:$$$o}{4:          "}{100:$$}{4:""}{100:$$$$$$}{4:""""           }{100:o$$$}{4:                   }{1:                    }|
   3924        {1:~}{4:                 }{100:$$$$o}{4:                                }{100:o$$$}{4:"                    }{1:                    }|
   3925        {1:~}{4:                  "}{100:$$$$o}{4:      }{100:o$$$$$$o}{4:"}{100:$$$$o}{4:        }{100:o$$$$}{4:                      }{1:                    }|
   3926        {1:~}{4:                    "}{100:$$$$$oo}{4:     ""}{100:$$$$o$$$$$o}{4:   }{100:o$$$$}{4:""                       }{1:                    }|
   3927        {1:~}{4:                       ""}{100:$$$$$oooo}{4:  "}{100:$$$o$$$$$$$$$}{4:"""                          }{1:                    }|
   3928        {1:~}{4:                          ""}{100:$$$$$$$oo}{4: }{100:$$$$$$$$$$}{4:                               }{1:                    }|
   3929        {1:~}{4:                                  """"}{100:$$$$$$$$$$$}{4:                              }{1:                    }|
   3930        {1:~}{4:                                      }{100:$$$$$$$$$$$$}{4:                             }{1:                    }|
   3931        {1:~}{4:                                       }{100:$$$$$$$$$$}{4:"                             }{1:                    }|
   3932        {1:~}{4:                                        "}{100:$$$}{4:""""                               }{1:                    }|
   3933        {1:~}{4:                                                                               }{1:                    }|
   3934        {1:~}{101:Press ENTER or type command to continue}{4:                                        }{1:                    }|
   3935        {1:~}{103:term://~/config2/docs/pres//32693:vim --clean +smile         29,39          All}{1:                    }|
   3936        {1:~}{4::call nvim__screenshot("smile2.cat")                                           }{1:                    }|
   3937        {1:~                                                                                                   }|*2
   3938                                                                                                            |
   3939      ]],
   3940      }
   3941    end)
   3942 
   3943    it('can handle input', function()
   3944      screen:try_resize(50, 10)
   3945      eq(
   3946        { 3, 2 },
   3947        exec_lua [[
   3948        buf = vim.api.nvim_create_buf(1,1)
   3949 
   3950        stream = ''
   3951        do_the_echo = false
   3952        function input(_,t1,b1,data)
   3953          stream = stream .. data
   3954          _G.vals = {t1, b1}
   3955          if do_the_echo then
   3956            vim.api.nvim_chan_send(t1, data)
   3957          end
   3958        end
   3959 
   3960        term = vim.api.nvim_open_term(buf, {on_input=input})
   3961        vim.api.nvim_open_win(buf, true, {width=40, height=5, row=1, col=1, relative='editor'})
   3962        return {term, buf}
   3963      ]]
   3964      )
   3965 
   3966      screen:expect {
   3967        grid = [[
   3968                                                          |
   3969        {1:~}{4:^                                        }{1:         }|
   3970        {1:~}{4:                                        }{1:         }|*4
   3971        {1:~                                                 }|*3
   3972                                                          |
   3973      ]],
   3974      }
   3975 
   3976      feed 'iba<c-x>bla'
   3977      screen:expect {
   3978        grid = [[
   3979                                                          |
   3980        {1:~}{4:^                                        }{1:         }|
   3981        {1:~}{4:                                        }{1:         }|*4
   3982        {1:~                                                 }|*3
   3983        {5:-- TERMINAL --}                                    |
   3984      ]],
   3985      }
   3986 
   3987      eq('ba\024bla', exec_lua [[ return stream ]])
   3988      eq({ 3, 2 }, exec_lua [[ return vals ]])
   3989 
   3990      exec_lua [[ do_the_echo = true ]]
   3991      feed 'herrejösses!'
   3992 
   3993      screen:expect {
   3994        grid = [[
   3995                                                          |
   3996        {1:~}{4:herrejösses!^                            }{1:         }|
   3997        {1:~}{4:                                        }{1:         }|*4
   3998        {1:~                                                 }|*3
   3999        {5:-- TERMINAL --}                                    |
   4000      ]],
   4001      }
   4002      eq('ba\024blaherrejösses!', exec_lua [[ return stream ]])
   4003    end)
   4004 
   4005    it('parses text from the current buffer', function()
   4006      local b = api.nvim_create_buf(true, true)
   4007      api.nvim_buf_set_lines(b, 0, -1, true, { '\027[31mHello\000\027[0m', '\027[33mworld\027[0m' })
   4008      api.nvim_set_current_buf(b)
   4009      screen:expect([[
   4010        {18:^^[}[31mHello{18:^@^[}[0m                                                                                  |
   4011        {18:^[}[33mworld{18:^[}[0m                                                                                    |
   4012        {1:~                                                                                                   }|*32
   4013                                                                                                            |
   4014      ]])
   4015      api.nvim_open_term(b, {})
   4016      screen:expect([[
   4017        {104:^Hello}                                                                                               |
   4018        {105:world}                                                                                               |
   4019                                                                                                            |*33
   4020      ]])
   4021    end)
   4022  end)
   4023 
   4024  describe('nvim_del_mark', function()
   4025    it('works', function()
   4026      local buf = api.nvim_create_buf(false, true)
   4027      api.nvim_buf_set_lines(buf, -1, -1, true, { 'a', 'bit of', 'text' })
   4028      eq(true, api.nvim_buf_set_mark(buf, 'F', 2, 2, {}))
   4029      eq(true, api.nvim_del_mark('F'))
   4030      eq({ 0, 0 }, api.nvim_buf_get_mark(buf, 'F'))
   4031    end)
   4032    it('validation', function()
   4033      eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(api.nvim_del_mark, 'f'))
   4034      eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(api.nvim_del_mark, '!'))
   4035      eq("Invalid mark name (must be a single char): 'fail'", pcall_err(api.nvim_del_mark, 'fail'))
   4036    end)
   4037  end)
   4038  describe('nvim_get_mark', function()
   4039    it('works', function()
   4040      local buf = api.nvim_create_buf(false, true)
   4041      api.nvim_buf_set_lines(buf, -1, -1, true, { 'a', 'bit of', 'text' })
   4042      api.nvim_buf_set_mark(buf, 'F', 2, 2, {})
   4043      api.nvim_buf_set_name(buf, 'mybuf')
   4044      local mark = api.nvim_get_mark('F', {})
   4045      -- Compare the path tail only
   4046      matches('mybuf$', mark[4])
   4047      eq({ 2, 2, buf, mark[4] }, mark)
   4048    end)
   4049    it('validation', function()
   4050      eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(api.nvim_get_mark, 'f', {}))
   4051      eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(api.nvim_get_mark, '!', {}))
   4052      eq(
   4053        "Invalid mark name (must be a single char): 'fail'",
   4054        pcall_err(api.nvim_get_mark, 'fail', {})
   4055      )
   4056    end)
   4057    it('returns the expected when mark is not set', function()
   4058      eq(true, api.nvim_del_mark('A'))
   4059      eq({ 0, 0, 0, '' }, api.nvim_get_mark('A', {}))
   4060    end)
   4061    it('works with deleted buffers', function()
   4062      local fname = tmpname()
   4063      write_file(fname, 'a\nbit of\text')
   4064      command('edit ' .. fname)
   4065      local buf = api.nvim_get_current_buf()
   4066 
   4067      api.nvim_buf_set_mark(buf, 'F', 2, 2, {})
   4068      command('new') -- Create new buf to avoid :bd failing
   4069      command('bd! ' .. buf)
   4070      os.remove(fname)
   4071 
   4072      local mark = api.nvim_get_mark('F', {})
   4073      -- To avoid comparing relative vs absolute path
   4074      local mfname = mark[4]
   4075      local tail_patt = [[[\/][^\/]*$]]
   4076      -- tail of paths should be equals
   4077      eq(fname:match(tail_patt), mfname:match(tail_patt))
   4078      eq({ 2, 2, buf, mark[4] }, mark)
   4079    end)
   4080  end)
   4081 
   4082  describe('nvim_eval_statusline', function()
   4083    it('works', function()
   4084      eq({
   4085        str = '%StatusLineStringWithHighlights',
   4086        width = 31,
   4087      }, api.nvim_eval_statusline('%%StatusLineString%#WarningMsg#WithHighlights', {}))
   4088    end)
   4089 
   4090    it("doesn't exceed maxwidth", function()
   4091      eq({
   4092        str = 'Should be trun>',
   4093        width = 15,
   4094      }, api.nvim_eval_statusline('Should be truncated%<', { maxwidth = 15 }))
   4095    end)
   4096 
   4097    it('has correct default fillchar', function()
   4098      local oldwin = api.nvim_get_current_win()
   4099      command('set fillchars=stl:#,stlnc:$,wbr:%')
   4100      command('new')
   4101      eq({ str = 'a###b', width = 5 }, api.nvim_eval_statusline('a%=b', { maxwidth = 5 }))
   4102      eq(
   4103        { str = 'a$$$b', width = 5 },
   4104        api.nvim_eval_statusline('a%=b', { winid = oldwin, maxwidth = 5 })
   4105      )
   4106      eq(
   4107        { str = 'a%%%b', width = 5 },
   4108        api.nvim_eval_statusline('a%=b', { use_winbar = true, maxwidth = 5 })
   4109      )
   4110      eq(
   4111        { str = 'a   b', width = 5 },
   4112        api.nvim_eval_statusline('a%=b', { use_tabline = true, maxwidth = 5 })
   4113      )
   4114      eq(
   4115        { str = 'a   b', width = 5 },
   4116        api.nvim_eval_statusline('a%=b', { use_statuscol_lnum = 1, maxwidth = 5 })
   4117      )
   4118    end)
   4119 
   4120    for fc, desc in pairs({
   4121      ['~'] = 'supports ASCII fillchar',
   4122      ['━'] = 'supports single-width multibyte fillchar',
   4123      ['c̳'] = 'supports single-width fillchar with composing',
   4124      ['哦'] = 'treats double-width fillchar as single-width',
   4125      ['\031'] = 'treats control character fillchar as single-width',
   4126    }) do
   4127      it(desc, function()
   4128        eq(
   4129          { str = 'a' .. fc:rep(3) .. 'b', width = 5 },
   4130          api.nvim_eval_statusline('a%=b', { fillchar = fc, maxwidth = 5 })
   4131        )
   4132        eq(
   4133          { str = 'a' .. fc:rep(3) .. 'b', width = 5 },
   4134          api.nvim_eval_statusline('a%=b', { fillchar = fc, use_winbar = true, maxwidth = 5 })
   4135        )
   4136        eq(
   4137          { str = 'a' .. fc:rep(3) .. 'b', width = 5 },
   4138          api.nvim_eval_statusline('a%=b', { fillchar = fc, use_tabline = true, maxwidth = 5 })
   4139        )
   4140        eq(
   4141          { str = 'a' .. fc:rep(3) .. 'b', width = 5 },
   4142          api.nvim_eval_statusline('a%=b', { fillchar = fc, use_statuscol_lnum = 1, maxwidth = 5 })
   4143        )
   4144      end)
   4145    end
   4146 
   4147    it('rejects multiple-character fillchar', function()
   4148      eq(
   4149        "Invalid 'fillchar': expected single character",
   4150        pcall_err(api.nvim_eval_statusline, '', { fillchar = 'aa' })
   4151      )
   4152    end)
   4153 
   4154    it('rejects empty string fillchar', function()
   4155      eq(
   4156        "Invalid 'fillchar': expected single character",
   4157        pcall_err(api.nvim_eval_statusline, '', { fillchar = '' })
   4158      )
   4159    end)
   4160 
   4161    it('rejects non-string fillchar', function()
   4162      eq(
   4163        "Invalid 'fillchar': expected String, got Integer",
   4164        pcall_err(api.nvim_eval_statusline, '', { fillchar = 1 })
   4165      )
   4166    end)
   4167 
   4168    it('rejects invalid string', function()
   4169      eq('E539: Illegal character <}>', pcall_err(api.nvim_eval_statusline, '%{%}', {}))
   4170    end)
   4171 
   4172    it('supports various items', function()
   4173      eq({ str = '0', width = 1 }, api.nvim_eval_statusline('%l', { maxwidth = 5 }))
   4174      command('set readonly')
   4175      eq({ str = '[RO]', width = 4 }, api.nvim_eval_statusline('%r', { maxwidth = 5 }))
   4176      local screen = Screen.new(80, 24)
   4177      command('set showcmd')
   4178      feed('1234')
   4179      screen:expect({ any = '1234' })
   4180      eq({ str = '1234', width = 4 }, api.nvim_eval_statusline('%S', { maxwidth = 5 }))
   4181      feed('56')
   4182      screen:expect({ any = '123456' })
   4183      eq({ str = '<3456', width = 5 }, api.nvim_eval_statusline('%S', { maxwidth = 5 }))
   4184    end)
   4185 
   4186    describe('highlight parsing', function()
   4187      it('works', function()
   4188        eq(
   4189          {
   4190            str = 'TextWithWarningHighlightTextWithUserHighlight',
   4191            width = 45,
   4192            highlights = {
   4193              { start = 0, group = 'WarningMsg', groups = { 'StatusLine', 'WarningMsg' } },
   4194              { start = 24, group = 'User1', groups = { 'StatusLine', 'User1' } },
   4195            },
   4196          },
   4197          api.nvim_eval_statusline(
   4198            '%#WarningMsg#TextWithWarningHighlight%1*TextWithUserHighlight',
   4199            { highlights = true }
   4200          )
   4201        )
   4202      end)
   4203 
   4204      it('works with no highlight', function()
   4205        eq({
   4206          str = 'TextWithNoHighlight',
   4207          width = 19,
   4208          highlights = {
   4209            { start = 0, group = 'StatusLine', groups = { 'StatusLine' } },
   4210          },
   4211        }, api.nvim_eval_statusline('TextWithNoHighlight', { highlights = true }))
   4212      end)
   4213 
   4214      it('works with inactive statusline', function()
   4215        command('split')
   4216        eq(
   4217          {
   4218            str = 'TextWithNoHighlightTextWithWarningHighlight',
   4219            width = 43,
   4220            highlights = {
   4221              { start = 0, group = 'StatusLineNC', groups = { 'StatusLineNC' } },
   4222              { start = 19, group = 'WarningMsg', groups = { 'StatusLineNC', 'WarningMsg' } },
   4223            },
   4224          },
   4225          api.nvim_eval_statusline(
   4226            'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight',
   4227            { winid = api.nvim_list_wins()[2], highlights = true }
   4228          )
   4229        )
   4230      end)
   4231 
   4232      it('works with tabline', function()
   4233        eq(
   4234          {
   4235            str = 'TextWithNoHighlightTextWithWarningHighlight',
   4236            width = 43,
   4237            highlights = {
   4238              { start = 0, group = 'TabLineFill', groups = { 'TabLineFill' } },
   4239              { start = 19, group = 'WarningMsg', groups = { 'TabLineFill', 'WarningMsg' } },
   4240            },
   4241          },
   4242          api.nvim_eval_statusline(
   4243            'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight',
   4244            { use_tabline = true, highlights = true }
   4245          )
   4246        )
   4247      end)
   4248 
   4249      it('works with winbar', function()
   4250        eq(
   4251          {
   4252            str = 'TextWithNoHighlightTextWithWarningHighlight',
   4253            width = 43,
   4254            highlights = {
   4255              { start = 0, group = 'WinBar', groups = { 'WinBar' } },
   4256              { start = 19, group = 'WarningMsg', groups = { 'WinBar', 'WarningMsg' } },
   4257            },
   4258          },
   4259          api.nvim_eval_statusline(
   4260            'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight',
   4261            { use_winbar = true, highlights = true }
   4262          )
   4263        )
   4264      end)
   4265 
   4266      it('works with statuscolumn', function()
   4267        exec([[
   4268          let &stc='%C%s%=%l '
   4269          " should not use "stl" from 'fillchars'
   4270          set cul nu nuw=3 scl=yes:2 fdc=2 fillchars=stl:#
   4271          call setline(1, repeat(['aaaaa'], 5))
   4272          let g:ns = nvim_create_namespace('')
   4273          call sign_define('a', {'text':'aa', 'texthl':'IncSearch', 'numhl':'Normal'})
   4274          call sign_place(2, 1, 'a', bufnr(), {'lnum':4})
   4275          call nvim_buf_set_extmark(0, g:ns, 3, 1, { 'sign_text':'bb', 'sign_hl_group':'ErrorMsg' })
   4276          1,5fold | 1,5 fold | foldopen!
   4277          norm 4G
   4278        ]])
   4279        eq({
   4280          str = '││bbaa 4 ',
   4281          width = 9,
   4282          highlights = {
   4283            { group = 'CursorLineFold', start = 0, groups = { 'CursorLineFold' } },
   4284            { group = 'Normal', start = 6, groups = { 'Normal' } },
   4285            { group = 'ErrorMsg', start = 6, groups = { 'CursorLineSign', 'ErrorMsg' } },
   4286            { group = 'IncSearch', start = 8, groups = { 'CursorLineSign', 'IncSearch' } },
   4287            { group = 'Normal', start = 10, groups = { 'Normal' } },
   4288          },
   4289        }, api.nvim_eval_statusline(
   4290          '%C%s%=%l ',
   4291          { use_statuscol_lnum = 4, highlights = true }
   4292        ))
   4293        eq(
   4294          {
   4295            str = '       3 ',
   4296            width = 9,
   4297            highlights = {
   4298              { group = 'LineNr', start = 0, groups = { 'LineNr' } },
   4299              { group = 'ErrorMsg', start = 8, groups = { 'LineNr', 'ErrorMsg' } },
   4300            },
   4301          },
   4302          api.nvim_eval_statusline('%l%#ErrorMsg# ', { use_statuscol_lnum = 3, highlights = true })
   4303        )
   4304      end)
   4305 
   4306      it('no memory leak with click functions', function()
   4307        api.nvim_eval_statusline('%@ClickFunc@StatusLineStringWithClickFunc%T', {})
   4308        eq({
   4309          str = 'StatusLineStringWithClickFunc',
   4310          width = 29,
   4311        }, api.nvim_eval_statusline('%@ClickFunc@StatusLineStringWithClickFunc%T', {}))
   4312      end)
   4313    end)
   4314  end)
   4315 
   4316  describe('nvim_parse_cmd', function()
   4317    it('works', function()
   4318      eq({
   4319        cmd = 'echo',
   4320        args = { 'foo' },
   4321        bang = false,
   4322        addr = 'none',
   4323        magic = {
   4324          file = false,
   4325          bar = false,
   4326        },
   4327        nargs = '*',
   4328        nextcmd = '',
   4329        mods = {
   4330          browse = false,
   4331          confirm = false,
   4332          emsg_silent = false,
   4333          filter = {
   4334            pattern = '',
   4335            force = false,
   4336          },
   4337          hide = false,
   4338          horizontal = false,
   4339          keepalt = false,
   4340          keepjumps = false,
   4341          keepmarks = false,
   4342          keeppatterns = false,
   4343          lockmarks = false,
   4344          noautocmd = false,
   4345          noswapfile = false,
   4346          sandbox = false,
   4347          silent = false,
   4348          split = '',
   4349          tab = -1,
   4350          unsilent = false,
   4351          verbose = -1,
   4352          vertical = false,
   4353        },
   4354      }, api.nvim_parse_cmd('echo foo', {}))
   4355    end)
   4356    it('works with ranges', function()
   4357      eq({
   4358        cmd = 'substitute',
   4359        args = { '/math.random/math.max/' },
   4360        bang = false,
   4361        range = { 4, 6 },
   4362        addr = 'line',
   4363        magic = {
   4364          file = false,
   4365          bar = false,
   4366        },
   4367        nargs = '*',
   4368        nextcmd = '',
   4369        mods = {
   4370          browse = false,
   4371          confirm = false,
   4372          emsg_silent = false,
   4373          filter = {
   4374            pattern = '',
   4375            force = false,
   4376          },
   4377          hide = false,
   4378          horizontal = false,
   4379          keepalt = false,
   4380          keepjumps = false,
   4381          keepmarks = false,
   4382          keeppatterns = false,
   4383          lockmarks = false,
   4384          noautocmd = false,
   4385          noswapfile = false,
   4386          sandbox = false,
   4387          silent = false,
   4388          split = '',
   4389          tab = -1,
   4390          unsilent = false,
   4391          verbose = -1,
   4392          vertical = false,
   4393        },
   4394      }, api.nvim_parse_cmd('4,6s/math.random/math.max/', {}))
   4395    end)
   4396    it('works with count', function()
   4397      eq({
   4398        cmd = 'buffer',
   4399        args = {},
   4400        bang = false,
   4401        range = { 1 },
   4402        count = 1,
   4403        addr = 'buf',
   4404        magic = {
   4405          file = false,
   4406          bar = true,
   4407        },
   4408        nargs = '*',
   4409        nextcmd = '',
   4410        mods = {
   4411          browse = false,
   4412          confirm = false,
   4413          emsg_silent = false,
   4414          filter = {
   4415            pattern = '',
   4416            force = false,
   4417          },
   4418          hide = false,
   4419          horizontal = false,
   4420          keepalt = false,
   4421          keepjumps = false,
   4422          keepmarks = false,
   4423          keeppatterns = false,
   4424          lockmarks = false,
   4425          noautocmd = false,
   4426          noswapfile = false,
   4427          sandbox = false,
   4428          silent = false,
   4429          split = '',
   4430          tab = -1,
   4431          unsilent = false,
   4432          verbose = -1,
   4433          vertical = false,
   4434        },
   4435      }, api.nvim_parse_cmd('buffer 1', {}))
   4436    end)
   4437    it('works with register', function()
   4438      eq({
   4439        cmd = 'put',
   4440        args = {},
   4441        bang = false,
   4442        reg = '+',
   4443        addr = 'line',
   4444        magic = {
   4445          file = false,
   4446          bar = true,
   4447        },
   4448        nargs = '0',
   4449        nextcmd = '',
   4450        mods = {
   4451          browse = false,
   4452          confirm = false,
   4453          emsg_silent = false,
   4454          filter = {
   4455            pattern = '',
   4456            force = false,
   4457          },
   4458          hide = false,
   4459          horizontal = false,
   4460          keepalt = false,
   4461          keepjumps = false,
   4462          keepmarks = false,
   4463          keeppatterns = false,
   4464          lockmarks = false,
   4465          noautocmd = false,
   4466          noswapfile = false,
   4467          sandbox = false,
   4468          silent = false,
   4469          split = '',
   4470          tab = -1,
   4471          unsilent = false,
   4472          verbose = -1,
   4473          vertical = false,
   4474        },
   4475      }, api.nvim_parse_cmd('put +', {}))
   4476      eq({
   4477        cmd = 'put',
   4478        args = {},
   4479        bang = false,
   4480        reg = '',
   4481        addr = 'line',
   4482        magic = {
   4483          file = false,
   4484          bar = true,
   4485        },
   4486        nargs = '0',
   4487        nextcmd = '',
   4488        mods = {
   4489          browse = false,
   4490          confirm = false,
   4491          emsg_silent = false,
   4492          filter = {
   4493            pattern = '',
   4494            force = false,
   4495          },
   4496          hide = false,
   4497          horizontal = false,
   4498          keepalt = false,
   4499          keepjumps = false,
   4500          keepmarks = false,
   4501          keeppatterns = false,
   4502          lockmarks = false,
   4503          noautocmd = false,
   4504          noswapfile = false,
   4505          sandbox = false,
   4506          silent = false,
   4507          split = '',
   4508          tab = -1,
   4509          unsilent = false,
   4510          verbose = -1,
   4511          vertical = false,
   4512        },
   4513      }, api.nvim_parse_cmd('put', {}))
   4514    end)
   4515    it('works with range, count and register', function()
   4516      eq({
   4517        cmd = 'delete',
   4518        args = {},
   4519        bang = false,
   4520        range = { 3, 7 },
   4521        count = 7,
   4522        reg = '*',
   4523        addr = 'line',
   4524        magic = {
   4525          file = false,
   4526          bar = true,
   4527        },
   4528        nargs = '0',
   4529        nextcmd = '',
   4530        mods = {
   4531          browse = false,
   4532          confirm = false,
   4533          emsg_silent = false,
   4534          filter = {
   4535            pattern = '',
   4536            force = false,
   4537          },
   4538          hide = false,
   4539          horizontal = false,
   4540          keepalt = false,
   4541          keepjumps = false,
   4542          keepmarks = false,
   4543          keeppatterns = false,
   4544          lockmarks = false,
   4545          noautocmd = false,
   4546          noswapfile = false,
   4547          sandbox = false,
   4548          silent = false,
   4549          split = '',
   4550          tab = -1,
   4551          unsilent = false,
   4552          verbose = -1,
   4553          vertical = false,
   4554        },
   4555      }, api.nvim_parse_cmd('1,3delete * 5', {}))
   4556    end)
   4557    it('works with bang', function()
   4558      eq({
   4559        cmd = 'write',
   4560        args = {},
   4561        bang = true,
   4562        addr = 'line',
   4563        magic = {
   4564          file = true,
   4565          bar = true,
   4566        },
   4567        nargs = '?',
   4568        nextcmd = '',
   4569        mods = {
   4570          browse = false,
   4571          confirm = false,
   4572          emsg_silent = false,
   4573          filter = {
   4574            pattern = '',
   4575            force = false,
   4576          },
   4577          hide = false,
   4578          horizontal = false,
   4579          keepalt = false,
   4580          keepjumps = false,
   4581          keepmarks = false,
   4582          keeppatterns = false,
   4583          lockmarks = false,
   4584          noautocmd = false,
   4585          noswapfile = false,
   4586          sandbox = false,
   4587          silent = false,
   4588          split = '',
   4589          tab = -1,
   4590          unsilent = false,
   4591          verbose = -1,
   4592          vertical = false,
   4593        },
   4594      }, api.nvim_parse_cmd('w!', {}))
   4595    end)
   4596    it('works with modifiers', function()
   4597      eq(
   4598        {
   4599          cmd = 'split',
   4600          args = { 'foo.txt' },
   4601          bang = false,
   4602          addr = '?',
   4603          magic = {
   4604            file = true,
   4605            bar = true,
   4606          },
   4607          nargs = '?',
   4608          nextcmd = '',
   4609          mods = {
   4610            browse = false,
   4611            confirm = false,
   4612            emsg_silent = true,
   4613            filter = {
   4614              pattern = 'foo',
   4615              force = false,
   4616            },
   4617            hide = false,
   4618            horizontal = true,
   4619            keepalt = false,
   4620            keepjumps = false,
   4621            keepmarks = false,
   4622            keeppatterns = false,
   4623            lockmarks = false,
   4624            noautocmd = false,
   4625            noswapfile = false,
   4626            sandbox = false,
   4627            silent = true,
   4628            split = 'topleft',
   4629            tab = 1,
   4630            unsilent = false,
   4631            verbose = 15,
   4632            vertical = false,
   4633          },
   4634        },
   4635        api.nvim_parse_cmd(
   4636          '15verbose silent! horizontal topleft tab filter /foo/ split foo.txt',
   4637          {}
   4638        )
   4639      )
   4640      eq(
   4641        {
   4642          cmd = 'split',
   4643          args = { 'foo.txt' },
   4644          bang = false,
   4645          addr = '?',
   4646          magic = {
   4647            file = true,
   4648            bar = true,
   4649          },
   4650          nargs = '?',
   4651          nextcmd = '',
   4652          mods = {
   4653            browse = false,
   4654            confirm = true,
   4655            emsg_silent = false,
   4656            filter = {
   4657              pattern = 'foo',
   4658              force = true,
   4659            },
   4660            hide = false,
   4661            horizontal = false,
   4662            keepalt = false,
   4663            keepjumps = false,
   4664            keepmarks = false,
   4665            keeppatterns = false,
   4666            lockmarks = false,
   4667            noautocmd = false,
   4668            noswapfile = false,
   4669            sandbox = false,
   4670            silent = false,
   4671            split = 'botright',
   4672            tab = 0,
   4673            unsilent = true,
   4674            verbose = 0,
   4675            vertical = false,
   4676          },
   4677        },
   4678        api.nvim_parse_cmd(
   4679          '0verbose unsilent botright 0tab confirm filter! /foo/ split foo.txt',
   4680          {}
   4681        )
   4682      )
   4683    end)
   4684    it('works with user commands', function()
   4685      command('command -bang -nargs=+ -range -addr=lines MyCommand echo foo')
   4686      eq({
   4687        cmd = 'MyCommand',
   4688        args = { 'test', 'it' },
   4689        bang = true,
   4690        range = { 4, 6 },
   4691        addr = 'line',
   4692        magic = {
   4693          file = false,
   4694          bar = false,
   4695        },
   4696        nargs = '+',
   4697        nextcmd = '',
   4698        mods = {
   4699          browse = false,
   4700          confirm = false,
   4701          emsg_silent = false,
   4702          filter = {
   4703            pattern = '',
   4704            force = false,
   4705          },
   4706          hide = false,
   4707          horizontal = false,
   4708          keepalt = false,
   4709          keepjumps = false,
   4710          keepmarks = false,
   4711          keeppatterns = false,
   4712          lockmarks = false,
   4713          noautocmd = false,
   4714          noswapfile = false,
   4715          sandbox = false,
   4716          silent = false,
   4717          split = '',
   4718          tab = -1,
   4719          unsilent = false,
   4720          verbose = -1,
   4721          vertical = false,
   4722        },
   4723      }, api.nvim_parse_cmd('4,6MyCommand! test it', {}))
   4724    end)
   4725    it('sets nextcmd for bar-separated commands', function()
   4726      eq({
   4727        cmd = 'argadd',
   4728        args = { 'a.txt' },
   4729        bang = false,
   4730        addr = 'arg',
   4731        magic = {
   4732          file = true,
   4733          bar = true,
   4734        },
   4735        nargs = '*',
   4736        nextcmd = 'argadd b.txt',
   4737        mods = {
   4738          browse = false,
   4739          confirm = false,
   4740          emsg_silent = false,
   4741          filter = {
   4742            pattern = '',
   4743            force = false,
   4744          },
   4745          hide = false,
   4746          horizontal = false,
   4747          keepalt = false,
   4748          keepjumps = false,
   4749          keepmarks = false,
   4750          keeppatterns = false,
   4751          lockmarks = false,
   4752          noautocmd = false,
   4753          noswapfile = false,
   4754          sandbox = false,
   4755          silent = false,
   4756          split = '',
   4757          tab = -1,
   4758          unsilent = false,
   4759          verbose = -1,
   4760          vertical = false,
   4761        },
   4762      }, api.nvim_parse_cmd('argadd a.txt | argadd b.txt', {}))
   4763    end)
   4764    it('sets nextcmd after expr-arg commands #36029', function()
   4765      local result = api.nvim_parse_cmd('exe "ls"|edit foo', {})
   4766      eq({ '"ls"' }, result.args)
   4767      eq('execute', result.cmd)
   4768      eq('edit foo', result.nextcmd)
   4769    end)
   4770    it('parses :map commands with space in RHS', function()
   4771      eq({
   4772        addr = 'none',
   4773        args = { 'a', 'b  c' },
   4774        bang = false,
   4775        cmd = 'map',
   4776        magic = {
   4777          bar = true,
   4778          file = false,
   4779        },
   4780        mods = {
   4781          browse = false,
   4782          confirm = false,
   4783          emsg_silent = false,
   4784          filter = {
   4785            force = false,
   4786            pattern = '',
   4787          },
   4788          hide = false,
   4789          horizontal = false,
   4790          keepalt = false,
   4791          keepjumps = false,
   4792          keepmarks = false,
   4793          keeppatterns = false,
   4794          lockmarks = false,
   4795          noautocmd = false,
   4796          noswapfile = false,
   4797          sandbox = false,
   4798          silent = false,
   4799          split = '',
   4800          tab = -1,
   4801          unsilent = false,
   4802          verbose = -1,
   4803          vertical = false,
   4804        },
   4805        nargs = '*',
   4806        nextcmd = '',
   4807      }, api.nvim_parse_cmd('map a b  c', {}))
   4808    end)
   4809    it('works for nargs=1', function()
   4810      command('command -nargs=1 MyCommand echo <q-args>')
   4811      eq({
   4812        cmd = 'MyCommand',
   4813        args = { 'test it' },
   4814        bang = false,
   4815        addr = 'none',
   4816        magic = {
   4817          file = false,
   4818          bar = false,
   4819        },
   4820        nargs = '1',
   4821        nextcmd = '',
   4822        mods = {
   4823          browse = false,
   4824          confirm = false,
   4825          emsg_silent = false,
   4826          filter = {
   4827            pattern = '',
   4828            force = false,
   4829          },
   4830          hide = false,
   4831          horizontal = false,
   4832          keepalt = false,
   4833          keepjumps = false,
   4834          keepmarks = false,
   4835          keeppatterns = false,
   4836          lockmarks = false,
   4837          noautocmd = false,
   4838          noswapfile = false,
   4839          sandbox = false,
   4840          silent = false,
   4841          split = '',
   4842          tab = -1,
   4843          unsilent = false,
   4844          verbose = -1,
   4845          vertical = false,
   4846        },
   4847      }, api.nvim_parse_cmd('MyCommand test it', {}))
   4848    end)
   4849    it('validates command', function()
   4850      eq('Parsing command-line', pcall_err(api.nvim_parse_cmd, '', {}))
   4851      eq('Parsing command-line', pcall_err(api.nvim_parse_cmd, '" foo', {}))
   4852      eq(
   4853        'Parsing command-line: E492: Not an editor command: Fubar',
   4854        pcall_err(api.nvim_parse_cmd, 'Fubar', {})
   4855      )
   4856      command('command! Fubar echo foo')
   4857      eq('Parsing command-line: E477: No ! allowed', pcall_err(api.nvim_parse_cmd, 'Fubar!', {}))
   4858      eq(
   4859        'Parsing command-line: E481: No range allowed',
   4860        pcall_err(api.nvim_parse_cmd, '4,6Fubar', {})
   4861      )
   4862      command('command! Foobar echo foo')
   4863      eq(
   4864        'Parsing command-line: E464: Ambiguous use of user-defined command',
   4865        pcall_err(api.nvim_parse_cmd, 'F', {})
   4866      )
   4867    end)
   4868    it('does not interfere with printing line in Ex mode #19400', function()
   4869      local screen = Screen.new(60, 7)
   4870      insert([[
   4871        foo
   4872        bar]])
   4873      feed('gQ1')
   4874      screen:expect([[
   4875        foo                                                         |
   4876        bar                                                         |
   4877        {1:~                                                           }|*2
   4878        {3:                                                            }|
   4879        Entering Ex mode.  Type "visual" to go to Normal mode.      |
   4880        :1^                                                          |
   4881      ]])
   4882      eq('Parsing command-line', pcall_err(api.nvim_parse_cmd, '', {}))
   4883      feed('<CR>')
   4884      screen:expect([[
   4885        foo                                                         |
   4886        bar                                                         |
   4887        {3:                                                            }|
   4888        Entering Ex mode.  Type "visual" to go to Normal mode.      |
   4889        :1                                                          |
   4890        foo                                                         |
   4891        :^                                                           |
   4892      ]])
   4893    end)
   4894    it('does not move cursor or change search history/pattern #19878 #19890', function()
   4895      api.nvim_buf_set_lines(0, 0, -1, true, { 'foo', 'bar', 'foo', 'bar' })
   4896      eq({ 1, 0 }, api.nvim_win_get_cursor(0))
   4897      eq('', fn.getreg('/'))
   4898      eq('', fn.histget('search'))
   4899      feed(':') -- call the API in cmdline mode to test whether it changes search history
   4900      eq({
   4901        cmd = 'normal',
   4902        args = { 'x' },
   4903        bang = true,
   4904        range = { 3, 4 },
   4905        addr = 'line',
   4906        magic = {
   4907          file = false,
   4908          bar = false,
   4909        },
   4910        nargs = '+',
   4911        nextcmd = '',
   4912        mods = {
   4913          browse = false,
   4914          confirm = false,
   4915          emsg_silent = false,
   4916          filter = {
   4917            pattern = '',
   4918            force = false,
   4919          },
   4920          hide = false,
   4921          horizontal = false,
   4922          keepalt = false,
   4923          keepjumps = false,
   4924          keepmarks = false,
   4925          keeppatterns = false,
   4926          lockmarks = false,
   4927          noautocmd = false,
   4928          noswapfile = false,
   4929          sandbox = false,
   4930          silent = false,
   4931          split = '',
   4932          tab = -1,
   4933          unsilent = false,
   4934          verbose = -1,
   4935          vertical = false,
   4936        },
   4937      }, api.nvim_parse_cmd('+2;/bar/normal! x', {}))
   4938      eq({ 1, 0 }, api.nvim_win_get_cursor(0))
   4939      eq('', fn.getreg('/'))
   4940      eq('', fn.histget('search'))
   4941    end)
   4942    it('result can be used directly by nvim_cmd #20051', function()
   4943      eq('foo', api.nvim_cmd(api.nvim_parse_cmd('echo "foo"', {}), { output = true }))
   4944      api.nvim_cmd(api.nvim_parse_cmd('set cursorline', {}), {})
   4945      eq(true, api.nvim_get_option_value('cursorline', {}))
   4946      -- Roundtrip on :bdelete which does not accept "range". #33394
   4947      api.nvim_cmd(api.nvim_parse_cmd('bdelete', {}), {})
   4948    end)
   4949    it('no side-effects (error messages) in pcall() #20339', function()
   4950      eq(
   4951        { false, 'Parsing command-line: E16: Invalid range' },
   4952        exec_lua([=[return {pcall(vim.api.nvim_parse_cmd, "'<,'>n", {})}]=])
   4953      )
   4954      eq('', eval('v:errmsg'))
   4955    end)
   4956    it('does not include count field when no count provided for builtin commands', function()
   4957      local result = api.nvim_parse_cmd('copen', {})
   4958      eq(nil, result.count)
   4959      api.nvim_cmd(result, {})
   4960      eq(10, api.nvim_win_get_height(0))
   4961      result = api.nvim_parse_cmd('copen 5', {})
   4962      eq(5, result.count)
   4963    end)
   4964  end)
   4965 
   4966  describe('nvim_cmd', function()
   4967    it('works', function()
   4968      api.nvim_cmd({ cmd = 'set', args = { 'cursorline' } }, {})
   4969      eq(true, api.nvim_get_option_value('cursorline', {}))
   4970    end)
   4971 
   4972    it('validation', function()
   4973      eq("Invalid 'cmd': expected non-empty String", pcall_err(api.nvim_cmd, { cmd = '' }, {}))
   4974      eq("Invalid 'cmd': expected String, got Array", pcall_err(api.nvim_cmd, { cmd = {} }, {}))
   4975      eq(
   4976        "Invalid 'args': expected Array, got Boolean",
   4977        pcall_err(api.nvim_cmd, { cmd = 'set', args = true }, {})
   4978      )
   4979      eq(
   4980        'Invalid command arg: expected non-whitespace',
   4981        pcall_err(api.nvim_cmd, { cmd = 'set', args = { '  ' } }, {})
   4982      )
   4983      eq(
   4984        'Invalid command arg: expected valid type, got Array',
   4985        pcall_err(api.nvim_cmd, { cmd = 'set', args = { {} } }, {})
   4986      )
   4987      eq('Wrong number of arguments', pcall_err(api.nvim_cmd, { cmd = 'aboveleft', args = {} }, {}))
   4988      eq(
   4989        'Command cannot accept bang: print',
   4990        pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, bang = true }, {})
   4991      )
   4992 
   4993      eq(
   4994        'Command cannot accept range: set',
   4995        pcall_err(api.nvim_cmd, { cmd = 'set', args = {}, range = { 1 } }, {})
   4996      )
   4997      eq(
   4998        "Invalid 'range': expected Array, got Boolean",
   4999        pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, range = true }, {})
   5000      )
   5001      eq(
   5002        "Invalid 'range': expected <=2 elements",
   5003        pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, range = { 1, 2, 3, 4 } }, {})
   5004      )
   5005      eq(
   5006        'Invalid range element: expected non-negative Integer',
   5007        pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, range = { -1 } }, {})
   5008      )
   5009 
   5010      eq(
   5011        'Command cannot accept count: set',
   5012        pcall_err(api.nvim_cmd, { cmd = 'set', args = {}, count = 1 }, {})
   5013      )
   5014      eq(
   5015        "Invalid 'count': expected Integer, got Boolean",
   5016        pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, count = true }, {})
   5017      )
   5018      eq(
   5019        "Invalid 'count': expected non-negative Integer",
   5020        pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, count = -1 }, {})
   5021      )
   5022 
   5023      eq(
   5024        'Command cannot accept register: set',
   5025        pcall_err(api.nvim_cmd, { cmd = 'set', args = {}, reg = 'x' }, {})
   5026      )
   5027      eq(
   5028        'Cannot use register "=',
   5029        pcall_err(api.nvim_cmd, { cmd = 'put', args = {}, reg = '=' }, {})
   5030      )
   5031      eq(
   5032        "Invalid 'reg': expected single character, got xx",
   5033        pcall_err(api.nvim_cmd, { cmd = 'put', args = {}, reg = 'xx' }, {})
   5034      )
   5035 
   5036      -- #20681
   5037      eq('Invalid command: "win_getid"', pcall_err(api.nvim_cmd, { cmd = 'win_getid' }, {}))
   5038      eq('Invalid command: "echo "hi""', pcall_err(api.nvim_cmd, { cmd = 'echo "hi"' }, {}))
   5039      matches('Invalid command: "win_getid"$', pcall_err(exec_lua, [[return vim.cmd.win_getid{}]]))
   5040 
   5041      -- Lua call allows empty {} for dict item.
   5042      eq('', exec_lua([[return vim.cmd{ cmd = "set", args = {}, magic = {} }]]))
   5043      eq('', exec_lua([[return vim.cmd{ cmd = "set", args = {}, mods = {} }]]))
   5044      eq('', api.nvim_cmd({ cmd = 'set', args = {}, magic = {} }, {}))
   5045 
   5046      -- Lua call does not allow non-empty list-like {} for dict item.
   5047      matches(
   5048        "Invalid 'magic': Expected Dict%-like Lua table$",
   5049        pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, magic = { 'a' } }]])
   5050      )
   5051      matches(
   5052        "Invalid key: 'bogus'$",
   5053        pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, magic = { bogus = true } }]])
   5054      )
   5055      matches(
   5056        "Invalid key: 'bogus'$",
   5057        pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, mods = { bogus = true } }]])
   5058      )
   5059    end)
   5060 
   5061    it('captures output', function()
   5062      eq('foo', api.nvim_cmd({ cmd = 'echo', args = { '"foo"' } }, { output = true }))
   5063      -- Returns output in cmdline mode #35321
   5064      feed(':')
   5065      eq('foo', api.nvim_cmd({ cmd = 'echo', args = { '"foo"' } }, { output = true }))
   5066    end)
   5067 
   5068    it('sets correct script context', function()
   5069      api.nvim_cmd({ cmd = 'set', args = { 'cursorline' } }, {})
   5070      local str = exec_capture([[verbose set cursorline?]])
   5071      neq(nil, str:find('cursorline\n\tLast set from API client %(channel id %d+%)'))
   5072    end)
   5073 
   5074    it('works with range', function()
   5075      insert [[
   5076        line1
   5077        line2
   5078        line3
   5079        line4
   5080        you didn't expect this
   5081        line5
   5082        line6
   5083      ]]
   5084      api.nvim_cmd({ cmd = 'del', range = { 2, 4 } }, {})
   5085      expect [[
   5086        line1
   5087        you didn't expect this
   5088        line5
   5089        line6
   5090      ]]
   5091    end)
   5092 
   5093    it('works with count', function()
   5094      insert [[
   5095        line1
   5096        line2
   5097        line3
   5098        line4
   5099        you didn't expect this
   5100        line5
   5101        line6
   5102      ]]
   5103      api.nvim_cmd({ cmd = 'del', range = { 2 }, count = 4 }, {})
   5104      expect [[
   5105        line1
   5106        line5
   5107        line6
   5108      ]]
   5109    end)
   5110 
   5111    it('works with register', function()
   5112      insert [[
   5113        line1
   5114        line2
   5115        line3
   5116        line4
   5117        you didn't expect this
   5118        line5
   5119        line6
   5120      ]]
   5121      api.nvim_cmd({ cmd = 'del', range = { 2, 4 }, reg = 'a' }, {})
   5122      command('1put a')
   5123      expect [[
   5124        line1
   5125        line2
   5126        line3
   5127        line4
   5128        you didn't expect this
   5129        line5
   5130        line6
   5131      ]]
   5132    end)
   5133 
   5134    it('works with bang', function()
   5135      api.nvim_create_user_command('Foo', 'echo "<bang>"', { bang = true })
   5136      eq('!', api.nvim_cmd({ cmd = 'Foo', bang = true }, { output = true }))
   5137      eq('', api.nvim_cmd({ cmd = 'Foo', bang = false }, { output = true }))
   5138    end)
   5139 
   5140    it('works with modifiers', function()
   5141      -- with silent = true output is still captured
   5142      eq(
   5143        '1',
   5144        api.nvim_cmd(
   5145          { cmd = 'echomsg', args = { '1' }, mods = { silent = true } },
   5146          { output = true }
   5147        )
   5148      )
   5149      -- but message isn't added to message history
   5150      eq('', api.nvim_cmd({ cmd = 'messages' }, { output = true }))
   5151 
   5152      api.nvim_create_user_command('Foo', 'set verbose', {})
   5153      eq('  verbose=1', api.nvim_cmd({ cmd = 'Foo', mods = { verbose = 1 } }, { output = true }))
   5154 
   5155      api.nvim_create_user_command('Mods', "echo '<mods>'", {})
   5156      eq(
   5157        'keepmarks keeppatterns silent 3verbose aboveleft horizontal',
   5158        api.nvim_cmd({
   5159          cmd = 'Mods',
   5160          mods = {
   5161            horizontal = true,
   5162            keepmarks = true,
   5163            keeppatterns = true,
   5164            silent = true,
   5165            split = 'aboveleft',
   5166            verbose = 3,
   5167          },
   5168        }, { output = true })
   5169      )
   5170      eq(0, api.nvim_get_option_value('verbose', {}))
   5171 
   5172      command('edit foo.txt | edit bar.txt')
   5173      eq(
   5174        '  1 #h   "foo.txt"                      line 1',
   5175        api.nvim_cmd(
   5176          { cmd = 'buffers', mods = { filter = { pattern = 'foo', force = false } } },
   5177          { output = true }
   5178        )
   5179      )
   5180      eq(
   5181        '  2 %a   "bar.txt"                      line 1',
   5182        api.nvim_cmd(
   5183          { cmd = 'buffers', mods = { filter = { pattern = 'foo', force = true } } },
   5184          { output = true }
   5185        )
   5186      )
   5187 
   5188      -- with emsg_silent = true error is suppressed
   5189      feed([[:lua vim.api.nvim_cmd({ cmd = 'call', mods = { emsg_silent = true } }, {})<CR>]])
   5190      eq('', api.nvim_cmd({ cmd = 'messages' }, { output = true }))
   5191      -- error from the next command typed is not suppressed #21420
   5192      feed(':call<CR><CR>')
   5193      eq('E471: Argument required', api.nvim_cmd({ cmd = 'messages' }, { output = true }))
   5194    end)
   5195 
   5196    it('works with magic.file', function()
   5197      exec_lua([[
   5198        vim.api.nvim_create_user_command("Foo", function(opts)
   5199          vim.api.nvim_echo({{ opts.fargs[1] }}, false, {})
   5200        end, { nargs = 1 })
   5201      ]])
   5202      eq(
   5203        uv.cwd(),
   5204        api.nvim_cmd(
   5205          { cmd = 'Foo', args = { '%:p:h' }, magic = { file = true } },
   5206          { output = true }
   5207        )
   5208      )
   5209    end)
   5210 
   5211    it('splits arguments correctly', function()
   5212      exec([[
   5213        function! FooFunc(...)
   5214          echo a:000
   5215        endfunction
   5216      ]])
   5217      api.nvim_create_user_command('Foo', 'call FooFunc(<f-args>)', { nargs = '+' })
   5218      eq(
   5219        [=[['a quick', 'brown fox', 'jumps over the', 'lazy dog']]=],
   5220        api.nvim_cmd(
   5221          { cmd = 'Foo', args = { 'a quick', 'brown fox', 'jumps over the', 'lazy dog' } },
   5222          { output = true }
   5223        )
   5224      )
   5225      eq(
   5226        [=[['test \ \\ \"""\', 'more\ tests\"  ']]=],
   5227        api.nvim_cmd(
   5228          { cmd = 'Foo', args = { [[test \ \\ \"""\]], [[more\ tests\"  ]] } },
   5229          { output = true }
   5230        )
   5231      )
   5232    end)
   5233 
   5234    it('splits arguments correctly for Lua callback', function()
   5235      api.nvim_exec_lua(
   5236        [[
   5237        local function FooFunc(opts)
   5238          vim.print(opts.fargs)
   5239        end
   5240 
   5241        vim.api.nvim_create_user_command("Foo", FooFunc, { nargs = '+' })
   5242      ]],
   5243        {}
   5244      )
   5245      eq(
   5246        [[{ "a quick", "brown fox", "jumps over the", "lazy dog" }]],
   5247        api.nvim_cmd(
   5248          { cmd = 'Foo', args = { 'a quick', 'brown fox', 'jumps over the', 'lazy dog' } },
   5249          { output = true }
   5250        )
   5251      )
   5252      eq(
   5253        [[{ 'test \\ \\\\ \\"""\\', 'more\\ tests\\"  ' }]],
   5254        api.nvim_cmd(
   5255          { cmd = 'Foo', args = { [[test \ \\ \"""\]], [[more\ tests\"  ]] } },
   5256          { output = true }
   5257        )
   5258      )
   5259    end)
   5260 
   5261    it('works with buffer names', function()
   5262      command('edit foo.txt | edit bar.txt')
   5263      api.nvim_cmd({ cmd = 'buffer', args = { 'foo.txt' } }, {})
   5264      eq('foo.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
   5265      api.nvim_cmd({ cmd = 'buffer', args = { 'bar.txt' } }, {})
   5266      eq('bar.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
   5267    end)
   5268 
   5269    it('triggers CmdUndefined event if command is not found', function()
   5270      api.nvim_exec_lua(
   5271        [[
   5272        vim.api.nvim_create_autocmd("CmdUndefined",
   5273                                    { pattern = "Foo",
   5274                                      callback = function()
   5275                                        vim.api.nvim_create_user_command("Foo", "echo 'foo'", {})
   5276                                      end
   5277                                    })
   5278      ]],
   5279        {}
   5280      )
   5281      eq('foo', api.nvim_cmd({ cmd = 'Foo' }, { output = true }))
   5282    end)
   5283 
   5284    it('errors if command is not implemented', function()
   5285      eq('Command not implemented: winpos', pcall_err(api.nvim_cmd, { cmd = 'winpos' }, {}))
   5286    end)
   5287 
   5288    it('works with empty arguments list', function()
   5289      api.nvim_cmd({ cmd = 'update' }, {})
   5290      api.nvim_cmd({ cmd = 'buffer', count = 0 }, {})
   5291    end)
   5292 
   5293    it("doesn't suppress errors when used in keymapping", function()
   5294      api.nvim_exec_lua(
   5295        [[
   5296        vim.keymap.set("n", "[l",
   5297                       function() vim.api.nvim_cmd({ cmd = "echo", args = {"foo"} }, {}) end)
   5298      ]],
   5299        {}
   5300      )
   5301      feed('[l')
   5302      neq(nil, string.find(eval('v:errmsg'), 'E5108:'))
   5303    end)
   5304 
   5305    it('handles 0 range #19608', function()
   5306      api.nvim_buf_set_lines(0, 0, -1, false, { 'aa' })
   5307      api.nvim_cmd({ cmd = 'delete', range = { 0 } }, {})
   5308      command('undo')
   5309      eq({ 'aa' }, api.nvim_buf_get_lines(0, 0, 1, false))
   5310      assert_alive()
   5311    end)
   5312 
   5313    it('supports filename expansion', function()
   5314      api.nvim_cmd({ cmd = 'argadd', args = { '%:p:h:t', '%:p:h:t' } }, {})
   5315      local arg = fn.expand('%:p:h:t')
   5316      eq({ arg, arg }, fn.argv())
   5317    end)
   5318 
   5319    it(":make command works when argument count isn't 1 #19696", function()
   5320      command('set makeprg=echo')
   5321      command('set shellquote=')
   5322      matches('^:!echo ', api.nvim_cmd({ cmd = 'make' }, { output = true }))
   5323      assert_alive()
   5324      matches(
   5325        '^:!echo foo bar',
   5326        api.nvim_cmd({ cmd = 'make', args = { 'foo', 'bar' } }, { output = true })
   5327      )
   5328      assert_alive()
   5329      local arg_pesc = pesc(fn.expand('%:p:h:t'))
   5330      matches(
   5331        ('^:!echo %s %s'):format(arg_pesc, arg_pesc),
   5332        api.nvim_cmd({ cmd = 'make', args = { '%:p:h:t', '%:p:h:t' } }, { output = true })
   5333      )
   5334      assert_alive()
   5335    end)
   5336 
   5337    it("doesn't display messages when output=true", function()
   5338      local screen = Screen.new(40, 6)
   5339      api.nvim_cmd({ cmd = 'echo', args = { [['hello']] } }, { output = true })
   5340      screen:expect {
   5341        grid = [[
   5342        ^                                        |
   5343        {1:~                                       }|*4
   5344                                                |
   5345      ]],
   5346      }
   5347      exec([[
   5348        func Print()
   5349          call nvim_cmd(#{cmd: 'echo', args: ['"hello"']}, #{output: v:true})
   5350        endfunc
   5351      ]])
   5352      feed([[:echon 1 | call Print() | echon 5<CR>]])
   5353      screen:expect {
   5354        grid = [[
   5355        ^                                        |
   5356        {1:~                                       }|*4
   5357        15                                      |
   5358      ]],
   5359      }
   5360    end)
   5361 
   5362    it('works with non-String args', function()
   5363      eq('2', api.nvim_cmd({ cmd = 'echo', args = { 2 } }, { output = true }))
   5364      eq('1', api.nvim_cmd({ cmd = 'echo', args = { true } }, { output = true }))
   5365    end)
   5366 
   5367    describe('first argument as count', function()
   5368      it('works', function()
   5369        command('vsplit | enew')
   5370        api.nvim_cmd({ cmd = 'bdelete', args = { api.nvim_get_current_buf() } }, {})
   5371        eq(1, api.nvim_get_current_buf())
   5372      end)
   5373 
   5374      it('works with :sleep using milliseconds', function()
   5375        local start = uv.now()
   5376        api.nvim_cmd({ cmd = 'sleep', args = { '100m' } }, {})
   5377        ok(uv.now() - start <= 300)
   5378      end)
   5379    end)
   5380 
   5381    it(':call with unknown function does not crash #26289', function()
   5382      eq(
   5383        'Vim:E117: Unknown function: UnknownFunc',
   5384        pcall_err(api.nvim_cmd, { cmd = 'call', args = { 'UnknownFunc()' } }, {})
   5385      )
   5386    end)
   5387 
   5388    it(':throw does not crash #24556', function()
   5389      eq('42', pcall_err(api.nvim_cmd, { cmd = 'throw', args = { '42' } }, {}))
   5390    end)
   5391 
   5392    it('can use :return #24556', function()
   5393      exec([[
   5394        func Foo()
   5395          let g:pos = 'before'
   5396          call nvim_cmd({'cmd': 'return', 'args': ['[1, 2, 3]']}, {})
   5397          let g:pos = 'after'
   5398        endfunc
   5399        let g:result = Foo()
   5400      ]])
   5401      eq('before', api.nvim_get_var('pos'))
   5402      eq({ 1, 2, 3 }, api.nvim_get_var('result'))
   5403    end)
   5404 
   5405    it('errors properly when command too recursive #27210', function()
   5406      exec_lua([[
   5407        _G.success = false
   5408        vim.api.nvim_create_user_command('Test', function()
   5409          vim.api.nvim_cmd({ cmd = 'Test' }, {})
   5410          _G.success = true
   5411        end, {})
   5412      ]])
   5413      pcall_err(command, 'Test')
   5414      assert_alive()
   5415      eq(false, exec_lua('return _G.success'))
   5416    end)
   5417 
   5418    it('handles +flags correctly', function()
   5419      -- Write a file for testing +flags
   5420      t.write_file('testfile', 'Line 1\nLine 2\nLine 3', false, false)
   5421 
   5422      -- Test + command (go to the last line)
   5423      local result = exec_lua([[
   5424        local parsed = vim.api.nvim_parse_cmd('edit + testfile', {})
   5425        vim.cmd(parsed)
   5426        return { vim.fn.line('.'), parsed.args, parsed.cmd }
   5427      ]])
   5428      eq({ 3, { '+ testfile' }, 'edit' }, result)
   5429 
   5430      -- Test +{num} command (go to line number)
   5431      result = exec_lua([[
   5432        vim.cmd(vim.api.nvim_parse_cmd('edit +1 testfile', {}))
   5433        return vim.fn.line('.')
   5434      ]])
   5435      eq(1, result)
   5436 
   5437      -- Test +/{pattern} command (go to line with pattern)
   5438      result = exec_lua([[
   5439        local parsed = vim.api.nvim_parse_cmd('edit +/Line\\ 2 testfile', {})
   5440        vim.cmd(parsed)
   5441        return {vim.fn.line('.'), parsed.args}
   5442      ]])
   5443      eq({ 2, { '+/Line\\ 2 testfile' } }, result)
   5444 
   5445      -- Test +{command} command (execute a command after opening the file)
   5446      result = exec_lua([[
   5447        vim.cmd(vim.api.nvim_parse_cmd('edit +set\\ nomodifiable testfile', {}))
   5448        return vim.bo.modifiable
   5449      ]])
   5450      eq(false, result)
   5451 
   5452      -- Test ++ flags structure in parsed command
   5453      result = exec_lua([[
   5454        local parsed = vim.api.nvim_parse_cmd('botright edit + testfile', {})
   5455        vim.cmd(parsed)
   5456        return { vim.fn.line('.'), parsed.cmd, parsed.args, parsed.mods.split }
   5457      ]])
   5458      eq({ 3, 'edit', { '+ testfile' }, 'botright' }, result)
   5459 
   5460      -- Clean up
   5461      os.remove('testfile')
   5462    end)
   5463 
   5464    it('handles various ++ flags correctly', function()
   5465      -- Test ++ff flag
   5466      local result = exec_lua [[
   5467        local parsed = vim.api.nvim_parse_cmd('edit ++ff=mac test_ff_mac.txt', {})
   5468        vim.cmd(parsed)
   5469        return parsed.args
   5470      ]]
   5471      eq({ '++ff=mac test_ff_mac.txt' }, result)
   5472      eq('mac', api.nvim_get_option_value('fileformat', {}))
   5473      eq('test_ff_mac.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
   5474 
   5475      exec_lua [[
   5476        vim.cmd(vim.api.nvim_parse_cmd('edit ++fileformat=unix test_ff_unix.txt', {}))
   5477      ]]
   5478      eq('unix', api.nvim_get_option_value('fileformat', {}))
   5479      eq('test_ff_unix.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
   5480 
   5481      -- Test ++enc flag
   5482      exec_lua [[
   5483        vim.cmd(vim.api.nvim_parse_cmd('edit ++enc=utf-32 test_enc.txt', {}))
   5484      ]]
   5485      eq('ucs-4', api.nvim_get_option_value('fileencoding', {}))
   5486      eq('test_enc.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
   5487 
   5488      -- Test ++bin and ++nobin flags
   5489      exec_lua [[
   5490        vim.cmd(vim.api.nvim_parse_cmd('edit ++bin test_bin.txt', {}))
   5491      ]]
   5492      eq(true, api.nvim_get_option_value('binary', {}))
   5493      eq('test_bin.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
   5494 
   5495      exec_lua [[
   5496        vim.cmd(vim.api.nvim_parse_cmd('edit ++nobin test_nobin.txt', {}))
   5497      ]]
   5498      eq(false, api.nvim_get_option_value('binary', {}))
   5499      eq('test_nobin.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
   5500 
   5501      -- Test multiple flags together
   5502      exec_lua [[
   5503        vim.cmd(vim.api.nvim_parse_cmd('edit ++ff=mac ++enc=utf-32 ++bin test_multi.txt', {}))
   5504      ]]
   5505      eq(true, api.nvim_get_option_value('binary', {}))
   5506      eq('mac', api.nvim_get_option_value('fileformat', {}))
   5507      eq('ucs-4', api.nvim_get_option_value('fileencoding', {}))
   5508      eq('test_multi.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
   5509    end)
   5510 
   5511    it('handles invalid and incorrect ++ flags gracefully', function()
   5512      -- Test invalid ++ff flag
   5513      local result = exec_lua [[
   5514        local cmd = vim.api.nvim_parse_cmd('edit ++ff=invalid test_invalid_ff.txt', {})
   5515        local _, err = pcall(vim.cmd, cmd)
   5516        return err
   5517      ]]
   5518      matches("Invalid argument : '%+%+ff=invalid'$", result)
   5519 
   5520      -- Test incorrect ++ syntax
   5521      result = exec_lua [[
   5522        local cmd = vim.api.nvim_parse_cmd('edit ++unknown=test_unknown.txt', {})
   5523        local _, err = pcall(vim.cmd, cmd)
   5524        return err
   5525      ]]
   5526      matches("Invalid argument : '%+%+unknown=test_unknown.txt'$", result)
   5527 
   5528      -- Test invalid ++bin flag
   5529      result = exec_lua [[
   5530        local cmd = vim.api.nvim_parse_cmd('edit ++binabc test_invalid_bin.txt', {})
   5531        local _, err = pcall(vim.cmd, cmd)
   5532        return err
   5533      ]]
   5534      matches("Invalid argument : '%+%+binabc test_invalid_bin.txt'$", result)
   5535    end)
   5536 
   5537    it('handles ++p for creating parent directory', function()
   5538      exec_lua [[
   5539        vim.cmd('edit flags_dir/test_create.txt')
   5540        vim.cmd(vim.api.nvim_parse_cmd('write! ++p', {}))
   5541      ]]
   5542      eq(true, fn.isdirectory('flags_dir') == 1)
   5543      fn.delete('flags_dir', 'rf')
   5544    end)
   5545 
   5546    it('tests editing files with bad utf8 sequences', function()
   5547      -- Write a file with bad utf8 sequences
   5548      local file = io.open('Xfile', 'wb')
   5549      file:write('[\255][\192][\226\137\240][\194\194]')
   5550      file:close()
   5551 
   5552      exec_lua([[
   5553        vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 Xfile', {}))
   5554      ]])
   5555      eq('[?][?][???][??]', api.nvim_get_current_line())
   5556 
   5557      exec_lua([[
   5558        vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 ++bad=_ Xfile', {}))
   5559      ]])
   5560      eq('[_][_][___][__]', api.nvim_get_current_line())
   5561 
   5562      exec_lua([[
   5563        vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 ++bad=drop Xfile', {}))
   5564      ]])
   5565      eq('[][][][]', api.nvim_get_current_line())
   5566 
   5567      exec_lua([[
   5568        vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 ++bad=keep Xfile', {}))
   5569      ]])
   5570      eq('[\255][\192][\226\137\240][\194\194]', api.nvim_get_current_line())
   5571 
   5572      local result = exec_lua([[
   5573        local _, err = pcall(vim.cmd, vim.api.nvim_parse_cmd('edit ++enc=utf8 ++bad=foo Xfile', {}))
   5574        return err
   5575      ]])
   5576      matches("Invalid argument : '%+%+bad=foo'$", result)
   5577      -- Clean up
   5578      os.remove('Xfile')
   5579    end)
   5580    it('interprets numeric args as count for count-only commands', function()
   5581      api.nvim_cmd({ cmd = 'copen', args = { 8 } }, {})
   5582      local height1 = api.nvim_win_get_height(0)
   5583      command('cclose')
   5584      api.nvim_cmd({ cmd = 'copen', count = 8 }, {})
   5585      local height2 = api.nvim_win_get_height(0)
   5586      command('cclose')
   5587      eq(height1, height2)
   5588 
   5589      exec_lua 'vim.cmd.copen(5)'
   5590      height2 = api.nvim_win_get_height(0)
   5591      command('cclose')
   5592      eq(5, height2)
   5593 
   5594      -- should reject both count and numeric arg
   5595      eq(
   5596        "Cannot specify both 'count' and numeric argument",
   5597        pcall_err(api.nvim_cmd, { cmd = 'copen', args = { 5 }, count = 10 }, {})
   5598      )
   5599    end)
   5600    it('handles string numeric arguments correctly', function()
   5601      -- Valid string numbers should work
   5602      api.nvim_cmd({ cmd = 'copen', args = { '6' } }, {})
   5603      eq(6, api.nvim_win_get_height(0))
   5604      command('cclose')
   5605      -- Invalid strings should be rejected
   5606      eq(
   5607        'Wrong number of arguments',
   5608        pcall_err(api.nvim_cmd, { cmd = 'copen', args = { 'abc' } }, {})
   5609      )
   5610      -- Partial numbers should be rejected
   5611      eq(
   5612        'Wrong number of arguments',
   5613        pcall_err(api.nvim_cmd, { cmd = 'copen', args = { '8abc' } }, {})
   5614      )
   5615      -- Empty string should be rejected
   5616      eq(
   5617        'Invalid command arg: expected non-whitespace',
   5618        pcall_err(api.nvim_cmd, { cmd = 'copen', args = { '' } }, {})
   5619      )
   5620      -- Negative string numbers should be rejected
   5621      eq(
   5622        'Wrong number of arguments',
   5623        pcall_err(api.nvim_cmd, { cmd = 'copen', args = { '-5' } }, {})
   5624      )
   5625      -- Leading/trailing spaces should be rejected
   5626      eq(
   5627        'Wrong number of arguments',
   5628        pcall_err(api.nvim_cmd, { cmd = 'copen', args = { ' 5 ' } }, {})
   5629      )
   5630    end)
   5631  end)
   5632 
   5633  it('nvim__redraw', function()
   5634    local screen = Screen.new(60, 5)
   5635    eq('at least one action required', pcall_err(api.nvim__redraw, {}))
   5636    eq('at least one action required', pcall_err(api.nvim__redraw, { buf = 0 }))
   5637    eq('at least one action required', pcall_err(api.nvim__redraw, { win = 0 }))
   5638    eq("cannot use both 'buf' and 'win'", pcall_err(api.nvim__redraw, { buf = 0, win = 0 }))
   5639    local win = api.nvim_get_current_win()
   5640    -- Can move cursor to recently opened window and window is flushed #28868
   5641    feed(':echo getchar()<CR>')
   5642    local newwin = api.nvim_open_win(0, false, {
   5643      relative = 'editor',
   5644      width = 1,
   5645      height = 1,
   5646      row = 1,
   5647      col = 10,
   5648    })
   5649    api.nvim__redraw({ win = newwin, cursor = true })
   5650    screen:expect({
   5651      grid = [[
   5652                                                                    |
   5653        {1:~         }{4:^ }{1:                                                 }|
   5654        {1:~                                                           }|*2
   5655        :echo getchar()                                             |
   5656      ]],
   5657    })
   5658    fn.setline(1, 'foobar')
   5659    command('vnew')
   5660    fn.setline(1, 'foobaz')
   5661    -- Can flush pending screen updates
   5662    api.nvim__redraw({ flush = true })
   5663    screen:expect({
   5664      grid = [[
   5665        foobaz                        │foobar                       |
   5666        {1:~         }{4:^f}{1:                   }│{1:~                            }|
   5667        {1:~                             }│{1:~                            }|
   5668        {3:[No Name] [+]                  }{2:[No Name] [+]                }|
   5669        :echo getchar()                                             |
   5670      ]],
   5671    })
   5672    api.nvim_win_close(newwin, true)
   5673    -- Can update the grid cursor position #20793
   5674    api.nvim__redraw({ cursor = true })
   5675    screen:expect({
   5676      grid = [[
   5677        ^foobaz                        │foobar                       |
   5678        {1:~                             }│{1:~                            }|*2
   5679        {3:[No Name] [+]                  }{2:[No Name] [+]                }|
   5680        :echo getchar()                                             |
   5681      ]],
   5682    })
   5683    -- Also in non-current window
   5684    api.nvim__redraw({ cursor = true, win = win })
   5685    screen:expect({
   5686      grid = [[
   5687        foobaz                        │^foobar                       |
   5688        {1:~                             }│{1:~                            }|*2
   5689        {3:[No Name] [+]                  }{2:[No Name] [+]                }|
   5690        :echo getchar()                                             |
   5691      ]],
   5692    })
   5693    -- Can update the 'statusline' in a single window
   5694    api.nvim_set_option_value('statusline', 'statusline1', { win = 0 })
   5695    api.nvim_set_option_value('statusline', 'statusline2', { win = win })
   5696    api.nvim__redraw({ cursor = true, win = 0, statusline = true })
   5697    screen:expect({
   5698      grid = [[
   5699        ^foobaz                        │foobar                       |
   5700        {1:~                             }│{1:~                            }|*2
   5701        {3:statusline1                    }{2:[No Name] [+]                }|
   5702        :echo getchar()                                             |
   5703      ]],
   5704    })
   5705    api.nvim__redraw({ win = win, statusline = true })
   5706    screen:expect({
   5707      grid = [[
   5708        ^foobaz                        │foobar                       |
   5709        {1:~                             }│{1:~                            }|*2
   5710        {3:statusline1                    }{2:statusline2                  }|
   5711        :echo getchar()                                             |
   5712      ]],
   5713    })
   5714    -- Can update the 'statusline' in all windows
   5715    api.nvim_set_option_value('statusline', '', { win = win })
   5716    api.nvim_set_option_value('statusline', 'statusline3', {})
   5717    api.nvim__redraw({ statusline = true })
   5718    screen:expect({
   5719      grid = [[
   5720        ^foobaz                        │foobar                       |
   5721        {1:~                             }│{1:~                            }|*2
   5722        {3:statusline3                    }{2:statusline3                  }|
   5723        :echo getchar()                                             |
   5724      ]],
   5725    })
   5726    -- Can update the 'statuscolumn'
   5727    api.nvim_set_option_value('statuscolumn', 'statuscolumn', { win = win })
   5728    api.nvim__redraw({ statuscolumn = true })
   5729    screen:expect({
   5730      grid = [[
   5731        ^foobaz                        │{8:statuscolumn}foobar           |
   5732        {1:~                             }│{1:~                            }|*2
   5733        {3:statusline3                    }{2:statusline3                  }|
   5734        :echo getchar()                                             |
   5735      ]],
   5736    })
   5737    -- Can update the 'winbar'
   5738    api.nvim_set_option_value('winbar', 'winbar', { win = 0 })
   5739    api.nvim__redraw({ win = 0, winbar = true })
   5740    screen:expect({
   5741      grid = [[
   5742        {5:^winbar                        }│{8:statuscolumn}foobar           |
   5743        foobaz                        │{1:~                            }|
   5744        {1:~                             }│{1:~                            }|
   5745        {3:statusline3                    }{2:statusline3                  }|
   5746        :echo getchar()                                             |
   5747      ]],
   5748    })
   5749    -- Can update the 'tabline'
   5750    api.nvim_set_option_value('showtabline', 2, {})
   5751    api.nvim_set_option_value('tabline', 'tabline', {})
   5752    api.nvim__redraw({ tabline = true })
   5753    screen:expect({
   5754      grid = [[
   5755        {2:^tabline                                                     }|
   5756        {5:winbar                        }│{8:statuscolumn}foobar           |
   5757        foobaz                        │{1:~                            }|
   5758        {3:statusline3                    }{2:statusline3                  }|
   5759        :echo getchar()                                             |
   5760      ]],
   5761    })
   5762    -- Can update multiple status widgets
   5763    api.nvim_set_option_value('tabline', 'tabline2', {})
   5764    api.nvim_set_option_value('statusline', 'statusline4', {})
   5765    api.nvim__redraw({ statusline = true, tabline = true })
   5766    screen:expect({
   5767      grid = [[
   5768        {2:^tabline2                                                    }|
   5769        {5:winbar                        }│{8:statuscolumn}foobar           |
   5770        foobaz                        │{1:~                            }|
   5771        {3:statusline4                    }{2:statusline4                  }|
   5772        :echo getchar()                                             |
   5773      ]],
   5774    })
   5775    -- Can update all status widgets
   5776    api.nvim_set_option_value('tabline', 'tabline3', {})
   5777    api.nvim_set_option_value('statusline', 'statusline5', {})
   5778    api.nvim_set_option_value('statuscolumn', 'statuscolumn2', {})
   5779    api.nvim_set_option_value('winbar', 'winbar2', {})
   5780    api.nvim__redraw({ statuscolumn = true, statusline = true, tabline = true, winbar = true })
   5781    screen:expect({
   5782      grid = [[
   5783        {2:^tabline3                                                    }|
   5784        {5:winbar2                       }│{5:winbar2                      }|
   5785        {8:statuscolumn2}foobaz           │{8:statuscolumn}foobar           |
   5786        {3:statusline5                    }{2:statusline5                  }|
   5787        :echo getchar()                                             |
   5788      ]],
   5789    })
   5790    -- Can update status widget for a specific window
   5791    feed('<CR><CR>')
   5792    command('let g:status=0')
   5793    api.nvim_set_option_value('statusline', '%{%g:status%}', { win = 0 })
   5794    command('vsplit')
   5795    screen:expect({
   5796      grid = [[
   5797        {2:tabline3                                                    }|
   5798        {5:winbar2             }│{5:winbar2            }│{5:winbar2            }|
   5799        {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar |
   5800        {3:0                    }{2:0                   statusline5        }|
   5801        13                                                          |
   5802      ]],
   5803    })
   5804    command('let g:status=1')
   5805    api.nvim__redraw({ win = 0, statusline = true })
   5806    screen:expect({
   5807      grid = [[
   5808        {2:tabline3                                                    }|
   5809        {5:winbar2             }│{5:winbar2            }│{5:winbar2            }|
   5810        {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar |
   5811        {3:1                    }{2:0                   statusline5        }|
   5812        13                                                          |
   5813      ]],
   5814    })
   5815    -- Can update status widget for a specific buffer
   5816    command('let g:status=2')
   5817    api.nvim__redraw({ buf = 0, statusline = true })
   5818    screen:expect({
   5819      grid = [[
   5820        {2:tabline3                                                    }|
   5821        {5:winbar2             }│{5:winbar2            }│{5:winbar2            }|
   5822        {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar |
   5823        {3:2                    }{2:2                   statusline5        }|
   5824        13                                                          |
   5825      ]],
   5826    })
   5827    -- valid = true does not draw any lines on its own
   5828    exec_lua([[
   5829      _G.lines = 0
   5830      ns = vim.api.nvim_create_namespace('')
   5831      vim.api.nvim_set_decoration_provider(ns, {
   5832        on_win = function()
   5833          if _G.do_win then
   5834            vim.api.nvim_buf_set_extmark(0, ns, 0, 0, { hl_group = 'IncSearch', end_col = 6 })
   5835          end
   5836        end,
   5837        on_line = function()
   5838          _G.lines = _G.lines + 1
   5839        end,
   5840      })
   5841    ]])
   5842    local lines = exec_lua('return lines')
   5843    api.nvim__redraw({ buf = 0, valid = true, flush = true })
   5844    eq(lines, exec_lua('return _G.lines'))
   5845    -- valid = false does
   5846    api.nvim__redraw({ buf = 0, valid = false, flush = true })
   5847    neq(lines, exec_lua('return _G.lines'))
   5848    -- valid = true does redraw lines if affected by on_win callback
   5849    exec_lua('_G.do_win = true')
   5850    api.nvim__redraw({ buf = 0, valid = true, flush = true })
   5851    screen:expect({
   5852      grid = [[
   5853        {2:tabline3                                                    }|
   5854        {5:winbar2             }│{5:winbar2            }│{5:winbar2            }|
   5855        {8:statuscolumn2}{2:^foobaz} │{8:statuscolumn2}{2:foobaz}│{8:statuscolumn}foobar |
   5856        {3:2                    }{2:2                   statusline5        }|
   5857        13                                                          |
   5858      ]],
   5859    })
   5860  end)
   5861 
   5862  it('nvim__redraw range parameter', function()
   5863    Screen.new(10, 5)
   5864    fn.setline(1, fn.range(4))
   5865 
   5866    exec_lua([[
   5867      _G.lines_list = {}
   5868      ns = vim.api.nvim_create_namespace('')
   5869      vim.api.nvim_set_decoration_provider(ns, {
   5870        on_win = function()
   5871        end,
   5872        on_line = function(_, _, _, line)
   5873          table.insert(_G.lines_list, line)
   5874        end,
   5875      })
   5876      function _G.get_lines()
   5877        local lines = _G.lines_list
   5878        _G.lines_list = {}
   5879        return lines
   5880      end
   5881    ]])
   5882 
   5883    api.nvim__redraw({ flush = true, valid = false })
   5884    exec_lua('_G.get_lines()')
   5885 
   5886    local actual_lines = {}
   5887    local function test(range)
   5888      api.nvim__redraw({ win = 0, range = range })
   5889      table.insert(actual_lines, exec_lua('return _G.get_lines()'))
   5890    end
   5891 
   5892    test({ 0, -1 })
   5893    test({ 2, 2 ^ 31 })
   5894    test({ 2, 2 ^ 32 })
   5895    test({ 2 ^ 31 - 1, 2 })
   5896    test({ 2 ^ 32 - 1, 2 })
   5897 
   5898    local expected_lines = {
   5899      { 0, 1, 2, 3 },
   5900      { 2, 3 },
   5901      { 2, 3 },
   5902      {},
   5903      {},
   5904    }
   5905    eq(expected_lines, actual_lines)
   5906 
   5907    n.assert_alive()
   5908  end)
   5909 
   5910  it('nvim__redraw updates topline', function()
   5911    local screen = Screen.new(40, 8)
   5912    fn.setline(1, fn.range(100))
   5913    feed(':call getchar()<CR>')
   5914    fn.cursor(50, 1)
   5915    screen:expect([[
   5916      0                                       |
   5917      1                                       |
   5918      2                                       |
   5919      3                                       |
   5920      4                                       |
   5921      5                                       |
   5922      6                                       |
   5923      ^:call getchar()                         |
   5924    ]])
   5925    api.nvim__redraw({ flush = true })
   5926    screen:expect([[
   5927      46                                      |
   5928      47                                      |
   5929      48                                      |
   5930      49                                      |
   5931      50                                      |
   5932      51                                      |
   5933      52                                      |
   5934      ^:call getchar()                         |
   5935    ]])
   5936  end)
   5937 end)