diff options
29 files changed, 600 insertions, 117 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b73a0063f1..7fef198934 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,8 +69,6 @@ jobs: - name: Install brew packages if: matrix.os == 'osx' run: | - # Workaround brew issues - rm -f /usr/local/bin/2to3 brew update >/dev/null brew install automake ccache perl cpanminus ninja diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5839be2944..5ad4c90eaf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -80,7 +80,6 @@ jobs: fetch-depth: 0 - name: Install brew packages run: | - rm -f /usr/local/bin/2to3 brew update >/dev/null brew install automake ninja - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly') diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index efffca72ad..e73634c632 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1580,8 +1580,11 @@ nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) *nvim_set_keymap()* {rhs} Right-hand-side |{rhs}| of the mapping. {opts} Optional parameters map. Accepts all |:map-arguments| as keys excluding |<buffer>| but - including |noremap|. Values are Booleans. Unknown - key is an error. + including |noremap| and "desc". |desc| can be used + to give a description to keymap. When called from + Lua, also accepts a "callback" key that takes a + Lua function to call when the mapping is executed. + Values are Booleans. Unknown key is an error. nvim_set_option({name}, {value}) *nvim_set_option()* Sets the global value of an option. diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index ffdd8427f9..953f097a92 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -1595,7 +1595,8 @@ r Automatically insert the current comment leader after hitting <Enter> in Insert mode. *fo-o* o Automatically insert the current comment leader after hitting 'o' or - 'O' in Normal mode. + 'O' in Normal mode. In case comment is unwanted in a specific place + use CTRL-U to quickly delete it. |i_CTRL-U| *fo-q* q Allow formatting of comments with "gq". Note that formatting will not change blank lines or lines containing diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index bbbe71ec3a..8ca763b45e 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -314,12 +314,12 @@ define yourself. There are a few ways to avoid this: You need to define your own mapping before the plugin is loaded (before editing a file of that type). The plugin will then skip installing the default mapping. - *no_mail_maps* + *no_mail_maps* *g:no_mail_maps* 3. Disable defining mappings for a specific filetype by setting a variable, which contains the name of the filetype. For the "mail" filetype this would be: > :let no_mail_maps = 1 -< *no_plugin_maps* +< *no_plugin_maps* *g:no_plugin_maps* 4. Disable defining mappings for all filetypes by setting a variable: > :let no_plugin_maps = 1 < diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index fd1d0f8ea6..d74d7cafa8 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -76,6 +76,8 @@ CTRL-U Delete all entered characters before the cursor in the current line. If there are no newly entered characters and 'backspace' is not empty, delete all characters before the cursor in the current line. + If C-indenting is enabled the indent will be adjusted if the + line becomes blank. See |i_backspacing| about joining lines. *i_CTRL-U-default* By default, sets a new undo point before deleting. @@ -1878,6 +1880,9 @@ When 'autoindent' is on, the indent for a new line is obtained from the previous line. When 'smartindent' or 'cindent' is on, the indent for a line is automatically adjusted for C programs. +'formatoptions' can be set to copy the comment leader when opening a new +line. + 'textwidth' can be set to the maximum width for a line. When a line becomes too long when appending characters a line break is automatically inserted. diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 104836862f..1f9b90ed78 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -959,9 +959,9 @@ au BufNewFile,BufRead lilo.conf setf lilo " Lisp (*.el = ELisp, *.cl = Common Lisp) " *.jl was removed, it's also used for Julia, better skip than guess wrong. if has("fname_case") - au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,*.L,.emacs,.sawfishrc setf lisp + au BufNewFile,BufRead *.lsp,*.lisp,*.asd,*.el,*.cl,*.L,.emacs,.sawfishrc setf lisp else - au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,.emacs,.sawfishrc setf lisp + au BufNewFile,BufRead *.lsp,*.lisp,*.asd,*.el,*.cl,.emacs,.sawfishrc setf lisp endif " SBCL implementation of Common Lisp @@ -1665,7 +1665,7 @@ au BufNewFile,BufRead .zshrc,.zshenv,.zlogin,.zlogout,.zcompdump setf zsh au BufNewFile,BufRead *.zsh setf zsh " Scheme -au BufNewFile,BufRead *.scm,*.ss,*.rkt,*.rktd,*.rktl setf scheme +au BufNewFile,BufRead *.scm,*.ss,*.sld,*.rkt,*.rktd,*.rktl setf scheme " Screen RC au BufNewFile,BufRead .screenrc,screenrc setf screen diff --git a/runtime/nvim.appdata.xml b/runtime/nvim.appdata.xml index 225dd79878..4ad656f1a3 100644 --- a/runtime/nvim.appdata.xml +++ b/runtime/nvim.appdata.xml @@ -26,7 +26,9 @@ </screenshots> <releases> + <release date="2021-12-31" version="0.6.1"/> <release date="2021-11-30" version="0.6.0"/> + <release date="2021-09-26" version="0.5.1"/> <release date="2021-07-02" version="0.5.0"/> <release date="2020-08-04" version="0.4.4"/> <release date="2019-11-06" version="0.4.3"/> diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index bacb991c4b..2d5403d4b8 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -835,7 +835,7 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) /// @param[out] err Error details, if any /// @returns Array of maparg()-like dictionaries describing mappings. /// The "buffer" key holds the associated buffer handle. -ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) +ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, String mode, Error *err) FUNC_API_SINCE(3) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -844,7 +844,7 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) return (Array)ARRAY_DICT_INIT; } - return keymap_array(mode, buf); + return keymap_array(mode, buf, channel_id == LUA_INTERNAL_CALL); } /// Sets a buffer-local |mapping| for the given mode. diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index e47dec9eb7..97ee885ff6 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -29,6 +29,8 @@ return { "script"; "expr"; "unique"; + "callback"; + "desc"; }; get_commands = { "builtin"; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 962fce6952..4884a44cea 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -594,6 +594,7 @@ Array string_to_array(const String input, bool crlf) void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err) { + LuaRef lua_funcref = LUA_NOREF; bool global = (buffer == -1); if (global) { buffer = 0; @@ -604,6 +605,10 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String return; } + if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) { + lua_funcref = opts->callback.data.luaref; + opts->callback.data.luaref = LUA_NOREF; + } MapArguments parsed_args = MAP_ARGUMENTS_INIT; if (opts) { #define KEY_TO_BOOL(name) \ @@ -623,9 +628,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String parsed_args.buffer = !global; set_maparg_lhs_rhs((char_u *)lhs.data, lhs.size, - (char_u *)rhs.data, rhs.size, + (char_u *)rhs.data, rhs.size, lua_funcref, CPO_TO_CPO_FLAGS, &parsed_args); - + if (opts != NULL && opts->desc.type == kObjectTypeString) { + parsed_args.desc = xstrdup(opts->desc.data.string.data); + } else { + parsed_args.desc = NULL; + } if (parsed_args.lhs_len > MAXMAPLEN) { api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data); goto fail_and_free; @@ -658,7 +667,8 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String bool is_noremap = parsed_args.noremap; assert(!(is_unmap && is_noremap)); - if (!is_unmap && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) { + if (!is_unmap && lua_funcref == LUA_NOREF + && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) { if (rhs.size == 0) { // assume that the user wants RHS to be a <Nop> parsed_args.rhs_is_noop = true; } else { @@ -668,9 +678,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String api_set_error(err, kErrorTypeValidation, "Parsing of nonempty RHS failed: %s", rhs.data); goto fail_and_free; } - } else if (is_unmap && parsed_args.rhs_len) { - api_set_error(err, kErrorTypeValidation, - "Gave nonempty RHS in unmap command: %s", parsed_args.rhs); + } else if (is_unmap && (parsed_args.rhs_len || parsed_args.rhs_lua != LUA_NOREF)) { + if (parsed_args.rhs_len) { + api_set_error(err, kErrorTypeValidation, + "Gave nonempty RHS in unmap command: %s", parsed_args.rhs); + } else { + api_set_error(err, kErrorTypeValidation, "Gave nonempty RHS for unmap"); + } goto fail_and_free; } @@ -700,9 +714,12 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String goto fail_and_free; } // switch + parsed_args.rhs_lua = LUA_NOREF; // don't clear ref on success fail_and_free: + NLUA_CLEAR_REF(parsed_args.rhs_lua); xfree(parsed_args.rhs); xfree(parsed_args.orig_rhs); + XFREE_CLEAR(parsed_args.desc); return; } @@ -1052,8 +1069,9 @@ void api_set_error(Error *err, ErrorType errType, const char *format, ...) /// /// @param mode The abbreviation for the mode /// @param buf The buffer to get the mapping array. NULL for global +/// @param from_lua Whether it is called from internal lua api. /// @returns Array of maparg()-like dictionaries describing mappings -ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) +ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua) { Array mappings = ARRAY_DICT_INIT; dict_T *const dict = tv_dict_alloc(); @@ -1073,8 +1091,19 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) // Check for correct mode if (int_mode & current_maphash->m_mode) { mapblock_fill_dict(dict, current_maphash, buffer_value, false); - ADD(mappings, vim_to_object((typval_T[]) { { .v_type = VAR_DICT, .vval.v_dict = dict } })); - + Object api_dict = vim_to_object((typval_T[]) { { .v_type = VAR_DICT, + .vval.v_dict = dict } }); + if (from_lua) { + Dictionary d = api_dict.data.dictionary; + for (size_t j = 0; j < d.size; j++) { + if (strequal("callback", d.items[j].key.data)) { + d.items[j].value.type = kObjectTypeLuaRef; + d.items[j].value.data.luaref = api_new_luaref((LuaRef)d.items[j].value.data.integer); + break; + } + } + } + ADD(mappings, api_dict); tv_dict_clear(dict); } } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index dfc606f927..693d2083e6 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1538,10 +1538,10 @@ Dictionary nvim_get_mode(void) /// @param mode Mode short-name ("n", "i", "v", ...) /// @returns Array of maparg()-like dictionaries describing mappings. /// The "buffer" key is always zero. -ArrayOf(Dictionary) nvim_get_keymap(String mode) +ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) FUNC_API_SINCE(3) { - return keymap_array(mode, NULL); + return keymap_array(mode, NULL, channel_id == LUA_INTERNAL_CALL); } /// Sets a global |mapping| for the given mode. @@ -1566,7 +1566,10 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) /// @param lhs Left-hand-side |{lhs}| of the mapping. /// @param rhs Right-hand-side |{rhs}| of the mapping. /// @param opts Optional parameters map. Accepts all |:map-arguments| -/// as keys excluding |<buffer>| but including |noremap|. +/// as keys excluding |<buffer>| but including |noremap| and "desc". +/// |desc| can be used to give a description to keymap. +/// When called from Lua, also accepts a "callback" key that takes +/// a Lua function to call when the mapping is executed. /// Values are Booleans. Unknown key is an error. /// @param[out] err Error details, if any. void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err) diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 49e527e98b..63a550c017 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -352,6 +352,7 @@ struct mapblock { char_u *m_keys; // mapped from, lhs char_u *m_str; // mapped to, rhs char_u *m_orig_str; // rhs as entered by the user + LuaRef m_luaref; // lua function reference as rhs int m_keylen; // strlen(m_keys) int m_mode; // valid mode int m_noremap; // if non-zero no re-mapping for m_str @@ -359,6 +360,7 @@ struct mapblock { char m_nowait; // <nowait> used char m_expr; // <expr> used, m_str is an expression sctx_T m_script_ctx; // SCTX where map was defined + char *m_desc; // description of keymap }; /// Used for highlighting in the status line. diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 3517b3244e..5eef4350b7 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1076,6 +1076,10 @@ static int insert_handle_key(InsertState *s) case K_COMMAND: // some command do_cmdline(NULL, getcmdkeycmd, NULL, 0); + goto check_pum; + + case K_LUA: + map_execute_lua(); check_pum: // TODO(bfredl): Not entirely sure this indirection is necessary diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 86384bc5b2..6dbdc09c3b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7299,12 +7299,19 @@ void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, long buf noremap_value = mp->m_noremap == REMAP_SCRIPT ? 2 : !!mp->m_noremap; } - if (compatible) { - tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str); + if (mp->m_luaref != LUA_NOREF) { + tv_dict_add_nr(dict, S_LEN("callback"), mp->m_luaref); } else { - tv_dict_add_allocated_str(dict, S_LEN("rhs"), - str2special_save((const char *)mp->m_str, false, - true)); + if (compatible) { + tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str); + } else { + tv_dict_add_allocated_str(dict, S_LEN("rhs"), + str2special_save((const char *)mp->m_str, false, + true)); + } + } + if (mp->m_desc != NULL) { + tv_dict_add_allocated_str(dict, S_LEN("desc"), xstrdup(mp->m_desc)); } tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs); tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value); diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 32026282cf..7701688b49 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3991,7 +3991,6 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) MotionType reg_type = get_reg_type(regname, ®len); format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf)); - rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)xstrdup(buf); } @@ -5980,6 +5979,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) { char_u *keys_buf = NULL; char_u *rhs; + LuaRef rhs_lua; int mode; int abbr = FALSE; int get_dict = FALSE; @@ -6016,7 +6016,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true, CPO_TO_CPO_FLAGS); - rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local); + rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); xfree(keys_buf); if (!get_dict) { @@ -6027,10 +6027,15 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) } else { rettv->vval.v_string = (char_u *)str2special_save((char *)rhs, false, false); } + } else if (rhs_lua != LUA_NOREF) { + size_t msglen = 100; + char *msg = (char *)xmalloc(msglen); + snprintf(msg, msglen, "<Lua function %d>", mp->m_luaref); + rettv->vval.v_string = (char_u *)msg; } } else { tv_dict_alloc_ret(rettv); - if (rhs != NULL) { + if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) { // Return a dictionary. mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true); } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index cc5ab1b554..95390b1a70 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3006,7 +3006,12 @@ void ex_append(exarg_T *eap) did_undo = true; ml_append(lnum, theline, (colnr_T)0, false); - appended_lines_mark(lnum + (empty ? 1 : 0), 1L); + if (empty) { + // there are no marks below the inserted lines + appended_lines(lnum, 1L); + } else { + appended_lines_mark(lnum, 1L); + } xfree(theline); ++lnum; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 67fd5a4efc..78b8e43e65 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1024,11 +1024,13 @@ static int command_line_execute(VimState *state, int key) CommandLineState *s = (CommandLineState *)state; s->c = key; - if (s->c == K_EVENT || s->c == K_COMMAND) { + if (s->c == K_EVENT || s->c == K_COMMAND || s->c == K_LUA) { if (s->c == K_EVENT) { state_handle_k_event(); - } else { + } else if (s->c == K_COMMAND) { do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT); + } else { + map_execute_lua(); } if (!cmdline_was_last_drawn) { diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 424bf758e2..5565e17597 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -15,6 +15,7 @@ #include <stdbool.h> #include <string.h> +#include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/buffer_defs.h" @@ -1902,7 +1903,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // complete match if (keylen >= 0 && keylen <= typebuf.tb_len) { - char_u *map_str; + char_u *map_str = NULL; int save_m_expr; int save_m_noremap; int save_m_silent; @@ -1947,6 +1948,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) save_m_silent = mp->m_silent; char_u *save_m_keys = NULL; // only saved when needed char_u *save_m_str = NULL; // only saved when needed + LuaRef save_m_luaref = mp->m_luaref; // Handle ":map <expr>": evaluate the {rhs} as an // expression. Also save and restore the command line @@ -1959,8 +1961,10 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) may_garbage_collect = false; save_m_keys = vim_strsave(mp->m_keys); - save_m_str = vim_strsave(mp->m_str); - map_str = eval_map_expr(save_m_str, NUL); + if (save_m_luaref == LUA_NOREF) { + save_m_str = vim_strsave(mp->m_str); + } + map_str = eval_map_expr(mp, NUL); vgetc_busy = save_vgetc_busy; may_garbage_collect = save_may_garbage_collect; } else { @@ -2618,11 +2622,13 @@ int fix_input_buffer(char_u *buf, int len) /// @param[in] orig_lhs Original mapping LHS, with characters to replace. /// @param[in] orig_lhs_len `strlen` of orig_lhs. /// @param[in] orig_rhs Original mapping RHS, with characters to replace. +/// @param[in] rhs_lua Lua reference for Lua maps. /// @param[in] orig_rhs_len `strlen` of orig_rhs. /// @param[in] cpo_flags See param docs for @ref replace_termcodes. /// @param[out] mapargs MapArguments struct holding the replaced strings. -void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const char_u *orig_rhs, - const size_t orig_rhs_len, int cpo_flags, MapArguments *mapargs) +void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, + const char_u *orig_rhs, const size_t orig_rhs_len, + LuaRef rhs_lua, int cpo_flags, MapArguments *mapargs) { char_u *lhs_buf = NULL; char_u *rhs_buf = NULL; @@ -2638,22 +2644,34 @@ void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const true, true, true, cpo_flags); mapargs->lhs_len = STRLEN(replaced); STRLCPY(mapargs->lhs, replaced, sizeof(mapargs->lhs)); + mapargs->rhs_lua = rhs_lua; - mapargs->orig_rhs_len = orig_rhs_len; - mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u)); - STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1); + if (rhs_lua == LUA_NOREF) { + mapargs->orig_rhs_len = orig_rhs_len; + mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u)); + STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1); - if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing - mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char - mapargs->rhs_len = 0; - mapargs->rhs_is_noop = true; + if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing + mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char + mapargs->rhs_len = 0; + mapargs->rhs_is_noop = true; + } else { + replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, + false, true, true, cpo_flags); + mapargs->rhs_len = STRLEN(replaced); + mapargs->rhs_is_noop = false; + mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u)); + STRLCPY(mapargs->rhs, replaced, mapargs->rhs_len + 1); + } } else { - replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, - false, true, true, cpo_flags); - mapargs->rhs_len = STRLEN(replaced); - mapargs->rhs_is_noop = false; - mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u)); - STRLCPY(mapargs->rhs, replaced, mapargs->rhs_len + 1); + char tmp_buf[64]; + // stores <lua>ref_no<cr> in map_str + mapargs->orig_rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "<LUA>%d<CR>", rhs_lua); + mapargs->orig_rhs = vim_strsave((char_u *)tmp_buf); + mapargs->rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "%c%c%c%d\r", K_SPECIAL, + (char_u)KEY2TERMCAP0(K_LUA), KEY2TERMCAP1(K_LUA), + rhs_lua); + mapargs->rhs = vim_strsave((char_u *)tmp_buf); } xfree(lhs_buf); @@ -2765,7 +2783,7 @@ int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs) size_t orig_rhs_len = STRLEN(rhs_start); set_maparg_lhs_rhs(lhs_to_replace, orig_lhs_len, - rhs_start, orig_rhs_len, + rhs_start, orig_rhs_len, LUA_NOREF, CPO_TO_CPO_FLAGS, &parsed_args); xfree(lhs_to_replace); @@ -2827,7 +2845,7 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T validate_maphash(); bool has_lhs = (args->lhs[0] != NUL); - bool has_rhs = (args->rhs[0] != NUL) || args->rhs_is_noop; + bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop; // check for :unmap without argument if (maptype == 1 && !has_lhs) { @@ -3017,10 +3035,14 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T } else { // new rhs for existing entry mp->m_mode &= ~mode; // remove mode bits if (mp->m_mode == 0 && !did_it) { // reuse entry - xfree(mp->m_str); + XFREE_CLEAR(mp->m_str); + XFREE_CLEAR(mp->m_orig_str); + XFREE_CLEAR(mp->m_desc); + NLUA_CLEAR_REF(mp->m_luaref); + mp->m_str = vim_strsave(rhs); - xfree(mp->m_orig_str); mp->m_orig_str = vim_strsave(orig_rhs); + mp->m_luaref = args->rhs_lua; mp->m_noremap = noremap; mp->m_nowait = args->nowait; mp->m_silent = args->silent; @@ -3028,6 +3050,9 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T mp->m_expr = args->expr; mp->m_script_ctx = current_sctx; mp->m_script_ctx.sc_lnum += sourcing_lnum; + if (args->desc != NULL) { + mp->m_desc = xstrdup(args->desc); + } did_it = true; } } @@ -3096,6 +3121,7 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T mp->m_keys = vim_strsave(lhs); mp->m_str = vim_strsave(rhs); mp->m_orig_str = vim_strsave(orig_rhs); + mp->m_luaref = args->rhs_lua; mp->m_keylen = (int)STRLEN(mp->m_keys); mp->m_noremap = noremap; mp->m_nowait = args->nowait; @@ -3104,6 +3130,10 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T mp->m_expr = args->expr; mp->m_script_ctx = current_sctx; mp->m_script_ctx.sc_lnum += sourcing_lnum; + mp->m_desc = NULL; + if (args->desc != NULL) { + mp->m_desc = xstrdup(args->desc); + } // add the new entry in front of the abbrlist or maphash[] list if (is_abbrev) { @@ -3200,8 +3230,10 @@ static void mapblock_free(mapblock_T **mpp) mp = *mpp; xfree(mp->m_keys); - xfree(mp->m_str); - xfree(mp->m_orig_str); + NLUA_CLEAR_REF(mp->m_luaref); + XFREE_CLEAR(mp->m_str); + XFREE_CLEAR(mp->m_orig_str); + XFREE_CLEAR(mp->m_desc); *mpp = mp->m_next; xfree(mp); } @@ -3392,7 +3424,8 @@ static void showmap(mapblock_T *mp, bool local) { size_t len = 1; - if (message_filtered(mp->m_keys) && message_filtered(mp->m_str)) { + if (message_filtered(mp->m_keys) + && mp->m_str != NULL && message_filtered(mp->m_str)) { return; } @@ -3437,7 +3470,11 @@ static void showmap(mapblock_T *mp, bool local) /* Use FALSE below if we only want things like <Up> to show up as such on * the rhs, and not M-x etc, TRUE gets both -- webb */ - if (*mp->m_str == NUL) { + if (mp->m_luaref != LUA_NOREF) { + char msg[100]; + snprintf(msg, sizeof(msg), "<Lua function %d>", mp->m_luaref); + msg_puts_attr(msg, HL_ATTR(HLF_8)); + } else if (mp->m_str == NULL) { msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { // Remove escaping of CSI, because "m_str" is in a format to be used @@ -3447,6 +3484,11 @@ static void showmap(mapblock_T *mp, bool local) msg_outtrans_special(s, false, 0); xfree(s); } + + if (mp->m_desc != NULL) { + msg_puts("\n "); // Shift line to same level as rhs. + msg_puts(mp->m_desc); + } if (p_verbose > 0) { last_set_msg(mp->m_script_ctx); } @@ -3533,7 +3575,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) } for (; mp; mp = mp->m_next) { if ((mp->m_mode & mode) - && strstr((char *)mp->m_str, rhs) != NULL) { + && mp->m_str != NULL && strstr((char *)mp->m_str, rhs) != NULL) { return true; } } @@ -3879,7 +3921,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) (void)ins_typebuf(tb, 1, 0, true, mp->m_silent); } if (mp->m_expr) { - s = eval_map_expr(mp->m_str, c); + s = eval_map_expr(mp, c); } else { s = mp->m_str; } @@ -3909,11 +3951,11 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) /// special characters. /// /// @param c NUL or typed character for abbreviation -static char_u *eval_map_expr(char_u *str, int c) +static char_u *eval_map_expr(mapblock_T *mp, int c) { char_u *res; - char_u *p; - char_u *expr; + char_u *p = NULL; + char_u *expr = NULL; char_u *save_cmd; pos_T save_cursor; int save_msg_col; @@ -3921,8 +3963,10 @@ static char_u *eval_map_expr(char_u *str, int c) /* Remove escaping of CSI, because "str" is in a format to be used as * typeahead. */ - expr = vim_strsave(str); - vim_unescape_csi(expr); + if (mp->m_luaref == LUA_NOREF) { + expr = vim_strsave(mp->m_str); + vim_unescape_csi(expr); + } save_cmd = save_cmdline_alloc(); @@ -3934,7 +3978,22 @@ static char_u *eval_map_expr(char_u *str, int c) save_cursor = curwin->w_cursor; save_msg_col = msg_col; save_msg_row = msg_row; - p = eval_to_string(expr, NULL, false); + if (mp->m_luaref != LUA_NOREF) { + Error err = ERROR_INIT; + Array args = ARRAY_DICT_INIT; + Object ret = nlua_call_ref(mp->m_luaref, NULL, args, true, &err); + if (ret.type == kObjectTypeString) { + p = (char_u *)xstrndup(ret.data.string.data, ret.data.string.size); + } + api_free_object(ret); + if (err.type != kErrorTypeNone) { + semsg_multiline("E5108: %s", err.msg); + api_clear_error(&err); + } + } else { + p = eval_to_string(expr, NULL, false); + xfree(expr); + } textlock--; ex_normal_lock--; curwin->w_cursor = save_cursor; @@ -3942,7 +4001,6 @@ static char_u *eval_map_expr(char_u *str, int c) msg_row = save_msg_row; restore_cmdline_alloc(save_cmd); - xfree(expr); if (p == NULL) { return NULL; @@ -4049,8 +4107,11 @@ int makemap(FILE *fd, buf_T *buf) continue; } - // skip mappings that contain a <SNR> (script-local thing), + // skip lua mappings and mappings that contain a <SNR> (script-local thing), // they probably don't work when loaded again + if (mp->m_luaref != LUA_NOREF) { + continue; + } for (p = mp->m_str; *p != NUL; p++) { if (p[0] == K_SPECIAL && p[1] == KS_EXTRA && p[2] == (int)KE_SNR) { @@ -4331,10 +4392,11 @@ int put_escstr(FILE *fd, char_u *strstart, int what) /// @param mp_ptr return: pointer to mapblock or NULL /// @param local_ptr return: buffer-local mapping or NULL char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr, - int *local_ptr) + int *local_ptr, int *rhs_lua) { int len, minlen; mapblock_T *mp; + *rhs_lua = LUA_NOREF; validate_maphash(); @@ -4375,7 +4437,8 @@ char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapb if (local_ptr != NULL) { *local_ptr = local; } - return mp->m_str; + *rhs_lua = mp->m_luaref; + return mp->m_luaref == LUA_NOREF ? mp->m_str : NULL; } } } @@ -4560,3 +4623,47 @@ char_u *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) return (char_u *)line_ga.ga_data; } + +bool map_execute_lua(void) +{ + garray_T line_ga; + int c1 = -1; + bool aborted = false; + + ga_init(&line_ga, 1, 32); + + no_mapping++; + + got_int = false; + while (c1 != NUL && !aborted) { + ga_grow(&line_ga, 32); + // Get one character at a time. + c1 = vgetorpeek(true); + if (got_int) { + aborted = true; + } else if (c1 == '\r' || c1 == '\n') { + c1 = NUL; // end the line + } else { + ga_append(&line_ga, (char)c1); + } + } + + no_mapping--; + + if (aborted) { + ga_clear(&line_ga); + return false; + } + + LuaRef ref = (LuaRef)atoi(line_ga.ga_data); + Error err = ERROR_INIT; + Array args = ARRAY_DICT_INIT; + nlua_call_ref(ref, NULL, args, false, &err); + if (err.type != kErrorTypeNone) { + semsg_multiline("E5108: %s", err.msg); + api_clear_error(&err); + } + + ga_clear(&line_ga); + return true; +} diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index 5950611d3f..be10e150e5 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -50,14 +50,16 @@ struct map_arguments { char_u *rhs; /// The {rhs} of the mapping. size_t rhs_len; + LuaRef rhs_lua; /// lua function as rhs bool rhs_is_noop; /// True when the {orig_rhs} is <nop>. char_u *orig_rhs; /// The original text of the {rhs}. size_t orig_rhs_len; + char *desc; /// map escription }; typedef struct map_arguments MapArguments; #define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \ - { 0 }, 0, NULL, 0, false, NULL, 0 } + { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL } #define KEYLEN_PART_KEY -1 // keylen value for incomplete key-code #define KEYLEN_PART_MAP -2 // keylen value for incomplete mapping diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index 5ff5a38614..5a74d1dc00 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -245,6 +245,7 @@ enum key_extra { KE_MOUSEMOVE = 100, // mouse moved with no button down // , KE_CANCEL = 101 // return from vgetc KE_EVENT = 102, // event + KE_LUA = 103, // lua special key KE_COMMAND = 104, // <Cmd> special key }; @@ -443,6 +444,7 @@ enum key_extra { #define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT) #define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND) +#define K_LUA TERMCAP2KEY(KS_EXTRA, KE_LUA) // Bits for modifier mask // 0x01 cannot be used, because the modifier must be 0x02 or higher diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 3246596f16..60bf393085 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -334,6 +334,7 @@ static const struct nv_cmd { { K_SELECT, nv_select, 0, 0 }, { K_EVENT, nv_event, NV_KEEPREG, 0 }, { K_COMMAND, nv_colon, 0, 0 }, + { K_LUA, nv_colon, 0, 0 }, }; // Number of commands in nv_cmds[]. @@ -4043,21 +4044,22 @@ static void nv_regreplay(cmdarg_T *cap) } } -/// Handle a ":" command and <Cmd>. +/// Handle a ":" command and <Cmd> or Lua keymaps. static void nv_colon(cmdarg_T *cap) { int old_p_im; bool cmd_result; bool is_cmdkey = cap->cmdchar == K_COMMAND; + bool is_lua = cap->cmdchar == K_LUA; - if (VIsual_active && !is_cmdkey) { + if (VIsual_active && !is_cmdkey && !is_lua) { nv_operator(cap); } else { if (cap->oap->op_type != OP_NOP) { // Using ":" as a movement is charwise exclusive. cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; - } else if (cap->count0 && !is_cmdkey) { + } else if (cap->count0 && !is_cmdkey && !is_lua) { // translate "count:" into ":.,.+(count - 1)" stuffcharReadbuff('.'); if (cap->count0 > 1) { @@ -4073,9 +4075,13 @@ static void nv_colon(cmdarg_T *cap) old_p_im = p_im; + if (is_lua) { + cmd_result = map_execute_lua(); + } else { // get a command line and execute it - cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, - cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); + cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, + cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); + } // If 'insertmode' changed, enter or exit Insert mode if (p_im != old_p_im) { diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index a7b52d8238..70a5c7aa08 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -528,6 +528,10 @@ static int terminal_execute(VimState *state, int key) do_cmdline(NULL, getcmdkeycmd, NULL, 0); break; + case K_LUA: + map_execute_lua(); + break; + case Ctrl_N: if (s->got_bsl) { return 0; diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index 2d01cbba83..1c053c824f 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -1,6 +1,8 @@ " Tests for various Ex commands. source check.vim +source shared.vim +source term_util.vim func Test_ex_delete() new @@ -122,6 +124,27 @@ func Test_append_cmd() close! endfunc +func Test_append_cmd_empty_buf() + CheckRunVimInTerminal + let lines =<< trim END + func Timer(timer) + append + aaaaa + bbbbb + . + endfunc + call timer_start(10, 'Timer') + END + call writefile(lines, 'Xtest_append_cmd_empty_buf') + let buf = RunVimInTerminal('-S Xtest_append_cmd_empty_buf', {'rows': 6}) + call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))}) + call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_append_cmd_empty_buf') +endfunc + " Test for the :insert command func Test_insert_cmd() set noautoindent " test assumes noautoindent, but it's on by default in Nvim @@ -151,6 +174,27 @@ func Test_insert_cmd() close! endfunc +func Test_insert_cmd_empty_buf() + CheckRunVimInTerminal + let lines =<< trim END + func Timer(timer) + insert + aaaaa + bbbbb + . + endfunc + call timer_start(10, 'Timer') + END + call writefile(lines, 'Xtest_insert_cmd_empty_buf') + let buf = RunVimInTerminal('-S Xtest_insert_cmd_empty_buf', {'rows': 6}) + call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))}) + call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_insert_cmd_empty_buf') +endfunc + " Test for the :change command func Test_change_cmd() set noautoindent " test assumes noautoindent, but it's on by default in Nvim diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 1ffa1f86dc..694e6faf21 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -286,7 +286,7 @@ let s:filename_checks = { \ 'lilo': ['lilo.conf', 'lilo.conf-file'], \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'], \ 'liquid': ['file.liquid'], - \ 'lisp': ['file.lsp', 'file.lisp', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'], + \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'], \ 'lite': ['file.lite', 'file.lt'], \ 'litestep': ['/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'], \ 'loginaccess': ['/etc/login.access', 'any/etc/login.access'], @@ -436,7 +436,7 @@ let s:filename_checks = { \ 'sather': ['file.sa'], \ 'sbt': ['file.sbt'], \ 'scala': ['file.scala', 'file.sc'], - \ 'scheme': ['file.scm', 'file.ss', 'file.rkt', 'file.rktd', 'file.rktl'], + \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.rkt', 'file.rktd', 'file.rktl'], \ 'scilab': ['file.sci', 'file.sce'], \ 'screen': ['.screenrc', 'screenrc'], \ 'sexplib': ['file.sexp'], diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index dd8eef7ca0..bc2759ade7 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -5,6 +5,7 @@ local clear = helpers.clear local command = helpers.command local curbufmeths = helpers.curbufmeths local eq, neq = helpers.eq, helpers.neq +local exec_lua = helpers.exec_lua local feed = helpers.feed local funcs = helpers.funcs local meths = helpers.meths @@ -316,6 +317,55 @@ describe('nvim_get_keymap', function() command('nnoremap \\|<Char-0x20><Char-32><Space><Bar> \\|<Char-0x20><Char-32><Space> <Bar>') eq({space_table}, meths.get_keymap('n')) end) + + it('can handle lua keymaps', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + eq(1, exec_lua[[return GlobalCount]]) + + eq(2, exec_lua[[ + vim.api.nvim_get_keymap('n')[1].callback() + return GlobalCount + ]]) + local mapargs = meths.get_keymap('n') + assert.Truthy(type(mapargs[1].callback) == 'number', 'callback is not luaref number') + mapargs[1].callback = nil + eq({ + lhs='asdf', + script=0, + silent=0, + expr=0, + sid=0, + buffer=0, + nowait=0, + mode='n', + noremap=0, + lnum=0, + }, mapargs[1]) + end) + + it ('can handle map descriptions', function() + meths.set_keymap('n', 'lhs', 'rhs', {desc="map description"}) + eq({ + lhs='lhs', + rhs='rhs', + script=0, + silent=0, + expr=0, + sid=0, + buffer=0, + nowait=0, + mode='n', + noremap=0, + lnum=0, + desc='map description' + }, meths.get_keymap('n')[1]) + end) end) describe('nvim_set_keymap, nvim_del_keymap', function() @@ -353,6 +403,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function() to_return.sid = not opts.sid and 0 or opts.sid to_return.buffer = not opts.buffer and 0 or opts.buffer to_return.lnum = not opts.lnum and 0 or opts.lnum + to_return.desc = opts.desc return to_return end @@ -717,6 +768,105 @@ describe('nvim_set_keymap, nvim_del_keymap', function() end) end end + + it('can make lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + end) + + it (':map command shows lua keymap correctly', function() + exec_lua [[ + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end }) + ]] + assert.truthy(string.match(exec_lua[[return vim.api.nvim_exec(':nmap asdf', true)]], + "^\nn asdf <Lua function %d+>")) + end) + + it ('mapcheck() returns lua keymap correctly', function() + exec_lua [[ + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end }) + ]] + assert.truthy(string.match(funcs.mapcheck('asdf', 'n'), + "^<Lua function %d+>")) + end) + + it ('maparg() returns lua keymap correctly', function() + exec_lua [[ + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end }) + ]] + assert.truthy(string.match(funcs.maparg('asdf', 'n'), + "^<Lua function %d+>")) + local mapargs = funcs.maparg('asdf', 'n', false, true) + assert.Truthy(type(mapargs.callback) == 'number', 'callback is not luaref number') + mapargs.callback = nil + eq(generate_mapargs('n', 'asdf', nil, {}), mapargs) + end) + + it('can make lua expr mappings', function() + exec_lua [[ + vim.api.nvim_set_keymap ('n', 'aa', '', {callback = function() return vim.api.nvim_replace_termcodes(':lua SomeValue = 99<cr>', true, false, true) end, expr = true }) + ]] + + feed('aa') + + eq(99, exec_lua[[return SomeValue]]) + end) + + it('can overwrite lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end }) + ]] + + feed('asdf\n') + + eq(0, exec_lua[[return GlobalCount]]) + end) + + it('can unmap lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.api.nvim_del_keymap('n', 'asdf' ) + ]] + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + eq('\nNo mapping found', helpers.exec_capture('nmap asdf')) + end) + + it('can set descriptions on keymaps', function() + meths.set_keymap('n', 'lhs', 'rhs', {desc="map description"}) + eq(generate_mapargs('n', 'lhs', 'rhs', {desc="map description"}), get_mapargs('n', 'lhs')) + eq("\nn lhs rhs\n map description", + helpers.exec_capture("nmap lhs")) + end) end) describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() @@ -814,4 +964,67 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() pcall_err(bufmeths.set_keymap, 100, '', 'lsh', 'irhs<Esc>', {}) helpers.assert_alive() end) + + it('can make lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + end) + + it('can make lua expr mappings', function() + exec_lua [[ + vim.api.nvim_buf_set_keymap (0, 'n', 'aa', '', {callback = function() return vim.api.nvim_replace_termcodes(':lua SomeValue = 99<cr>', true, false, true) end, expr = true }) + ]] + + feed('aa') + + eq(99, exec_lua[[return SomeValue ]]) + end) + + it('can overwrite lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end }) + ]] + + feed('asdf\n') + + eq(0, exec_lua[[return GlobalCount]]) + end) + + it('can unmap lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.api.nvim_buf_del_keymap(0, 'n', 'asdf' ) + ]] + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + eq('\nNo mapping found', helpers.exec_capture('nmap asdf')) + end) end) diff --git a/test/functional/ex_cmds/append_spec.lua b/test/functional/ex_cmds/append_spec.lua index 0a4d701794..fadb5c9b42 100644 --- a/test/functional/ex_cmds/append_spec.lua +++ b/test/functional/ex_cmds/append_spec.lua @@ -1,23 +1,26 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq +local dedent = helpers.dedent +local exec = helpers.exec local feed = helpers.feed local clear = helpers.clear local funcs = helpers.funcs local command = helpers.command local curbufmeths = helpers.curbufmeths - -before_each(function() - clear() - curbufmeths.set_lines(0, 1, true, { 'foo', 'bar', 'baz' }) -end) - -local buffer_contents = function() - return curbufmeths.get_lines(0, -1, false) -end +local Screen = require('test.functional.ui.screen') local cmdtest = function(cmd, prep, ret1) describe(':' .. cmd, function() + before_each(function() + clear() + curbufmeths.set_lines(0, 1, true, { 'foo', 'bar', 'baz' }) + end) + + local buffer_contents = function() + return curbufmeths.get_lines(0, -1, false) + end + it(cmd .. 's' .. prep .. ' the current line by default', function() command(cmd .. '\nabc\ndef\n') eq(ret1, buffer_contents()) @@ -52,3 +55,52 @@ end cmdtest('insert', ' before', { 'abc', 'def', 'foo', 'bar', 'baz' }) cmdtest('append', ' after', { 'foo', 'abc', 'def', 'bar', 'baz' }) cmdtest('change', '', { 'abc', 'def', 'bar', 'baz' }) + +describe('the first line is redrawn correctly after inserting text in an empty buffer', function() + local screen + before_each(function() + clear() + screen = Screen.new(20, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, + [2] = {bold = true, reverse = true}, + }) + screen:attach() + end) + + it('using :append', function() + exec(dedent([[ + append + aaaaa + bbbbb + .]])) + screen:expect([[ + aaaaa | + ^bbbbb | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + + it('using :insert', function() + exec(dedent([[ + insert + aaaaa + bbbbb + .]])) + screen:expect([[ + aaaaa | + ^bbbbb | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) +end) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index f25cfa2039..1ff451434d 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -350,7 +350,7 @@ describe('on_lines does not emit out-of-bounds line indexes when', function() end) it('creating a terminal buffer #16394', function() - feed_command([[autocmd TermOpen * ++once call v:lua.register_callback(expand("<abuf>"))]]) + feed_command('autocmd TermOpen * ++once call v:lua.register_callback(str2nr(expand("<abuf>")))') feed_command('terminal') sleep(500) eq('', exec_lua([[return _G.cb_error]])) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index bf57b135cb..faf44fa01d 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -20,7 +20,6 @@ local nvim_prog = helpers.nvim_prog local nvim_set = helpers.nvim_set local ok = helpers.ok local read_file = helpers.read_file -local exec_lua = helpers.exec_lua if helpers.pending_win32(pending) then return end @@ -581,34 +580,21 @@ describe('TUI', function() end) it("paste: 'nomodifiable' buffer", function() - local has_luajit = exec_lua('return jit ~= nil') child_session:request('nvim_command', 'set nomodifiable') child_session:request('nvim_exec_lua', [[ - -- Stack traces for this test are non-deterministic, so disable them - _G.debug.traceback = function(msg) return msg end + -- Truncate the error message to hide the line number + _G.debug.traceback = function(msg) return msg:sub(-49) end ]], {}) feed_data('\027[200~fail 1\nfail 2\n\027[201~') - if has_luajit then - screen:expect{grid=[[ - | - {4:~ }| - {5: }| - {8:paste: Error executing lua: vim.lua:0: Vim:E21: Ca}| - {8:nnot make changes, 'modifiable' is off} | - {10:Press ENTER or type command to continue}{1: } | - {3:-- TERMINAL --} | - ]]} - else - screen:expect{grid=[[ - | - {4:~ }| - {5: }| - {8:paste: Error executing lua: Vim:E21: Cannot make c}| - {8:hanges, 'modifiable' is off} | - {10:Press ENTER or type command to continue}{1: } | - {3:-- TERMINAL --} | - ]]} - end + screen:expect{grid=[[ + | + {4:~ }| + {5: }| + {8:paste: Error executing lua: Vim:E21: Cannot make c}| + {8:hanges, 'modifiable' is off} | + {10:Press ENTER or type command to continue}{1: } | + {3:-- TERMINAL --} | + ]]} feed_data('\n') -- <Enter> child_session:request('nvim_command', 'set modifiable') feed_data('\027[200~success 1\nsuccess 2\n\027[201~') |