aboutsummaryrefslogtreecommitdiff
path: root/test/functional/api
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/api')
-rw-r--r--test/functional/api/buffer_spec.lua18
-rw-r--r--test/functional/api/highlight_spec.lua112
-rw-r--r--test/functional/api/keymap_spec.lua310
-rw-r--r--test/functional/api/proc_spec.lua81
-rw-r--r--test/functional/api/server_notifications_spec.lua22
-rw-r--r--test/functional/api/server_requests_spec.lua190
-rw-r--r--test/functional/api/tabpage_spec.lua2
-rw-r--r--test/functional/api/ui_spec.lua37
-rw-r--r--test/functional/api/version_spec.lua16
-rw-r--r--test/functional/api/vim_spec.lua614
-rw-r--r--test/functional/api/window_spec.lua9
11 files changed, 1349 insertions, 62 deletions
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua
index 552e3a8564..823c134ebd 100644
--- a/test/functional/api/buffer_spec.lua
+++ b/test/functional/api/buffer_spec.lua
@@ -5,11 +5,12 @@ local curbufmeths, ok = helpers.curbufmeths, helpers.ok
local funcs = helpers.funcs
local request = helpers.request
local exc_exec = helpers.exc_exec
-local execute = helpers.execute
+local feed_command = helpers.feed_command
local insert = helpers.insert
local NIL = helpers.NIL
local meth_pcall = helpers.meth_pcall
local command = helpers.command
+local bufmeths = helpers.bufmeths
describe('api/buf', function()
before_each(clear)
@@ -121,6 +122,15 @@ describe('api/buf', function()
local get_lines, set_lines = curbufmeths.get_lines, curbufmeths.set_lines
local line_count = curbufmeths.line_count
+ it('fails correctly when input is not valid', function()
+ eq(1, curbufmeths.get_number())
+ local err, emsg = pcall(bufmeths.set_lines, 1, 1, 2, false, {'b\na'})
+ eq(false, err)
+ local exp_emsg = 'String cannot contain newlines'
+ -- Expected {filename}:{lnum}: {exp_emsg}
+ eq(': ' .. exp_emsg, emsg:sub(-#exp_emsg - 2))
+ end)
+
it('has correct line_count when inserting and deleting', function()
eq(1, line_count())
set_lines(-1, -1, true, {'line'})
@@ -246,7 +256,7 @@ describe('api/buf', function()
end)
it("set_line on alternate buffer does not access invalid line (E315)", function()
- execute('set hidden')
+ feed_command('set hidden')
insert('Initial file')
command('enew')
insert([[
@@ -257,7 +267,7 @@ describe('api/buf', function()
The
Other
Buffer]])
- execute('$')
+ feed_command('$')
local retval = exc_exec("call nvim_buf_set_lines(1, 0, 1, v:false, ['test'])")
eq(0, retval)
end)
@@ -271,7 +281,7 @@ describe('api/buf', function()
eq(1, funcs.exists('b:lua'))
curbufmeths.del_var('lua')
eq(0, funcs.exists('b:lua'))
- eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curbufmeths.del_var, 'lua'))
+ eq({false, 'Key does not exist: lua'}, meth_pcall(curbufmeths.del_var, 'lua'))
curbufmeths.set_var('lua', 1)
command('lockvar b:lua')
eq({false, 'Key is locked: lua'}, meth_pcall(curbufmeths.del_var, 'lua'))
diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua
new file mode 100644
index 0000000000..fed53a3dfd
--- /dev/null
+++ b/test/functional/api/highlight_spec.lua
@@ -0,0 +1,112 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear, nvim = helpers.clear, helpers.nvim
+local Screen = require('test.functional.ui.screen')
+local eq, eval = helpers.eq, helpers.eval
+local command = helpers.command
+local meths = helpers.meths
+
+describe('highlight api',function()
+ local expected_rgb = {
+ background = Screen.colors.Yellow,
+ foreground = Screen.colors.Red,
+ special = Screen.colors.Blue,
+ bold = true,
+ }
+ local expected_cterm = {
+ background = 10,
+ underline = true,
+ }
+ local expected_rgb2 = {
+ background = Screen.colors.Yellow,
+ foreground = Screen.colors.Red,
+ special = Screen.colors.Blue,
+ bold = true,
+ italic = true,
+ reverse = true,
+ undercurl = true,
+ underline = true,
+ }
+
+ before_each(function()
+ clear()
+ command("hi NewHighlight cterm=underline ctermbg=green guifg=red guibg=yellow guisp=blue gui=bold")
+ end)
+
+ it("nvim_get_hl_by_id", function()
+ local hl_id = eval("hlID('NewHighlight')")
+ eq(expected_cterm, nvim("get_hl_by_id", hl_id, false))
+
+ hl_id = eval("hlID('NewHighlight')")
+ -- Test valid id.
+ eq(expected_rgb, nvim("get_hl_by_id", hl_id, true))
+
+ -- Test invalid id.
+ local err, emsg = pcall(meths.get_hl_by_id, 30000, false)
+ eq(false, err)
+ eq('Invalid highlight id: 30000', string.match(emsg, 'Invalid.*'))
+
+ -- Test all highlight properties.
+ command('hi NewHighlight gui=underline,bold,undercurl,italic,reverse')
+ eq(expected_rgb2, nvim("get_hl_by_id", hl_id, true))
+
+ -- Test nil argument.
+ err, emsg = pcall(meths.get_hl_by_id, { nil }, false)
+ eq(false, err)
+ eq('Wrong type for argument 1, expecting Integer',
+ string.match(emsg, 'Wrong.*'))
+
+ -- Test 0 argument.
+ err, emsg = pcall(meths.get_hl_by_id, 0, false)
+ eq(false, err)
+ eq('Invalid highlight id: 0',
+ string.match(emsg, 'Invalid.*'))
+
+ -- Test -1 argument.
+ err, emsg = pcall(meths.get_hl_by_id, -1, false)
+ eq(false, err)
+ eq('Invalid highlight id: -1',
+ string.match(emsg, 'Invalid.*'))
+ end)
+
+ it("nvim_get_hl_by_name", function()
+ local expected_normal = { background = Screen.colors.Yellow,
+ foreground = Screen.colors.Red }
+
+ -- Test `Normal` default values.
+ eq({}, nvim("get_hl_by_name", 'Normal', true))
+
+ eq(expected_cterm, nvim("get_hl_by_name", 'NewHighlight', false))
+ eq(expected_rgb, nvim("get_hl_by_name", 'NewHighlight', true))
+
+ -- Test `Normal` modified values.
+ command('hi Normal guifg=red guibg=yellow')
+ eq(expected_normal, nvim("get_hl_by_name", 'Normal', true))
+
+ -- Test invalid name.
+ local err, emsg = pcall(meths.get_hl_by_name , 'unknown_highlight', false)
+ eq(false, err)
+ eq('Invalid highlight name: unknown_highlight',
+ string.match(emsg, 'Invalid.*'))
+
+ -- Test nil argument.
+ err, emsg = pcall(meths.get_hl_by_name , { nil }, false)
+ eq(false, err)
+ eq('Wrong type for argument 1, expecting String',
+ string.match(emsg, 'Wrong.*'))
+
+ -- Test empty string argument.
+ err, emsg = pcall(meths.get_hl_by_name , '', false)
+ eq(false, err)
+ eq('Invalid highlight name: ',
+ string.match(emsg, 'Invalid.*'))
+
+ -- Test "standout" attribute. #8054
+ eq({ underline = true, },
+ meths.get_hl_by_name('cursorline', 0));
+ command('hi CursorLine cterm=standout,underline term=standout,underline gui=standout,underline')
+ command('set cursorline')
+ eq({ underline = true, standout = true, },
+ meths.get_hl_by_name('cursorline', 0));
+
+ end)
+end)
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
new file mode 100644
index 0000000000..3a10f9c60f
--- /dev/null
+++ b/test/functional/api/keymap_spec.lua
@@ -0,0 +1,310 @@
+local helpers = require('test.functional.helpers')(after_each)
+local global_helpers = require('test.helpers')
+
+local clear = helpers.clear
+local command = helpers.command
+local curbufmeths = helpers.curbufmeths
+local eq = helpers.eq
+local funcs = helpers.funcs
+local meths = helpers.meths
+local source = helpers.source
+
+local shallowcopy = global_helpers.shallowcopy
+
+describe('get_keymap', function()
+ before_each(clear)
+
+ -- Basic mapping and table to be used to describe results
+ local foo_bar_string = 'nnoremap foo bar'
+ local foo_bar_map_table = {
+ lhs='foo',
+ silent=0,
+ rhs='bar',
+ expr=0,
+ sid=0,
+ buffer=0,
+ nowait=0,
+ mode='n',
+ noremap=1,
+ }
+
+ it('returns empty list when no map', function()
+ eq({}, meths.get_keymap('n'))
+ end)
+
+ it('returns list of all applicable mappings', function()
+ command(foo_bar_string)
+ -- Only one mapping available
+ -- Should be the same as the dictionary we supplied earlier
+ -- and the dictionary you would get from maparg
+ -- since this is a global map, and not script local
+ eq({foo_bar_map_table}, meths.get_keymap('n'))
+ eq({funcs.maparg('foo', 'n', false, true)},
+ meths.get_keymap('n')
+ )
+
+ -- Add another mapping
+ command('nnoremap foo_longer bar_longer')
+ local foolong_bar_map_table = shallowcopy(foo_bar_map_table)
+ foolong_bar_map_table['lhs'] = 'foo_longer'
+ foolong_bar_map_table['rhs'] = 'bar_longer'
+
+ eq({foolong_bar_map_table, foo_bar_map_table},
+ meths.get_keymap('n')
+ )
+
+ -- Remove a mapping
+ command('unmap foo_longer')
+ eq({foo_bar_map_table},
+ meths.get_keymap('n')
+ )
+ end)
+
+ it('works for other modes', function()
+ -- Add two mappings, one in insert and one normal
+ -- We'll only check the insert mode one
+ command('nnoremap not_going to_check')
+
+ command('inoremap foo bar')
+ -- The table will be the same except for the mode
+ local insert_table = shallowcopy(foo_bar_map_table)
+ insert_table['mode'] = 'i'
+
+ eq({insert_table}, meths.get_keymap('i'))
+ end)
+
+ it('considers scope', function()
+ -- change the map slightly
+ command('nnoremap foo_longer bar_longer')
+ local foolong_bar_map_table = shallowcopy(foo_bar_map_table)
+ foolong_bar_map_table['lhs'] = 'foo_longer'
+ foolong_bar_map_table['rhs'] = 'bar_longer'
+
+ local buffer_table = shallowcopy(foo_bar_map_table)
+ buffer_table['buffer'] = 1
+
+ command('nnoremap <buffer> foo bar')
+
+ -- The buffer mapping should not show up
+ eq({foolong_bar_map_table}, meths.get_keymap('n'))
+ eq({buffer_table}, curbufmeths.get_keymap('n'))
+ end)
+
+ it('considers scope for overlapping maps', function()
+ command('nnoremap foo bar')
+
+ local buffer_table = shallowcopy(foo_bar_map_table)
+ buffer_table['buffer'] = 1
+
+ command('nnoremap <buffer> foo bar')
+
+ eq({foo_bar_map_table}, meths.get_keymap('n'))
+ eq({buffer_table}, curbufmeths.get_keymap('n'))
+ end)
+
+ it('can retrieve mapping for different buffers', function()
+ local original_buffer = curbufmeths.get_number()
+ -- Place something in each of the buffers to make sure they stick around
+ -- and set hidden so we can leave them
+ command('set hidden')
+ command('new')
+ command('normal! ihello 2')
+ command('new')
+ command('normal! ihello 3')
+
+ local final_buffer = curbufmeths.get_number()
+
+ command('nnoremap <buffer> foo bar')
+ -- Final buffer will have buffer mappings
+ local buffer_table = shallowcopy(foo_bar_map_table)
+ buffer_table['buffer'] = final_buffer
+ eq({buffer_table}, meths.buf_get_keymap(final_buffer, 'n'))
+ eq({buffer_table}, meths.buf_get_keymap(0, 'n'))
+
+ command('buffer ' .. original_buffer)
+ eq(original_buffer, curbufmeths.get_number())
+ -- Original buffer won't have any mappings
+ eq({}, meths.get_keymap('n'))
+ eq({}, curbufmeths.get_keymap('n'))
+ eq({buffer_table}, meths.buf_get_keymap(final_buffer, 'n'))
+ end)
+
+ -- Test toggle switches for basic options
+ -- @param option The key represented in the `maparg()` result dict
+ local function global_and_buffer_test(map,
+ option,
+ option_token,
+ global_on_result,
+ buffer_on_result,
+ global_off_result,
+ buffer_off_result,
+ new_windows)
+
+ local function make_new_windows(number_of_windows)
+ if new_windows == nil then
+ return nil
+ end
+
+ for _=1,number_of_windows do
+ command('new')
+ end
+ end
+
+ local mode = string.sub(map, 1,1)
+ -- Don't run this for the <buffer> mapping, since it doesn't make sense
+ if option_token ~= '<buffer>' then
+ it(string.format( 'returns %d for the key "%s" when %s is used globally with %s (%s)',
+ global_on_result, option, option_token, map, mode), function()
+ make_new_windows(new_windows)
+ command(map .. ' ' .. option_token .. ' foo bar')
+ local result = meths.get_keymap(mode)[1][option]
+ eq(global_on_result, result)
+ end)
+ end
+
+ it(string.format('returns %d for the key "%s" when %s is used for buffers with %s (%s)',
+ buffer_on_result, option, option_token, map, mode), function()
+ make_new_windows(new_windows)
+ command(map .. ' <buffer> ' .. option_token .. ' foo bar')
+ local result = curbufmeths.get_keymap(mode)[1][option]
+ eq(buffer_on_result, result)
+ end)
+
+ -- Don't run these for the <buffer> mapping, since it doesn't make sense
+ if option_token ~= '<buffer>' then
+ it(string.format('returns %d for the key "%s" when %s is not used globally with %s (%s)',
+ global_off_result, option, option_token, map, mode), function()
+ make_new_windows(new_windows)
+ command(map .. ' baz bat')
+ local result = meths.get_keymap(mode)[1][option]
+ eq(global_off_result, result)
+ end)
+
+ it(string.format('returns %d for the key "%s" when %s is not used for buffers with %s (%s)',
+ buffer_off_result, option, option_token, map, mode), function()
+ make_new_windows(new_windows)
+ command(map .. ' <buffer> foo bar')
+
+ local result = curbufmeths.get_keymap(mode)[1][option]
+ eq(buffer_off_result, result)
+ end)
+ end
+ end
+
+ -- Standard modes and returns the same values in the dictionary as maparg()
+ local mode_list = {'nnoremap', 'nmap', 'imap', 'inoremap', 'cnoremap'}
+ for mode in pairs(mode_list) do
+ global_and_buffer_test(mode_list[mode], 'silent', '<silent>', 1, 1, 0, 0)
+ global_and_buffer_test(mode_list[mode], 'nowait', '<nowait>', 1, 1, 0, 0)
+ global_and_buffer_test(mode_list[mode], 'expr', '<expr>', 1, 1, 0, 0)
+ end
+
+ -- noremap will now be 2 if script was used, which is not the same as maparg()
+ global_and_buffer_test('nmap', 'noremap', '<script>', 2, 2, 0, 0)
+ global_and_buffer_test('nnoremap', 'noremap', '<script>', 2, 2, 1, 1)
+
+ -- buffer will return the buffer ID, which is not the same as maparg()
+ -- Three of these tests won't run
+ global_and_buffer_test('nnoremap', 'buffer', '<buffer>', nil, 3, nil, nil, 2)
+
+ it('returns script numbers for global maps', function()
+ source([[
+ function! s:maparg_test_function() abort
+ return 'testing'
+ endfunction
+
+ nnoremap fizz :call <SID>maparg_test_function()<CR>
+ ]])
+ local sid_result = meths.get_keymap('n')[1]['sid']
+ eq(1, sid_result)
+ eq('testing', meths.call_function('<SNR>' .. sid_result .. '_maparg_test_function', {}))
+ end)
+
+ it('returns script numbers for buffer maps', function()
+ source([[
+ function! s:maparg_test_function() abort
+ return 'testing'
+ endfunction
+
+ nnoremap <buffer> fizz :call <SID>maparg_test_function()<CR>
+ ]])
+ local sid_result = curbufmeths.get_keymap('n')[1]['sid']
+ eq(1, sid_result)
+ eq('testing', meths.call_function('<SNR>' .. sid_result .. '_maparg_test_function', {}))
+ end)
+
+ it('works with <F12> and others', function()
+ command('nnoremap <F12> :let g:maparg_test_var = 1<CR>')
+ eq('<F12>', meths.get_keymap('n')[1]['lhs'])
+ eq(':let g:maparg_test_var = 1<CR>', meths.get_keymap('n')[1]['rhs'])
+ end)
+
+ it('works correctly despite various &cpo settings', function()
+ local cpo_table = {
+ silent=0,
+ expr=0,
+ sid=0,
+ buffer=0,
+ nowait=0,
+ noremap=1,
+ }
+ local function cpomap(lhs, rhs, mode)
+ local ret = shallowcopy(cpo_table)
+ ret.lhs = lhs
+ ret.rhs = rhs
+ ret.mode = mode
+ return ret
+ end
+
+ command('set cpo+=B')
+ command('nnoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\')
+ command('nnoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\')
+
+ command('set cpo+=B')
+ command('xnoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\')
+ command('xnoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\')
+
+ command('set cpo-=B')
+ command('snoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\')
+ command('snoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\')
+
+ command('set cpo-=B')
+ command('onoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\')
+ command('onoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\')
+
+ for _, cmd in ipairs({
+ 'set cpo-=B',
+ 'set cpo+=B',
+ }) do
+ command(cmd)
+ eq({cpomap('\\<C-C><C-C><lt>C-c>\\', '\\<C-D><C-D><lt>C-d>\\', 'n'),
+ cpomap('\\<C-A><C-A><lt>C-a>\\', '\\<C-B><C-B><lt>C-b>\\', 'n')},
+ meths.get_keymap('n'))
+ eq({cpomap('\\<C-C><C-C><lt>C-c>\\', '\\<C-D><C-D><lt>C-d>\\', 'x'),
+ cpomap('\\<C-A><C-A><lt>C-a>\\', '\\<C-B><C-B><lt>C-b>\\', 'x')},
+ meths.get_keymap('x'))
+ eq({cpomap('<lt>C-c><C-C><lt>C-c> ', '<lt>C-d><C-D><lt>C-d>', 's'),
+ cpomap('<lt>C-a><C-A><lt>C-a> ', '<lt>C-b><C-B><lt>C-b>', 's')},
+ meths.get_keymap('s'))
+ eq({cpomap('<lt>C-c><C-C><lt>C-c> ', '<lt>C-d><C-D><lt>C-d>', 'o'),
+ cpomap('<lt>C-a><C-A><lt>C-a> ', '<lt>C-b><C-B><lt>C-b>', 'o')},
+ meths.get_keymap('o'))
+ end
+ end)
+
+ it('always uses space for space and bar for bar', function()
+ local space_table = {
+ lhs='| |',
+ rhs='| |',
+ mode='n',
+ silent=0,
+ expr=0,
+ sid=0,
+ buffer=0,
+ nowait=0,
+ noremap=1,
+ }
+ command('nnoremap \\|<Char-0x20><Char-32><Space><Bar> \\|<Char-0x20><Char-32><Space> <Bar>')
+ eq({space_table}, meths.get_keymap('n'))
+ end)
+end)
diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua
new file mode 100644
index 0000000000..d99c26b6c2
--- /dev/null
+++ b/test/functional/api/proc_spec.lua
@@ -0,0 +1,81 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local eq = helpers.eq
+local funcs = helpers.funcs
+local iswin = helpers.iswin
+local nvim_argv = helpers.nvim_argv
+local ok = helpers.ok
+local request = helpers.request
+local retry = helpers.retry
+local NIL = helpers.NIL
+
+describe('api', function()
+ before_each(clear)
+
+ describe('nvim_get_proc_children', function()
+ it('returns child process ids', function()
+ local this_pid = funcs.getpid()
+
+ local job1 = funcs.jobstart(nvim_argv)
+ retry(nil, nil, function()
+ eq(1, #request('nvim_get_proc_children', this_pid))
+ end)
+
+ local job2 = funcs.jobstart(nvim_argv)
+ retry(nil, nil, function()
+ eq(2, #request('nvim_get_proc_children', this_pid))
+ end)
+
+ funcs.jobstop(job1)
+ retry(nil, nil, function()
+ eq(1, #request('nvim_get_proc_children', this_pid))
+ end)
+
+ funcs.jobstop(job2)
+ retry(nil, nil, function()
+ eq(0, #request('nvim_get_proc_children', this_pid))
+ end)
+ end)
+
+ it('validates input', function()
+ local status, rv = pcall(request, "nvim_get_proc_children", -1)
+ eq(false, status)
+ eq("Invalid pid: -1", string.match(rv, "Invalid.*"))
+
+ status, rv = pcall(request, "nvim_get_proc_children", 0)
+ eq(false, status)
+ eq("Invalid pid: 0", string.match(rv, "Invalid.*"))
+
+ -- Assume PID 99999 does not exist.
+ status, rv = pcall(request, "nvim_get_proc_children", 99999)
+ eq(true, status)
+ eq({}, rv)
+ end)
+ end)
+
+ describe('nvim_get_proc', function()
+ it('returns process info', function()
+ local pid = funcs.getpid()
+ local pinfo = request('nvim_get_proc', pid)
+ eq((iswin() and 'nvim.exe' or 'nvim'), pinfo.name)
+ ok(pinfo.pid == pid)
+ ok(type(pinfo.ppid) == 'number' and pinfo.ppid ~= pid)
+ end)
+
+ it('validates input', function()
+ local status, rv = pcall(request, "nvim_get_proc", -1)
+ eq(false, status)
+ eq("Invalid pid: -1", string.match(rv, "Invalid.*"))
+
+ status, rv = pcall(request, "nvim_get_proc", 0)
+ eq(false, status)
+ eq("Invalid pid: 0", string.match(rv, "Invalid.*"))
+
+ -- Assume PID 99999 does not exist.
+ status, rv = pcall(request, "nvim_get_proc", 99999)
+ eq(true, status)
+ eq(NIL, rv)
+ end)
+ end)
+end)
diff --git a/test/functional/api/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua
index 78639d7ed7..1d64ae7103 100644
--- a/test/functional/api/server_notifications_spec.lua
+++ b/test/functional/api/server_notifications_spec.lua
@@ -1,7 +1,7 @@
local helpers = require('test.functional.helpers')(after_each)
-local eq, clear, eval, execute, nvim, next_message =
- helpers.eq, helpers.clear, helpers.eval, helpers.execute, helpers.nvim,
- helpers.next_message
+local eq, clear, eval, command, nvim, next_msg =
+ helpers.eq, helpers.clear, helpers.eval, helpers.command, helpers.nvim,
+ helpers.next_msg
local meths = helpers.meths
describe('notify', function()
@@ -15,10 +15,10 @@ describe('notify', function()
describe('passing a valid channel id', function()
it('sends the notification/args to the corresponding channel', function()
eval('rpcnotify('..channel..', "test-event", 1, 2, 3)')
- eq({'notification', 'test-event', {1, 2, 3}}, next_message())
- execute('au FileType lua call rpcnotify('..channel..', "lua!")')
- execute('set filetype=lua')
- eq({'notification', 'lua!', {}}, next_message())
+ eq({'notification', 'test-event', {1, 2, 3}}, next_msg())
+ command('au FileType lua call rpcnotify('..channel..', "lua!")')
+ command('set filetype=lua')
+ eq({'notification', 'lua!', {}}, next_msg())
end)
end)
@@ -28,13 +28,13 @@ describe('notify', function()
eval('rpcnotify(0, "event1", 1, 2, 3)')
eval('rpcnotify(0, "event2", 4, 5, 6)')
eval('rpcnotify(0, "event2", 7, 8, 9)')
- eq({'notification', 'event2', {4, 5, 6}}, next_message())
- eq({'notification', 'event2', {7, 8, 9}}, next_message())
+ eq({'notification', 'event2', {4, 5, 6}}, next_msg())
+ eq({'notification', 'event2', {7, 8, 9}}, next_msg())
nvim('unsubscribe', 'event2')
nvim('subscribe', 'event1')
eval('rpcnotify(0, "event2", 10, 11, 12)')
eval('rpcnotify(0, "event1", 13, 14, 15)')
- eq({'notification', 'event1', {13, 14, 15}}, next_message())
+ eq({'notification', 'event1', {13, 14, 15}}, next_msg())
end)
it('does not crash for deeply nested variable', function()
@@ -42,7 +42,7 @@ describe('notify', function()
local nest_level = 1000
meths.command(('call map(range(%u), "extend(g:, {\'l\': [g:l]})")'):format(nest_level - 1))
eval('rpcnotify('..channel..', "event", g:l)')
- local msg = next_message()
+ local msg = next_msg()
eq('notification', msg[1])
eq('event', msg[2])
local act_ret = msg[3]
diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua
index 76a335a8f4..18229b54ff 100644
--- a/test/functional/api/server_requests_spec.lua
+++ b/test/functional/api/server_requests_spec.lua
@@ -1,12 +1,16 @@
-- Test server -> client RPC scenarios. Note: unlike `rpcnotify`, to evaluate
-- `rpcrequest` calls we need the client event loop to be running.
local helpers = require('test.functional.helpers')(after_each)
+local Paths = require('test.config.paths')
+
local clear, nvim, eval = helpers.clear, helpers.nvim, helpers.eval
local eq, neq, run, stop = helpers.eq, helpers.neq, helpers.run, helpers.stop
local nvim_prog, command, funcs = helpers.nvim_prog, helpers.command, helpers.funcs
-local source, next_message = helpers.source, helpers.next_message
+local source, next_msg = helpers.source, helpers.next_msg
local ok = helpers.ok
local meths = helpers.meths
+local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv
+local set_session = helpers.set_session
describe('server -> client', function()
local cid
@@ -16,6 +20,22 @@ describe('server -> client', function()
cid = nvim('get_api_info')[1]
end)
+ it('handles unexpected closed stream while preparing RPC response', function()
+ source([[
+ let g:_nvim_args = [v:progpath, '--embed', '-n', '-u', 'NONE', '-i', 'NONE', ]
+ let ch1 = jobstart(g:_nvim_args, {'rpc': v:true})
+ let child1_ch = rpcrequest(ch1, "nvim_get_api_info")[0]
+ call rpcnotify(ch1, 'nvim_eval', 'rpcrequest('.child1_ch.', "nvim_get_api_info")')
+
+ let ch2 = jobstart(g:_nvim_args, {'rpc': v:true})
+ let child2_ch = rpcrequest(ch2, "nvim_get_api_info")[0]
+ call rpcnotify(ch2, 'nvim_eval', 'rpcrequest('.child2_ch.', "nvim_get_api_info")')
+
+ call jobstop(ch1)
+ ]])
+ eq(2, eval("1+1")) -- Still alive?
+ end)
+
describe('simple call', function()
it('works', function()
local function on_setup()
@@ -89,7 +109,28 @@ describe('server -> client', function()
end)
describe('requests and notifications interleaved', function()
- -- This tests that the following scenario won't happen:
+ it('does not delay notifications during pending request', function()
+ local received = false
+ local function on_setup()
+ eq("retval", funcs.rpcrequest(cid, "doit"))
+ stop()
+ end
+ local function on_request(method)
+ if method == "doit" then
+ funcs.rpcnotify(cid, "headsup")
+ eq(true,received)
+ return "retval"
+ end
+ end
+ local function on_notification(method)
+ if method == "headsup" then
+ received = true
+ end
+ end
+ run(on_request, on_notification, on_setup)
+ end)
+
+ -- This tests the following scenario:
--
-- server->client [request ] (1)
-- client->server [request ] (2) triggered by (1)
@@ -104,40 +145,42 @@ describe('server -> client', function()
-- only deals with one server->client request at a time. (In other words,
-- the client cannot send a response to a request that is not at the top
-- of nvim's request stack).
- --
- -- But above scenario shoudn't happen by the way notifications are dealt in
- -- Nvim: they are only sent after there are no pending server->client
- -- request(the request stack fully unwinds). So (3) is only sent after the
- -- client returns (6).
- it('works', function()
- local expected = 300
- local notified = 0
+ pending('will close connection if not properly synchronized', function()
local function on_setup()
eq('notified!', eval('rpcrequest('..cid..', "notify")'))
end
local function on_request(method)
- eq('notify', method)
- eq(1, eval('rpcnotify('..cid..', "notification")'))
- return 'notified!'
+ if method == "notify" then
+ eq(1, eval('rpcnotify('..cid..', "notification")'))
+ return 'notified!'
+ elseif method == "nested" then
+ -- do some busywork, so the first request will return
+ -- before this one
+ for _ = 1, 5 do
+ eq(2, eval("1+1"))
+ end
+ eq(1, eval('rpcnotify('..cid..', "nested_done")'))
+ return 'done!'
+ end
end
local function on_notification(method)
- eq('notification', method)
- if notified == expected then
- stop()
- return
+ if method == "notification" then
+ eq('done!', eval('rpcrequest('..cid..', "nested")'))
+ elseif method == "nested_done" then
+ -- this should never have been sent
+ ok(false)
end
- notified = notified + 1
- eq('notified!', eval('rpcrequest('..cid..', "notify")'))
end
run(on_request, on_notification, on_setup)
- eq(expected, notified)
+ -- ignore disconnect failure, otherwise detected by after_each
+ clear()
end)
end)
- describe('when the client is a recursive vim instance', function()
+ describe('recursive (child) nvim client', function()
if os.getenv("TRAVIS") and helpers.os_name() == "osx" then
-- XXX: Hangs Travis macOS since e9061117a5b8f195c3f26a5cb94e18ddd7752d86.
pending("[Hangs on Travis macOS. #5002]", function() end)
@@ -151,7 +194,7 @@ describe('server -> client', function()
after_each(function() command('call rpcstop(vim)') end)
- it('can send/recieve notifications and make requests', function()
+ it('can send/receive notifications and make requests', function()
nvim('command', "call rpcnotify(vim, 'vim_set_current_line', 'SOME TEXT')")
-- Wait for the notification to complete.
@@ -184,7 +227,7 @@ describe('server -> client', function()
end)
end)
- describe('when using jobstart', function()
+ describe('jobstart()', function()
local jobid
before_each(function()
local channel = nvim('get_api_info')[1]
@@ -200,7 +243,7 @@ describe('server -> client', function()
\ 'rpc': v:true
\ }
]])
- local lua_prog = arg[-1]
+ local lua_prog = Paths.test_lua_prg
meths.set_var("args", {lua_prog, 'test/functional/api/rpc_fixture.lua'})
jobid = eval("jobstart(g:args, g:job_opts)")
neq(0, 'jobid')
@@ -215,12 +258,105 @@ describe('server -> client', function()
it('rpc and text stderr can be combined', function()
eq("ok",funcs.rpcrequest(jobid, "poll"))
funcs.rpcnotify(jobid, "ping")
- eq({'notification', 'pong', {}}, next_message())
+ eq({'notification', 'pong', {}}, next_msg())
eq("done!",funcs.rpcrequest(jobid, "write_stderr", "fluff\n"))
- eq({'notification', 'stderr', {0, {'fluff', ''}}}, next_message())
+ eq({'notification', 'stderr', {0, {'fluff', ''}}}, next_msg())
funcs.rpcrequest(jobid, "exit")
- eq({'notification', 'exit', {0, 0}}, next_message())
+ eq({'notification', 'stderr', {0, {''}}}, next_msg())
+ eq({'notification', 'exit', {0, 0}}, next_msg())
+ end)
+ end)
+
+ describe('connecting to another (peer) nvim', function()
+ local function connect_test(server, mode, address)
+ local serverpid = funcs.getpid()
+ local client = spawn(nvim_argv)
+ set_session(client, true)
+ local clientpid = funcs.getpid()
+ neq(serverpid, clientpid)
+ local id = funcs.sockconnect(mode, address, {rpc=true})
+ ok(id > 0)
+
+ funcs.rpcrequest(id, 'nvim_set_current_line', 'hello')
+ local client_id = funcs.rpcrequest(id, 'nvim_get_api_info')[1]
+
+ set_session(server, true)
+ eq(serverpid, funcs.getpid())
+ eq('hello', meths.get_current_line())
+
+ -- method calls work both ways
+ funcs.rpcrequest(client_id, 'nvim_set_current_line', 'howdy!')
+ eq(id, funcs.rpcrequest(client_id, 'nvim_get_api_info')[1])
+
+ set_session(client, true)
+ eq(clientpid, funcs.getpid())
+ eq('howdy!', meths.get_current_line())
+
+ server:close()
+ client:close()
+ end
+
+ it('via named pipe', function()
+ local server = spawn(nvim_argv)
+ set_session(server)
+ local address = funcs.serverlist()[1]
+ local first = string.sub(address,1,1)
+ ok(first == '/' or first == '\\')
+ connect_test(server, 'pipe', address)
+ end)
+
+ it('via ipv4 address', function()
+ local server = spawn(nvim_argv)
+ set_session(server)
+ local address = funcs.serverstart("127.0.0.1:")
+ if #address == 0 then
+ pending('no ipv4 stack', function() end)
+ return
+ end
+ eq('127.0.0.1:', string.sub(address,1,10))
+ connect_test(server, 'tcp', address)
+ end)
+
+ it('via ipv6 address', function()
+ local server = spawn(nvim_argv)
+ set_session(server)
+ local address = funcs.serverstart('::1:')
+ if #address == 0 then
+ pending('no ipv6 stack', function() end)
+ return
+ end
+ eq('::1:', string.sub(address,1,4))
+ connect_test(server, 'tcp', address)
+ end)
+
+ it('via hostname', function()
+ local server = spawn(nvim_argv)
+ set_session(server)
+ local address = funcs.serverstart("localhost:")
+ eq('localhost:', string.sub(address,1,10))
+ connect_test(server, 'tcp', address)
end)
end)
+ describe('connecting to its own pipe address', function()
+ it('does not deadlock', function()
+ if not os.getenv("TRAVIS") and helpers.os_name() == "osx" then
+ -- It does, in fact, deadlock on QuickBuild. #6851
+ pending("deadlocks on QuickBuild", function() end)
+ return
+ end
+ local address = funcs.serverlist()[1]
+ local first = string.sub(address,1,1)
+ ok(first == '/' or first == '\\')
+ local serverpid = funcs.getpid()
+
+ local id = funcs.sockconnect('pipe', address, {rpc=true})
+
+ funcs.rpcrequest(id, 'nvim_set_current_line', 'hello')
+ eq('hello', meths.get_current_line())
+ eq(serverpid, funcs.rpcrequest(id, "nvim_eval", "getpid()"))
+
+ eq(id, funcs.rpcrequest(id, 'nvim_get_api_info')[1])
+ end)
+ end)
end)
diff --git a/test/functional/api/tabpage_spec.lua b/test/functional/api/tabpage_spec.lua
index d7ef53a88f..260a91a80c 100644
--- a/test/functional/api/tabpage_spec.lua
+++ b/test/functional/api/tabpage_spec.lua
@@ -34,7 +34,7 @@ describe('api/tabpage', function()
eq(1, funcs.exists('t:lua'))
curtabmeths.del_var('lua')
eq(0, funcs.exists('t:lua'))
- eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curtabmeths.del_var, 'lua'))
+ eq({false, 'Key does not exist: lua'}, meth_pcall(curtabmeths.del_var, 'lua'))
curtabmeths.set_var('lua', 1)
command('lockvar t:lua')
eq({false, 'Key is locked: lua'}, meth_pcall(curtabmeths.del_var, 'lua'))
diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua
new file mode 100644
index 0000000000..b028a50b02
--- /dev/null
+++ b/test/functional/api/ui_spec.lua
@@ -0,0 +1,37 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local eq = helpers.eq
+local eval = helpers.eval
+local expect_err = helpers.expect_err
+local meths = helpers.meths
+local request = helpers.request
+
+describe('nvim_ui_attach()', function()
+ before_each(function()
+ clear()
+ end)
+ it('handles very large width/height #2180', function()
+ local screen = Screen.new(999, 999)
+ screen:attach()
+ eq(999, eval('&lines'))
+ eq(999, eval('&columns'))
+ end)
+ it('invalid option returns error', function()
+ expect_err('No such UI option: foo',
+ meths.ui_attach, 80, 24, { foo={'foo'} })
+ end)
+ it('validates channel arg', function()
+ expect_err('UI not attached to channel: 1',
+ request, 'nvim_ui_try_resize', 40, 10)
+ expect_err('UI not attached to channel: 1',
+ request, 'nvim_ui_set_option', 'rgb', true)
+ expect_err('UI not attached to channel: 1',
+ request, 'nvim_ui_detach')
+
+ local screen = Screen.new()
+ screen:attach({rgb=false})
+ expect_err('UI already attached to channel: 1',
+ request, 'nvim_ui_attach', 40, 10, { rgb=false })
+ end)
+end)
diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua
index d23f058f69..7bf54c0d1e 100644
--- a/test/functional/api/version_spec.lua
+++ b/test/functional/api/version_spec.lua
@@ -1,6 +1,7 @@
local helpers = require('test.functional.helpers')(after_each)
local mpack = require('mpack')
local clear, funcs, eq = helpers.clear, helpers.funcs, helpers.eq
+local call = helpers.call
local function read_mpack_file(fname)
local fd = io.open(fname, 'rb')
@@ -18,7 +19,7 @@ describe("api_info()['version']", function()
before_each(clear)
it("returns API level", function()
- local version = helpers.call('api_info')['version']
+ local version = call('api_info')['version']
local current = version['api_level']
local compat = version['api_compatible']
eq("number", type(current))
@@ -27,7 +28,7 @@ describe("api_info()['version']", function()
end)
it("returns Nvim version", function()
- local version = helpers.call('api_info')['version']
+ local version = call('api_info')['version']
local major = version['major']
local minor = version['minor']
local patch = version['patch']
@@ -147,3 +148,14 @@ describe("api functions", function()
end)
end)
+
+describe("ui_options in metadata", function()
+ it('are correct', function()
+ -- TODO(bfredl) once a release freezes this into metadata,
+ -- instead check that all old options are present
+ local api = helpers.call('api_info')
+ local options = api.ui_options
+ eq({'rgb', 'ext_cmdline', 'ext_popupmenu',
+ 'ext_tabline', 'ext_wildmenu'}, options)
+ end)
+end)
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 8f9f155110..718294d941 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -1,5 +1,7 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
+local global_helpers = require('test.helpers')
+
local NIL = helpers.NIL
local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq
local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed
@@ -9,6 +11,11 @@ local funcs = helpers.funcs
local request = helpers.request
local meth_pcall = helpers.meth_pcall
local command = helpers.command
+local iswin = helpers.iswin
+
+local intchar2lua = global_helpers.intchar2lua
+local format_string = global_helpers.format_string
+local mergedicts_copy = global_helpers.mergedicts_copy
describe('api', function()
before_each(clear)
@@ -31,7 +38,7 @@ describe('api', function()
os.remove(fname)
end)
- it("VimL error: fails (VimL error), does NOT update v:errmsg", function()
+ it("parse error: fails (specific error), does NOT update v:errmsg", function()
-- Most API methods return generic errors (or no error) if a VimL
-- expression fails; nvim_command returns the VimL error details.
local status, rv = pcall(nvim, "command", "bogus_command")
@@ -39,6 +46,85 @@ describe('api', function()
eq("E492:", string.match(rv, "E%d*:")) -- VimL error was returned.
eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated.
end)
+
+ it("runtime error: fails (specific error)", function()
+ local status, rv = pcall(nvim, "command_output", "buffer 23487")
+ eq(false, status) -- nvim_command() failed.
+ eq("E86: Buffer 23487 does not exist", string.match(rv, "E%d*:.*"))
+ eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated.
+ end)
+ end)
+
+ describe('nvim_command_output', function()
+ it('does not induce hit-enter prompt', function()
+ -- Induce a hit-enter prompt use nvim_input (non-blocking).
+ nvim('command', 'set cmdheight=1')
+ nvim('input', [[:echo "hi\nhi2"<CR>]])
+
+ -- Verify hit-enter prompt.
+ eq({mode='r', blocking=true}, nvim("get_mode"))
+ nvim('input', [[<C-c>]])
+
+ -- Verify NO hit-enter prompt.
+ nvim('command_output', [[echo "hi\nhi2"]])
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end)
+
+ it('captures command output', function()
+ eq('this is\nspinal tap',
+ nvim('command_output', [[echo "this is\nspinal tap"]]))
+ eq('no line ending!',
+ nvim('command_output', [[echon "no line ending!"]]))
+ end)
+
+ it('captures empty command output', function()
+ eq('', nvim('command_output', 'echo'))
+ end)
+
+ it('captures single-char command output', function()
+ eq('x', nvim('command_output', 'echo "x"'))
+ end)
+
+ it('captures multiple commands', function()
+ eq('foo\n 1 %a "[No Name]" line 1',
+ nvim('command_output', 'echo "foo" | ls'))
+ end)
+
+ it('captures nested execute()', function()
+ eq('\nnested1\nnested2\n 1 %a "[No Name]" line 1',
+ nvim('command_output',
+ [[echo execute('echo "nested1\nnested2"') | ls]]))
+ end)
+
+ it('captures nested nvim_command_output()', function()
+ eq('nested1\nnested2\n 1 %a "[No Name]" line 1',
+ nvim('command_output',
+ [[echo nvim_command_output('echo "nested1\nnested2"') | ls]]))
+ end)
+
+ it('returns shell |:!| output', function()
+ local win_lf = iswin() and '\r' or ''
+ eq(':!echo foo\r\n\nfoo'..win_lf..'\n', nvim('command_output', [[!echo foo]]))
+ end)
+
+ it("parse error: fails (specific error), does NOT update v:errmsg", function()
+ local status, rv = pcall(nvim, "command_output", "bogus commannnd")
+ eq(false, status) -- nvim_command_output() failed.
+ eq("E492: Not an editor command: bogus commannnd",
+ string.match(rv, "E%d*:.*"))
+ eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated.
+ -- Verify NO hit-enter prompt.
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end)
+
+ it("runtime error: fails (specific error)", function()
+ local status, rv = pcall(nvim, "command_output", "buffer 42")
+ eq(false, status) -- nvim_command_output() failed.
+ eq("E86: Buffer 42 does not exist", string.match(rv, "E%d*:.*"))
+ eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated.
+ -- Verify NO hit-enter prompt.
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end)
end)
describe('nvim_eval', function()
@@ -81,6 +167,36 @@ describe('api', function()
end)
end)
+ describe('nvim_execute_lua', function()
+ it('works', function()
+ meths.execute_lua('vim.api.nvim_set_var("test", 3)', {})
+ eq(3, meths.get_var('test'))
+
+ eq(17, meths.execute_lua('a, b = ...\nreturn a + b', {10,7}))
+
+ eq(NIL, meths.execute_lua('function xx(a,b)\nreturn a..b\nend',{}))
+ eq("xy", meths.execute_lua('return xx(...)', {'x','y'}))
+ end)
+
+ it('reports errors', function()
+ eq({false, 'Error loading lua: [string "<nvim>"]:1: '..
+ "'=' expected near '+'"},
+ meth_pcall(meths.execute_lua, 'a+*b', {}))
+
+ eq({false, 'Error loading lua: [string "<nvim>"]:1: '..
+ "unexpected symbol near '1'"},
+ meth_pcall(meths.execute_lua, '1+2', {}))
+
+ eq({false, 'Error loading lua: [string "<nvim>"]:1: '..
+ "unexpected symbol"},
+ meth_pcall(meths.execute_lua, 'aa=bb\0', {}))
+
+ eq({false, 'Error executing lua: [string "<nvim>"]:1: '..
+ "attempt to call global 'bork' (a nil value)"},
+ meth_pcall(meths.execute_lua, 'bork()', {}))
+ end)
+ end)
+
describe('nvim_input', function()
it("VimL error: does NOT fail, updates v:errmsg", function()
local status, _ = pcall(nvim, "input", ":call bogus_fn()<CR>")
@@ -119,7 +235,7 @@ describe('api', function()
eq(1, funcs.exists('g:lua'))
meths.del_var('lua')
eq(0, funcs.exists('g:lua'))
- eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(meths.del_var, 'lua'))
+ eq({false, 'Key does not exist: lua'}, meth_pcall(meths.del_var, 'lua'))
meths.set_var('lua', 1)
command('lockvar lua')
eq({false, 'Key is locked: lua'}, meth_pcall(meths.del_var, 'lua'))
@@ -221,6 +337,170 @@ describe('api', function()
end)
end)
+ describe('nvim_get_mode', function()
+ it("during normal-mode `g` returns blocking=true", function()
+ nvim("input", "o") -- add a line
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+ nvim("input", [[<C-\><C-N>]])
+ eq(2, nvim("eval", "line('.')"))
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+
+ nvim("input", "g")
+ eq({mode='n', blocking=true}, nvim("get_mode"))
+
+ nvim("input", "k") -- complete the operator
+ eq(1, nvim("eval", "line('.')")) -- verify the completed operator
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end)
+
+ it("returns the correct result multiple consecutive times", function()
+ for _ = 1,5 do
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end
+ nvim("input", "g")
+ for _ = 1,4 do
+ eq({mode='n', blocking=true}, nvim("get_mode"))
+ end
+ nvim("input", "g")
+ for _ = 1,7 do
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end
+ end)
+
+ it("during normal-mode CTRL-W, returns blocking=true", function()
+ nvim("input", "<C-W>")
+ eq({mode='n', blocking=true}, nvim("get_mode"))
+
+ nvim("input", "s") -- complete the operator
+ eq(2, nvim("eval", "winnr('$')")) -- verify the completed operator
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end)
+
+ it("during press-enter prompt returns blocking=true", function()
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ command("echom 'msg1'")
+ command("echom 'msg2'")
+ command("echom 'msg3'")
+ command("echom 'msg4'")
+ command("echom 'msg5'")
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ nvim("input", ":messages<CR>")
+ eq({mode='r', blocking=true}, nvim("get_mode"))
+ end)
+
+ it("during getchar() returns blocking=false", function()
+ nvim("input", ":let g:test_input = nr2char(getchar())<CR>")
+ -- Events are enabled during getchar(), RPC calls are *not* blocked. #5384
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ eq(0, nvim("eval", "exists('g:test_input')"))
+ nvim("input", "J")
+ eq("J", nvim("eval", "g:test_input"))
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end)
+
+ -- TODO: bug #6247#issuecomment-286403810
+ it("batched with input", function()
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ command("echom 'msg1'")
+ command("echom 'msg2'")
+ command("echom 'msg3'")
+ command("echom 'msg4'")
+ command("echom 'msg5'")
+
+ local req = {
+ {'nvim_get_mode', {}},
+ {'nvim_input', {':messages<CR>'}},
+ {'nvim_get_mode', {}},
+ {'nvim_eval', {'1'}},
+ }
+ eq({ { {mode='n', blocking=false},
+ 13,
+ {mode='n', blocking=false}, -- TODO: should be blocked=true ?
+ 1 },
+ NIL}, meths.call_atomic(req))
+ eq({mode='r', blocking=true}, nvim("get_mode"))
+ end)
+ it("during insert-mode map-pending, returns blocking=true #6166", function()
+ command("inoremap xx foo")
+ nvim("input", "ix")
+ eq({mode='i', blocking=true}, nvim("get_mode"))
+ end)
+ it("during normal-mode gU, returns blocking=false #6166", function()
+ nvim("input", "gu")
+ eq({mode='no', blocking=false}, nvim("get_mode"))
+ end)
+ end)
+
+ describe('RPC (K_EVENT) #6166', function()
+ it('does not complete ("interrupt") normal-mode operator-pending', function()
+ helpers.insert([[
+ FIRST LINE
+ SECOND LINE]])
+ nvim('input', 'gg')
+ nvim('input', 'gu')
+ -- Make any RPC request (can be non-async: op-pending does not block).
+ nvim('get_current_buf')
+ -- Buffer should not change.
+ helpers.expect([[
+ FIRST LINE
+ SECOND LINE]])
+ -- Now send input to complete the operator.
+ nvim('input', 'j')
+ helpers.expect([[
+ first line
+ second line]])
+ end)
+
+ it('does not complete ("interrupt") `d` #3732', function()
+ local screen = Screen.new(20, 4)
+ screen:attach()
+ command('set listchars=eol:$')
+ command('set list')
+ feed('ia<cr>b<cr>c<cr><Esc>kkk')
+ feed('d')
+ -- Make any RPC request (can be non-async: op-pending does not block).
+ nvim('get_current_buf')
+ screen:expect([[
+ ^a$ |
+ b$ |
+ c$ |
+ |
+ ]])
+ end)
+
+ it('does not complete ("interrupt") normal-mode map-pending', function()
+ command("nnoremap dd :let g:foo='it worked...'<CR>")
+ helpers.insert([[
+ FIRST LINE
+ SECOND LINE]])
+ nvim('input', 'gg')
+ nvim('input', 'd')
+ -- Make any RPC request (must be async, because map-pending blocks).
+ nvim('get_api_info')
+ -- Send input to complete the mapping.
+ nvim('input', 'd')
+ helpers.expect([[
+ FIRST LINE
+ SECOND LINE]])
+ eq('it worked...', helpers.eval('g:foo'))
+ end)
+ it('does not complete ("interrupt") insert-mode map-pending', function()
+ command('inoremap xx foo')
+ command('set timeoutlen=9999')
+ helpers.insert([[
+ FIRST LINE
+ SECOND LINE]])
+ nvim('input', 'ix')
+ -- Make any RPC request (must be async, because map-pending blocks).
+ nvim('get_api_info')
+ -- Send input to complete the mapping.
+ nvim('input', 'x')
+ helpers.expect([[
+ FIRST LINE
+ SECOND LINfooE]])
+ end)
+ end)
+
describe('nvim_replace_termcodes', function()
it('escapes K_SPECIAL as K_SPECIAL KS_SPECIAL KE_FILLER', function()
eq('\128\254X', helpers.nvim('replace_termcodes', '\128', true, true, true))
@@ -241,6 +521,27 @@ describe('api', function()
eq('\128\253\44', helpers.nvim('replace_termcodes',
'<LeftMouse>', true, true, true))
end)
+
+ it('converts keycodes', function()
+ eq('\nx\27x\rx<x', helpers.nvim('replace_termcodes',
+ '<NL>x<Esc>x<CR>x<lt>x', true, true, true))
+ end)
+
+ it('does not convert keycodes if special=false', function()
+ eq('<NL>x<Esc>x<CR>x<lt>x', helpers.nvim('replace_termcodes',
+ '<NL>x<Esc>x<CR>x<lt>x', true, true, false))
+ end)
+
+ it('does not crash when transforming an empty string', function()
+ -- Actually does not test anything, because current code will use NULL for
+ -- an empty string.
+ --
+ -- Problem here is that if String argument has .data in allocated memory
+ -- then `return str` in vim_replace_termcodes body will make Neovim free
+ -- `str.data` twice: once when freeing arguments, then when freeing return
+ -- value.
+ eq('', meths.replace_termcodes('', true, true, true))
+ end)
end)
describe('nvim_feedkeys', function()
@@ -249,13 +550,13 @@ describe('api', function()
-- notice the special char(…) \xe2\80\xa6
nvim('feedkeys', ':let x1="…"\n', '', true)
- -- Both replace_termcodes and feedkeys escape \x80
+ -- Both nvim_replace_termcodes and nvim_feedkeys escape \x80
local inp = helpers.nvim('replace_termcodes', ':let x2="…"<CR>', true, true, true)
- nvim('feedkeys', inp, '', true)
+ nvim('feedkeys', inp, '', true) -- escape_csi=true
- -- Disabling CSI escaping in feedkeys
+ -- nvim_feedkeys with CSI escaping disabled
inp = helpers.nvim('replace_termcodes', ':let x3="…"<CR>', true, true, true)
- nvim('feedkeys', inp, '', false)
+ nvim('feedkeys', inp, '', false) -- escape_csi=false
helpers.stop()
end
@@ -280,7 +581,8 @@ describe('api', function()
screen:set_default_attr_ids({
[0] = {bold=true, foreground=Screen.colors.Blue},
[1] = {foreground = Screen.colors.White, background = Screen.colors.Red},
- [2] = {bold = true, foreground = Screen.colors.SeaGreen}
+ [2] = {bold = true, foreground = Screen.colors.SeaGreen},
+ [3] = {bold = true, reverse = true},
})
end)
@@ -301,11 +603,11 @@ describe('api', function()
it('shows return prompt when more than &cmdheight lines', function()
nvim_async('err_write', 'something happened\nvery bad\n')
screen:expect([[
+ |
{0:~ }|
{0:~ }|
{0:~ }|
- {0:~ }|
- {0:~ }|
+ {3: }|
{1:something happened} |
{1:very bad} |
{2:Press ENTER or type command to continue}^ |
@@ -315,9 +617,9 @@ describe('api', function()
it('shows return prompt after all lines are shown', function()
nvim_async('err_write', 'FAILURE\nERROR\nEXCEPTION\nTRACEBACK\n')
screen:expect([[
+ |
{0:~ }|
- {0:~ }|
- {0:~ }|
+ {3: }|
{1:FAILURE} |
{1:ERROR} |
{1:EXCEPTION} |
@@ -345,11 +647,11 @@ describe('api', function()
-- shows up to &cmdheight lines
nvim_async('err_write', 'more fail\ntoo fail\n')
screen:expect([[
+ |
{0:~ }|
{0:~ }|
{0:~ }|
- {0:~ }|
- {0:~ }|
+ {3: }|
{1:more fail} |
{1:too fail} |
{2:Press ENTER or type command to continue}^ |
@@ -412,7 +714,7 @@ describe('api', function()
eq(5, meths.get_var('avar'))
end)
- it('throws error on malformated arguments', function()
+ it('throws error on malformed arguments', function()
local req = {
{'nvim_set_var', {'avar', 1}},
{'nvim_set_var'},
@@ -439,23 +741,303 @@ describe('api', function()
}
status, err = pcall(meths.call_atomic, req)
eq(false, status)
- ok(err:match('args must be Array') ~= nil)
+ ok(err:match('Args must be Array') ~= nil)
-- call before was done, but not after
eq(1, meths.get_var('avar'))
eq({''}, meths.buf_get_lines(0, 0, -1, true))
end)
end)
+ describe('nvim_list_runtime_paths', function()
+ it('returns nothing with empty &runtimepath', function()
+ meths.set_option('runtimepath', '')
+ eq({}, meths.list_runtime_paths())
+ end)
+ it('returns single runtimepath', function()
+ meths.set_option('runtimepath', 'a')
+ eq({'a'}, meths.list_runtime_paths())
+ end)
+ it('returns two runtimepaths', function()
+ meths.set_option('runtimepath', 'a,b')
+ eq({'a', 'b'}, meths.list_runtime_paths())
+ end)
+ it('returns empty strings when appropriate', function()
+ meths.set_option('runtimepath', 'a,,b')
+ eq({'a', '', 'b'}, meths.list_runtime_paths())
+ meths.set_option('runtimepath', ',a,b')
+ eq({'', 'a', 'b'}, meths.list_runtime_paths())
+ meths.set_option('runtimepath', 'a,b,')
+ eq({'a', 'b', ''}, meths.list_runtime_paths())
+ end)
+ it('truncates too long paths', function()
+ local long_path = ('/a'):rep(8192)
+ meths.set_option('runtimepath', long_path)
+ local paths_list = meths.list_runtime_paths()
+ neq({long_path}, paths_list)
+ eq({long_path:sub(1, #(paths_list[1]))}, paths_list)
+ end)
+ end)
+
it('can throw exceptions', function()
local status, err = pcall(nvim, 'get_option', 'invalid-option')
eq(false, status)
ok(err:match('Invalid option name') ~= nil)
end)
- it("doesn't leak memory on incorrect argument types", function()
+ it('does not truncate error message <1 MB #5984', function()
+ local very_long_name = 'A'..('x'):rep(10000)..'Z'
+ local status, err = pcall(nvim, 'get_option', very_long_name)
+ eq(false, status)
+ eq(very_long_name, err:match('Ax+Z?'))
+ end)
+
+ it("does not leak memory on incorrect argument types", function()
local status, err = pcall(nvim, 'set_current_dir',{'not', 'a', 'dir'})
eq(false, status)
ok(err:match(': Wrong type for argument 1, expecting String') ~= nil)
end)
+ describe('nvim_parse_expression', function()
+ before_each(function()
+ meths.set_option('isident', '')
+ end)
+ local function simplify_east_api_node(line, east_api_node)
+ if east_api_node == NIL then
+ return nil
+ end
+ if east_api_node.children then
+ for k, v in pairs(east_api_node.children) do
+ east_api_node.children[k] = simplify_east_api_node(line, v)
+ end
+ end
+ local typ = east_api_node.type
+ if typ == 'Register' then
+ typ = typ .. ('(name=%s)'):format(
+ tostring(intchar2lua(east_api_node.name)))
+ east_api_node.name = nil
+ elseif typ == 'PlainIdentifier' then
+ typ = typ .. ('(scope=%s,ident=%s)'):format(
+ tostring(intchar2lua(east_api_node.scope)), east_api_node.ident)
+ east_api_node.scope = nil
+ east_api_node.ident = nil
+ elseif typ == 'PlainKey' then
+ typ = typ .. ('(key=%s)'):format(east_api_node.ident)
+ east_api_node.ident = nil
+ elseif typ == 'Comparison' then
+ typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format(
+ east_api_node.cmp_type, east_api_node.invert and 1 or 0,
+ east_api_node.ccs_strategy)
+ east_api_node.ccs_strategy = nil
+ east_api_node.cmp_type = nil
+ east_api_node.invert = nil
+ elseif typ == 'Integer' then
+ typ = typ .. ('(val=%u)'):format(east_api_node.ivalue)
+ east_api_node.ivalue = nil
+ elseif typ == 'Float' then
+ typ = typ .. format_string('(val=%e)', east_api_node.fvalue)
+ east_api_node.fvalue = nil
+ elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then
+ typ = format_string('%s(val=%q)', typ, east_api_node.svalue)
+ east_api_node.svalue = nil
+ elseif typ == 'Option' then
+ typ = ('%s(scope=%s,ident=%s)'):format(
+ typ,
+ tostring(intchar2lua(east_api_node.scope)),
+ east_api_node.ident)
+ east_api_node.ident = nil
+ east_api_node.scope = nil
+ elseif typ == 'Environment' then
+ typ = ('%s(ident=%s)'):format(typ, east_api_node.ident)
+ east_api_node.ident = nil
+ elseif typ == 'Assignment' then
+ local aug = east_api_node.augmentation
+ if aug == '' then aug = 'Plain' end
+ typ = ('%s(%s)'):format(typ, aug)
+ east_api_node.augmentation = nil
+ end
+ typ = ('%s:%u:%u:%s'):format(
+ typ, east_api_node.start[1], east_api_node.start[2],
+ line:sub(east_api_node.start[2] + 1,
+ east_api_node.start[2] + 1 + east_api_node.len - 1))
+ assert(east_api_node.start[2] + east_api_node.len - 1 <= #line)
+ for k, _ in pairs(east_api_node.start) do
+ assert(({true, true})[k])
+ end
+ east_api_node.start = nil
+ east_api_node.type = nil
+ east_api_node.len = nil
+ local can_simplify = true
+ for _, _ in pairs(east_api_node) do
+ if can_simplify then can_simplify = false end
+ end
+ if can_simplify then
+ return typ
+ else
+ east_api_node[1] = typ
+ return east_api_node
+ end
+ end
+ local function simplify_east_api(line, east_api)
+ if east_api.error then
+ east_api.err = east_api.error
+ east_api.error = nil
+ east_api.err.msg = east_api.err.message
+ east_api.err.message = nil
+ end
+ if east_api.ast then
+ east_api.ast = {simplify_east_api_node(line, east_api.ast)}
+ if #east_api.ast == 0 then
+ east_api.ast = nil
+ end
+ end
+ if east_api.len == #line then
+ east_api.len = nil
+ end
+ return east_api
+ end
+ local function simplify_east_hl(line, east_hl)
+ for i, v in ipairs(east_hl) do
+ east_hl[i] = ('%s:%u:%u:%s'):format(
+ v[4],
+ v[1],
+ v[2],
+ line:sub(v[2] + 1, v[3]))
+ end
+ return east_hl
+ end
+ local FLAGS_TO_STR = {
+ [0] = "",
+ [1] = "m",
+ [2] = "E",
+ [3] = "mE",
+ [4] = "l",
+ [5] = "lm",
+ [6] = "lE",
+ [7] = "lmE",
+ }
+ local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs,
+ nz_flags_exps)
+ if type(str) ~= 'string' then
+ return
+ end
+ local zflags = opts.flags[1]
+ nz_flags_exps = nz_flags_exps or {}
+ for _, flags in ipairs(opts.flags) do
+ local err, msg = pcall(function()
+ local east_api = meths.parse_expression(str, FLAGS_TO_STR[flags], true)
+ local east_hl = east_api.highlight
+ east_api.highlight = nil
+ local ast = simplify_east_api(str, east_api)
+ local hls = simplify_east_hl(str, east_hl)
+ local exps = {
+ ast = exp_ast,
+ hl_fs = exp_highlighting_fs,
+ }
+ local add_exps = nz_flags_exps[flags]
+ if not add_exps and flags == 3 + zflags then
+ add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags]
+ end
+ if add_exps then
+ if add_exps.ast then
+ exps.ast = mergedicts_copy(exps.ast, add_exps.ast)
+ end
+ if add_exps.hl_fs then
+ exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs)
+ end
+ end
+ eq(exps.ast, ast)
+ if exp_highlighting_fs then
+ local exp_highlighting = {}
+ local next_col = 0
+ for i, h in ipairs(exps.hl_fs) do
+ exp_highlighting[i], next_col = h(next_col)
+ end
+ eq(exp_highlighting, hls)
+ end
+ end)
+ if not err then
+ if type(msg) == 'table' then
+ local merr, new_msg = pcall(
+ format_string, 'table error:\n%s\n\n(%r)', msg.message, msg)
+ if merr then
+ msg = new_msg
+ else
+ msg = format_string('table error without .message:\n(%r)',
+ msg)
+ end
+ elseif type(msg) ~= 'string' then
+ msg = format_string('non-string non-table error:\n%r', msg)
+ end
+ error(format_string('Error while processing test (%r, %s):\n%s',
+ str, FLAGS_TO_STR[flags], msg))
+ end
+ end
+ end
+ local function hl(group, str, shift)
+ return function(next_col)
+ local col = next_col + (shift or 0)
+ return (('%s:%u:%u:%s'):format(
+ 'Nvim' .. group,
+ 0,
+ col,
+ str)), (col + #str)
+ end
+ end
+ local function fmtn(typ, args, rest)
+ if (typ == 'UnknownFigure'
+ or typ == 'DictLiteral'
+ or typ == 'CurlyBracesIdentifier'
+ or typ == 'Lambda') then
+ return ('%s%s'):format(typ, rest)
+ elseif typ == 'DoubleQuotedString' or typ == 'SingleQuotedString' then
+ if args:sub(-4) == 'NULL' then
+ args = args:sub(1, -5) .. '""'
+ end
+ return ('%s(%s)%s'):format(typ, args, rest)
+ end
+ end
+ assert:set_parameter('TableFormatLevel', 1000000)
+ require('test.unit.viml.expressions.parser_tests')(
+ it, _check_parsing, hl, fmtn)
+ end)
+
+ describe('nvim_list_uis', function()
+ it('returns empty if --headless', function()
+ -- --embed implies --headless.
+ eq({}, nvim("list_uis"))
+ end)
+ it('returns attached UIs', function()
+ local screen = Screen.new(20, 4)
+ screen:attach()
+ local expected = {
+ {
+ ext_cmdline = false,
+ ext_popupmenu = false,
+ ext_tabline = false,
+ ext_wildmenu = false,
+ height = 4,
+ rgb = true,
+ width = 20,
+ }
+ }
+ eq(expected, nvim("list_uis"))
+
+ screen:detach()
+ screen = Screen.new(44, 99)
+ screen:attach({ rgb = false })
+ expected = {
+ {
+ ext_cmdline = false,
+ ext_popupmenu = false,
+ ext_tabline = false,
+ ext_wildmenu = false,
+ height = 99,
+ rgb = false,
+ width = 44,
+ }
+ }
+ eq(expected, nvim("list_uis"))
+ end)
+ end)
+
end)
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index deffc68994..8a65d3f71e 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -9,6 +9,7 @@ local funcs = helpers.funcs
local request = helpers.request
local NIL = helpers.NIL
local meth_pcall = helpers.meth_pcall
+local meths = helpers.meths
local command = helpers.command
-- check if str is visible at the beginning of some line
@@ -55,6 +56,12 @@ describe('api/win', function()
eq('typing\n some dumb text', curbuf_contents())
end)
+ it('does not leak memory when using invalid window ID with invalid pos',
+ function()
+ eq({false, 'Invalid window id'},
+ meth_pcall(meths.win_set_cursor, 1, {"b\na"}))
+ end)
+
it('updates the screen, and also when the window is unfocused', function()
insert("prologue")
feed('100o<esc>')
@@ -139,7 +146,7 @@ describe('api/win', function()
eq(1, funcs.exists('w:lua'))
curwinmeths.del_var('lua')
eq(0, funcs.exists('w:lua'))
- eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curwinmeths.del_var, 'lua'))
+ eq({false, 'Key does not exist: lua'}, meth_pcall(curwinmeths.del_var, 'lua'))
curwinmeths.set_var('lua', 1)
command('lockvar w:lua')
eq({false, 'Key is locked: lua'}, meth_pcall(curwinmeths.del_var, 'lua'))