eval: context: add ctx-family functions

This commit is contained in:
Abdelhakeem 2019-07-10 20:41:31 +02:00 committed by Justin M. Keyes
parent a80f691a6a
commit 691deca2e8
4 changed files with 418 additions and 0 deletions

View File

@ -26,6 +26,7 @@
#include "nvim/buffer.h" #include "nvim/buffer.h"
#include "nvim/channel.h" #include "nvim/channel.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/context.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/diff.h" #include "nvim/diff.h"
#include "nvim/edit.h" #include "nvim/edit.h"
@ -8014,6 +8015,112 @@ static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr)
(char_u *)prepend); (char_u *)prepend);
} }
/// "ctxget([{index}])" function
static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
size_t index = 0;
if (argvars[0].v_type == VAR_NUMBER) {
index = argvars[0].vval.v_number;
} else if (argvars[0].v_type != VAR_UNKNOWN) {
EMSG2(_(e_invarg2), "expected nothing or a Number as an argument");
return;
}
Context *ctx = ctx_get(index);
if (ctx == NULL) {
EMSG3(_(e_invargNval), "index", "out of bounds");
return;
}
Dictionary ctx_dict = ctx_to_dict(ctx);
Error err = ERROR_INIT;
object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
api_free_dictionary(ctx_dict);
api_clear_error(&err);
}
/// "ctxpop()" function
static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
if (!ctx_restore(NULL, kCtxAll)) {
EMSG(_("Context stack is empty"));
}
}
/// "ctxpush([{types}])" function
static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int types = kCtxAll;
if (argvars[0].v_type == VAR_LIST) {
types = 0;
TV_LIST_ITER(argvars[0].vval.v_list, li, {
typval_T *tv_li = TV_LIST_ITEM_TV(li);
if (tv_li->v_type == VAR_STRING) {
if (strequal((char *)tv_li->vval.v_string, "regs")) {
types |= kCtxRegs;
} else if (strequal((char *)tv_li->vval.v_string, "jumps")) {
types |= kCtxJumps;
} else if (strequal((char *)tv_li->vval.v_string, "buflist")) {
types |= kCtxBuflist;
} else if (strequal((char *)tv_li->vval.v_string, "gvars")) {
types |= kCtxGVars;
}
}
});
} else if (argvars[0].v_type != VAR_UNKNOWN) {
EMSG2(_(e_invarg2), "expected nothing or a List as an argument");
return;
}
ctx_save(NULL, types);
}
/// "ctxset({context}[, {index}])" function
static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
if (argvars[0].v_type != VAR_DICT) {
EMSG2(_(e_invarg2), "expected dictionary as first argument");
return;
}
size_t index = 0;
if (argvars[1].v_type == VAR_NUMBER) {
index = argvars[1].vval.v_number;
} else if (argvars[1].v_type != VAR_UNKNOWN) {
EMSG2(_(e_invarg2), "expected nothing or a Number as second argument");
return;
}
Context *ctx = ctx_get(index);
if (ctx == NULL) {
EMSG3(_(e_invargNval), "index", "out of bounds");
return;
}
int save_did_emsg = did_emsg;
did_emsg = false;
Dictionary dict = vim_to_object(&argvars[0]).data.dictionary;
Context tmp = CONTEXT_INIT;
ctx_from_dict(dict, &tmp);
if (did_emsg) {
ctx_free(&tmp);
} else {
ctx_free(ctx);
*ctx = tmp;
}
api_free_dictionary(dict);
did_emsg = save_did_emsg;
}
/// "ctxsize()" function
static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = ctx_size();
}
/// "cursor(lnum, col)" function, or /// "cursor(lnum, col)" function, or
/// "cursor(list)" /// "cursor(list)"
/// ///

View File

@ -74,6 +74,11 @@ return {
cosh={args=1, func="float_op_wrapper", data="&cosh"}, cosh={args=1, func="float_op_wrapper", data="&cosh"},
count={args={2, 4}}, count={args={2, 4}},
cscope_connection={args={0, 3}}, cscope_connection={args={0, 3}},
ctxget={args={0, 1}},
ctxpop={},
ctxpush={args={0, 1}},
ctxset={args={1, 2}},
ctxsize={},
cursor={args={1, 3}}, cursor={args={1, 3}},
deepcopy={args={1, 2}}, deepcopy={args={1, 2}},
delete={args={1,2}}, delete={args={1,2}},

View File

@ -14,6 +14,7 @@
#include "nvim/main.h" #include "nvim/main.h"
#include "nvim/buffer.h" #include "nvim/buffer.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/context.h"
#include "nvim/diff.h" #include "nvim/diff.h"
#include "nvim/eval.h" #include "nvim/eval.h"
#include "nvim/ex_cmds.h" #include "nvim/ex_cmds.h"
@ -672,6 +673,8 @@ void getout(int exitval)
garbage_collect(false); garbage_collect(false);
} }
free_ctx_stack();
mch_exit(exitval); mch_exit(exitval);
} }

View File

@ -0,0 +1,303 @@
local helpers = require('test.functional.helpers')(after_each)
local call = helpers.call
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local eval = helpers.eval
local expect_err = helpers.expect_err
local feed = helpers.feed
local map = helpers.map
local nvim = helpers.nvim
local parse_context = helpers.parse_context
local trim = helpers.trim
local write_file = helpers.write_file
describe('context functions', function()
local fname1 = 'Xtest-functional-eval-ctx1'
local fname2 = 'Xtest-functional-eval-ctx2'
local outofbounds =
'Vim:E475: Invalid value for argument index: out of bounds'
before_each(function()
clear()
write_file(fname1, "1\n2\n3")
write_file(fname2, "a\nb\nc")
end)
after_each(function()
os.remove(fname1)
os.remove(fname2)
end)
describe('ctxpush/ctxpop', function()
it('saves and restores registers properly', function()
local regs = {'1', '2', '3', 'a'}
local vals = {'1', '2', '3', 'hjkl'}
feed('i1<cr>2<cr>3<c-[>ddddddqahjklq')
eq(vals, map(function(r) return trim(call('getreg', r)) end, regs))
call('ctxpush')
call('ctxpush', {'regs'})
map(function(r) call('setreg', r, {}) end, regs)
eq({'', '', '', ''},
map(function(r) return trim(call('getreg', r)) end, regs))
call('ctxpop')
eq(vals, map(function(r) return trim(call('getreg', r)) end, regs))
map(function(r) call('setreg', r, {}) end, regs)
eq({'', '', '', ''},
map(function(r) return trim(call('getreg', r)) end, regs))
call('ctxpop')
eq(vals, map(function(r) return trim(call('getreg', r)) end, regs))
end)
it('saves and restores jumplist properly', function()
command('edit '..fname1)
feed('G')
feed('gg')
command('edit '..fname2)
local jumplist = call('getjumplist')
call('ctxpush')
call('ctxpush', {'jumps'})
command('clearjumps')
eq({{}, 0}, call('getjumplist'))
call('ctxpop')
eq(jumplist, call('getjumplist'))
command('clearjumps')
eq({{}, 0}, call('getjumplist'))
call('ctxpop')
eq(jumplist, call('getjumplist'))
end)
it('saves and restores buffer list properly', function()
command('edit '..fname1)
command('edit '..fname2)
command('edit TEST')
local buflist = call('map', call('getbufinfo'), 'v:val.name')
call('ctxpush')
call('ctxpush', {'buflist'})
command('%bwipeout')
eq({''}, call('map', call('getbufinfo'), 'v:val.name'))
call('ctxpop')
eq({'', unpack(buflist)}, call('map', call('getbufinfo'), 'v:val.name'))
command('%bwipeout')
eq({''}, call('map', call('getbufinfo'), 'v:val.name'))
call('ctxpop')
eq({'', unpack(buflist)}, call('map', call('getbufinfo'), 'v:val.name'))
end)
it('saves and restores global variables properly', function()
nvim('set_var', 'one', 1)
nvim('set_var', 'Two', 2)
nvim('set_var', 'THREE', 3)
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
call('ctxpush')
call('ctxpush', {'gvars'})
nvim('del_var', 'one')
nvim('del_var', 'Two')
nvim('del_var', 'THREE')
expect_err('E121: Undefined variable: g:one', eval, 'g:one')
expect_err('E121: Undefined variable: g:Two', eval, 'g:Two')
expect_err('E121: Undefined variable: g:THREE', eval, 'g:THREE')
call('ctxpop')
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
nvim('del_var', 'one')
nvim('del_var', 'Two')
nvim('del_var', 'THREE')
expect_err('E121: Undefined variable: g:one', eval, 'g:one')
expect_err('E121: Undefined variable: g:Two', eval, 'g:Two')
expect_err('E121: Undefined variable: g:THREE', eval, 'g:THREE')
call('ctxpop')
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
end)
it('errors out when context stack is empty', function()
local err = 'Vim:Context stack is empty'
expect_err(err, call, 'ctxpop')
expect_err(err, call, 'ctxpop')
call('ctxpush')
call('ctxpush')
call('ctxpop')
call('ctxpop')
expect_err(err, call, 'ctxpop')
end)
end)
describe('ctxsize()', function()
it('returns context stack size', function()
eq(0, call('ctxsize'))
call('ctxpush')
eq(1, call('ctxsize'))
call('ctxpush')
eq(2, call('ctxsize'))
call('ctxpush')
eq(3, call('ctxsize'))
call('ctxpop')
eq(2, call('ctxsize'))
call('ctxpop')
eq(1, call('ctxsize'))
call('ctxpop')
eq(0, call('ctxsize'))
end)
end)
describe('ctxget()', function()
it('errors out when index is out of bounds', function()
expect_err(outofbounds, call, 'ctxget')
call('ctxpush')
expect_err(outofbounds, call, 'ctxget', 1)
call('ctxpop')
expect_err(outofbounds, call, 'ctxget', 0)
end)
it('returns context dictionary at index in context stack', function()
feed('i1<cr>2<cr>3<c-[>ddddddqahjklq')
command('edit! '..fname1)
feed('G')
feed('gg')
command('edit '..fname2)
nvim('set_var', 'one', 1)
nvim('set_var', 'Two', 2)
nvim('set_var', 'THREE', 3)
local with_regs = {
['regs'] = {
{['rt'] = 1, ['rc'] = {'1'}, ['n'] = 49, ['ru'] = true},
{['rt'] = 1, ['rc'] = {'2'}, ['n'] = 50},
{['rt'] = 1, ['rc'] = {'3'}, ['n'] = 51},
{['rc'] = {'hjkl'}, ['n'] = 97},
}
}
local with_jumps = {
['jumps'] = eval(([[
filter(map(add(
getjumplist()[0], { 'bufnr': bufnr('%'), 'lnum': getcurpos()[1] }),
'filter(
{ "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum },
{ k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)')
]]):gsub('\n', ''))
}
local with_buflist = {
['buflist'] = eval([[
filter(map(getbufinfo(), '{ "f": v:val.name }'), '!empty(v:val.f)')
]])
}
local with_gvars = {
['gvars'] = {{'one', 1}, {'Two', 2}, {'THREE', 3}}
}
local with_all = {
['regs'] = with_regs['regs'],
['jumps'] = with_jumps['jumps'],
['buflist'] = with_buflist['buflist'],
['gvars'] = with_gvars['gvars'],
}
call('ctxpush')
eq(with_all, parse_context(call('ctxget')))
eq(with_all, parse_context(call('ctxget', 0)))
call('ctxpush', {'gvars'})
eq(with_gvars, parse_context(call('ctxget')))
eq(with_gvars, parse_context(call('ctxget', 0)))
eq(with_all, parse_context(call('ctxget', 1)))
call('ctxpush', {'buflist'})
eq(with_buflist, parse_context(call('ctxget')))
eq(with_buflist, parse_context(call('ctxget', 0)))
eq(with_gvars, parse_context(call('ctxget', 1)))
eq(with_all, parse_context(call('ctxget', 2)))
call('ctxpush', {'jumps'})
eq(with_jumps, parse_context(call('ctxget')))
eq(with_jumps, parse_context(call('ctxget', 0)))
eq(with_buflist, parse_context(call('ctxget', 1)))
eq(with_gvars, parse_context(call('ctxget', 2)))
eq(with_all, parse_context(call('ctxget', 3)))
call('ctxpush', {'regs'})
eq(with_regs, parse_context(call('ctxget')))
eq(with_regs, parse_context(call('ctxget', 0)))
eq(with_jumps, parse_context(call('ctxget', 1)))
eq(with_buflist, parse_context(call('ctxget', 2)))
eq(with_gvars, parse_context(call('ctxget', 3)))
eq(with_all, parse_context(call('ctxget', 4)))
call('ctxpop')
eq(with_jumps, parse_context(call('ctxget')))
eq(with_jumps, parse_context(call('ctxget', 0)))
eq(with_buflist, parse_context(call('ctxget', 1)))
eq(with_gvars, parse_context(call('ctxget', 2)))
eq(with_all, parse_context(call('ctxget', 3)))
call('ctxpop')
eq(with_buflist, parse_context(call('ctxget')))
eq(with_buflist, parse_context(call('ctxget', 0)))
eq(with_gvars, parse_context(call('ctxget', 1)))
eq(with_all, parse_context(call('ctxget', 2)))
call('ctxpop')
eq(with_gvars, parse_context(call('ctxget')))
eq(with_gvars, parse_context(call('ctxget', 0)))
eq(with_all, parse_context(call('ctxget', 1)))
call('ctxpop')
eq(with_all, parse_context(call('ctxget')))
eq(with_all, parse_context(call('ctxget', 0)))
end)
end)
describe('ctxset()', function()
it('errors out when index is out of bounds', function()
expect_err(outofbounds, call, 'ctxset', {dummy = 1})
call('ctxpush')
expect_err(outofbounds, call, 'ctxset', {dummy = 1}, 1)
call('ctxpop')
expect_err(outofbounds, call, 'ctxset', {dummy = 1}, 0)
end)
it('sets context dictionary at index in context stack', function()
nvim('set_var', 'one', 1)
nvim('set_var', 'Two', 2)
nvim('set_var', 'THREE', 3)
call('ctxpush')
local ctx1 = call('ctxget')
nvim('set_var', 'one', 'a')
nvim('set_var', 'Two', 'b')
nvim('set_var', 'THREE', 'c')
call('ctxpush')
call('ctxpush')
local ctx2 = call('ctxget')
eq({'a', 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
call('ctxset', ctx1)
call('ctxset', ctx2, 2)
call('ctxpop')
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
call('ctxpop')
eq({'a', 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
nvim('set_var', 'one', 1.5)
eq({1.5, 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
call('ctxpop')
eq({'a', 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
end)
end)
end)