diff options
-rw-r--r-- | src/nvim/eval.c | 107 | ||||
-rw-r--r-- | src/nvim/eval.lua | 5 | ||||
-rw-r--r-- | src/nvim/main.c | 3 | ||||
-rw-r--r-- | test/functional/eval/ctx_functions_spec.lua | 303 |
4 files changed, 418 insertions, 0 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index cd89a22db3..33e3cd64f2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -26,6 +26,7 @@ #include "nvim/buffer.h" #include "nvim/channel.h" #include "nvim/charset.h" +#include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/edit.h" @@ -8014,6 +8015,112 @@ static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) (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(list)" /// diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 089b08d5d1..0b77a24f7a 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -74,6 +74,11 @@ return { cosh={args=1, func="float_op_wrapper", data="&cosh"}, count={args={2, 4}}, cscope_connection={args={0, 3}}, + ctxget={args={0, 1}}, + ctxpop={}, + ctxpush={args={0, 1}}, + ctxset={args={1, 2}}, + ctxsize={}, cursor={args={1, 3}}, deepcopy={args={1, 2}}, delete={args={1,2}}, diff --git a/src/nvim/main.c b/src/nvim/main.c index 9c342e62c0..216343d8f4 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -14,6 +14,7 @@ #include "nvim/main.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/context.h" #include "nvim/diff.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" @@ -672,6 +673,8 @@ void getout(int exitval) garbage_collect(false); } + free_ctx_stack(); + mch_exit(exitval); } diff --git a/test/functional/eval/ctx_functions_spec.lua b/test/functional/eval/ctx_functions_spec.lua new file mode 100644 index 0000000000..3699af0793 --- /dev/null +++ b/test/functional/eval/ctx_functions_spec.lua @@ -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) |