aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt9
-rw-r--r--cmake/InstallHelpers.cmake9
-rw-r--r--runtime/autoload/man.vim11
-rw-r--r--runtime/doc/eval.txt37
-rw-r--r--runtime/doc/gui.txt26
-rw-r--r--runtime/doc/intro.txt9
-rw-r--r--runtime/doc/job_control.txt1
-rw-r--r--runtime/doc/nvim_terminal_emulator.txt14
-rw-r--r--runtime/doc/options.txt2
-rw-r--r--runtime/doc/usr_05.txt16
-rw-r--r--runtime/doc/various.txt13
-rw-r--r--src/nvim/api/buffer.c20
-rw-r--r--src/nvim/api/private/helpers.c39
-rw-r--r--src/nvim/api/vim.c11
-rw-r--r--src/nvim/buffer_defs.h7
-rw-r--r--src/nvim/eval.c57
-rw-r--r--src/nvim/getchar.c25
-rw-r--r--src/nvim/os/pty_process_unix.c2
-rw-r--r--src/nvim/quickfix.c14
-rw-r--r--src/nvim/terminal.c2
-rw-r--r--src/nvim/testdir/runtest.vim1
-rw-r--r--src/nvim/tui/tui.c198
-rw-r--r--test/functional/api/keymap_spec.lua246
-rw-r--r--test/functional/eval/map_functions_spec.lua120
24 files changed, 737 insertions, 152 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ab7595eb11..117519230f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -228,7 +228,7 @@ else()
# On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang
# 3.4.1 used there.
- if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+ if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND CMAKE_C_COMPILER_ID MATCHES "Clang")
add_definitions(-Wno-c11-extensions)
endif()
endif()
@@ -445,11 +445,7 @@ message(STATUS "Using the Lua interpreter ${LUA_PRG}.")
find_program(BUSTED_PRG NAMES busted busted.bat)
find_program(BUSTED_LUA_PRG busted-lua)
if(NOT BUSTED_OUTPUT_TYPE)
- if(WIN32)
- set(BUSTED_OUTPUT_TYPE "plainTerminal")
- else()
- set(BUSTED_OUTPUT_TYPE "utfTerminal")
- endif()
+ set(BUSTED_OUTPUT_TYPE "nvim")
endif()
find_program(LUACHECK_PRG luacheck)
@@ -460,7 +456,6 @@ include(InstallHelpers)
file(GLOB MANPAGES
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
man/nvim.1)
-
install_helper(
FILES ${MANPAGES}
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
diff --git a/cmake/InstallHelpers.cmake b/cmake/InstallHelpers.cmake
index ee07ba2c66..ca20ddf354 100644
--- a/cmake/InstallHelpers.cmake
+++ b/cmake/InstallHelpers.cmake
@@ -1,3 +1,12 @@
+# Fix CMAKE_INSTALL_MANDIR on BSD before including GNUInstallDirs. #6771
+if(CMAKE_SYSTEM_NAME MATCHES "BSD" AND NOT DEFINED CMAKE_INSTALL_MANDIR)
+ if(DEFINED ENV{MANPREFIX})
+ set(CMAKE_INSTALL_MANDIR "$ENV{MANPREFIX}/man")
+ else()
+ set(CMAKE_INSTALL_MANDIR "/usr/local/man")
+ endif()
+endif()
+
# For $CMAKE_INSTALL_{DATAROOT,MAN, ...}DIR
include(GNUInstallDirs)
diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim
index 361ade59d3..dd71ede680 100644
--- a/runtime/autoload/man.vim
+++ b/runtime/autoload/man.vim
@@ -2,8 +2,17 @@
let s:find_arg = '-w'
let s:localfile_arg = v:true " Always use -l if possible. #6683
+let s:section_arg = '-s'
+
+function! s:init_section_flag()
+ call system(['env', 'MANPAGER=cat', 'man', s:section_arg, '1', 'man'])
+ if v:shell_error
+ let s:section_arg = '-S'
+ endif
+endfunction
function! s:init() abort
+ call s:init_section_flag()
" TODO(nhooyr): Does `man -l` on SunOS list searched directories?
try
if !has('win32') && $OSTYPE !~? 'cygwin\|linux' && system('uname -s') =~? 'SunOS' && system('uname -r') =~# '^5'
@@ -211,7 +220,7 @@ function! s:get_path(sect, name) abort
" - sections starting with '-'
" - 3pcap section (found on macOS)
" - commas between sections (for section priority)
- return s:system(['man', s:find_arg, '-s', a:sect, a:name])
+ return s:system(['man', s:find_arg, s:section_arg, a:sect, a:name])
endfunction
function! s:verify_exists(sect, name) abort
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index a11f013950..4e71b89067 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2845,8 +2845,6 @@ confirm({msg} [, {choices} [, {default} [, {type}]]])
Confirm() offers the user a dialog, from which a choice can be
made. It returns the number of the choice. For the first
choice this is 1.
- Note: confirm() is only supported when compiled with dialog
- support, see |+dialog_con| and |+dialog_gui|.
{msg} is displayed in a |dialog| with {choices} as the
alternatives. When {choices} is missing or empty, "&OK" is
@@ -4883,9 +4881,9 @@ jobstart({cmd}[, {opts}]) {Nvim} *jobstart()*
unless cmd[0] is some form of "cmd.exe".
{opts} is a dictionary with these keys:
- on_stdout: stdout event handler (function name or |Funcref|)
- on_stderr: stderr event handler (function name or |Funcref|)
- on_exit : exit event handler (function name or |Funcref|)
+ |on_stdout|: stdout event handler (function name or |Funcref|)
+ |on_stderr|: stderr event handler (function name or |Funcref|)
+ |on_exit| : exit event handler (function name or |Funcref|)
cwd : Working directory of the job; defaults to
|current-directory|.
rpc : If set, |msgpack-rpc| will be used to communicate
@@ -8039,7 +8037,7 @@ There are four types of features:
:if has("cindent")
2. Features that are only supported when certain conditions have been met.
Example: >
- :if has("gui_running")
+ :if has("win32")
< *has-patch*
3. {Nvim} version. The "nvim-1.2.3" feature means that the Nvim version is
1.2.3 or later. Example: >
@@ -8074,17 +8072,14 @@ browse Compiled with |:browse| support, and browse() will
browsefilter Compiled with support for |browsefilter|.
byte_offset Compiled with support for 'o' in 'statusline'
cindent Compiled with 'cindent' support.
-clientserver Compiled with remote invocation support |clientserver|.
clipboard Compiled with 'clipboard' support.
cmdline_compl Compiled with |cmdline-completion| support.
cmdline_hist Compiled with |cmdline-history| support.
cmdline_info Compiled with 'showcmd' and 'ruler' support.
comments Compiled with |'comments'| support.
-compatible Compiled to be very Vi compatible.
cscope Compiled with |cscope| support.
debug Compiled with "DEBUG" defined.
dialog_con Compiled with console dialog support.
-dialog_gui Compiled with GUI dialog support.
digraphs Compiled with support for digraphs.
eval Compiled with expression evaluation support. Always
true, of course!
@@ -8102,9 +8097,6 @@ fname_case Case in file names matters (for Windows this is not
present).
folding Compiled with |folding| support.
gettext Compiled with message translation |multi-lang|
-gui Compiled with GUI enabled.
-gui_running Vim is running in the GUI, or it will start soon.
-gui_win32 Compiled with MS Windows Win32 GUI.
iconv Can use iconv() for conversion.
insert_expand Compiled with support for CTRL-X expansion commands in
Insert mode.
@@ -8119,8 +8111,7 @@ lispindent Compiled with support for lisp indenting.
listcmds Compiled with commands for the buffer list |:files|
and the argument list |arglist|.
localmap Compiled with local mappings and abbr. |:map-local|
-mac Macintosh version of Vim.
-macunix Macintosh version of Vim, using Unix files (OS-X).
+mac macOS version of Vim.
menu Compiled with support for |:menu|.
mksession Compiled with support for |:mksession|.
modify_fname Compiled with file name modifiers. |filename-modifiers|
@@ -8128,17 +8119,15 @@ mouse Compiled with support mouse.
mouseshape Compiled with support for 'mouseshape'.
multi_byte Compiled with support for 'encoding'
multi_byte_encoding 'encoding' is set to a multi-byte encoding.
-multi_byte_ime Compiled with support for IME input method.
multi_lang Compiled with support for multiple languages.
nvim This is Nvim. |has-patch|
-ole Compiled with OLE automation support for Win32.
path_extra Compiled with up/downwards search in 'path' and 'tags'
persistent_undo Compiled with support for persistent undo history.
postscript Compiled with PostScript file printing.
printer Compiled with |:hardcopy| support.
profile Compiled with |:profile| support.
-python Compiled with Python 2.x interface. |has-python|
-python3 Compiled with Python 3.x interface. |has-python|
+python Legacy Vim Python 2.x API is available. |has-python|
+python3 Legacy Vim Python 3.x API is available. |has-python|
quickfix Compiled with |quickfix| support.
reltime Compiled with |reltime()| support.
rightleft Compiled with 'rightleft' support.
@@ -8161,14 +8150,10 @@ tag_old_static Compiled with support for old static tags
|tag-old-static|.
tag_any_white Compiled with support for any white characters in tags
files |tag-any-white|.
-terminfo Compiled with terminfo instead of termcap.
termresponse Compiled with support for |t_RV| and |v:termresponse|.
textobjects Compiled with support for |text-objects|.
-tgetent Compiled with tgetent support, able to use a termcap
- or terminfo file.
timers Compiled with |timer_start()| support.
title Compiled with window title support |'title'|.
-toolbar Compiled with support for |gui-toolbar|.
unix Unix version of Vim.
unnamedplus Compiled with support for "unnamedplus" in 'clipboard'
user_commands User-defined commands.
@@ -8183,17 +8168,9 @@ vreplace Compiled with |gR| and |gr| commands.
wildignore Compiled with 'wildignore' option.
wildmenu Compiled with 'wildmenu' option.
win32 Windows version of Vim (32 or 64 bit).
-win32unix Windows version of Vim, using Unix files (Cygwin).
-win64 Windows version of Vim (64 bit).
winaltkeys Compiled with 'winaltkeys' option.
windows Compiled with support for more than one window.
writebackup Compiled with 'writebackup' default on.
-xfontset Compiled with X fontset support |xfontset|.
-xim Compiled with X input method support |xim|.
-xpm Compiled with pixmap support.
-xpm_w32 Compiled with pixmap support for Win32. (Only for
- backward compatibility. Use "xpm" instead.)
-x11 Compiled with X11 support.
*string-match*
Matching a pattern in a String
diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt
index 314799d083..0bd3a40a7c 100644
--- a/runtime/doc/gui.txt
+++ b/runtime/doc/gui.txt
@@ -854,30 +854,4 @@ This section describes other features which are related to the GUI.
- In the GUI, several normal keys may have modifiers in mappings etc, these
are <Space>, <Tab>, <NL>, <CR>, <Esc>.
-- To check in a Vim script if the GUI is being used, you can use something
- like this: >
-
- if has("gui_running")
- echo "yes, we have a GUI"
- else
- echo "Boring old console"
- endif
-< *setting-guifont*
-- When you use the same vimrc file on various systems, you can use something
- like this to set options specifically for each type of GUI: >
-
- if has("gui_running")
- if has("gui_gtk2")
- :set guifont=Luxi\ Mono\ 12
- elseif has("x11")
- " Also for GTK 1
- :set guifont=*-lucidatypewriter-medium-r-normal-*-*-180-*-*-m-*-*
- elseif has("gui_win32")
- :set guifont=Luxi_Mono:h12:cANSI
- endif
- endif
-
-A recommended Japanese font is MS Mincho. You can find info here:
-http://www.lexikan.com/mincho.htm
-
vim:tw=78:sw=4:ts=8:ft=help:norl:
diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index f9e97c4c38..c745d60ebc 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -489,7 +489,7 @@ examples and use them directly. Or type them literally, including the '<' and
==============================================================================
5. Modes, introduction *vim-modes-intro* *vim-modes*
-Vim has six BASIC modes:
+Vim has seven BASIC modes:
*Normal* *Normal-mode* *command-mode*
Normal mode In Normal mode you can enter all the normal editor
@@ -525,6 +525,13 @@ Ex mode Like Command-line mode, but after entering a command
you remain in Ex mode. Very limited editing of the
command line. |Ex-mode|
+ *Terminal-mode*
+Terminal mode In Terminal mode all input (except |c_CTRL-\_CTRL-N|)
+ is sent to the process running in the current
+ |terminal| buffer.
+ If the 'showmode' option is on "-- TERMINAL --" is shown
+ at the bottom of the window.
+
There are six ADDITIONAL modes. These are variants of the BASIC modes:
*Operator-pending* *Operator-pending-mode*
diff --git a/runtime/doc/job_control.txt b/runtime/doc/job_control.txt
index 5f9654d83c..da592d6eb0 100644
--- a/runtime/doc/job_control.txt
+++ b/runtime/doc/job_control.txt
@@ -75,6 +75,7 @@ Here's what is happening:
- The `JobHandler()` function is a callback passed to |jobstart()| to handle
various job events. It takes care of displaying stdout/stderr received from
the shells.
+ *on_stdout* *on_stderr* *on_exit*
- The arguments passed to `JobHandler()` are:
0: The job id
diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt
index 129a2dfed3..801ff75647 100644
--- a/runtime/doc/nvim_terminal_emulator.txt
+++ b/runtime/doc/nvim_terminal_emulator.txt
@@ -4,7 +4,7 @@
NVIM REFERENCE MANUAL by Thiago de Arruda
-Terminal emulator *terminal-emulator*
+Terminal emulator *terminal* *terminal-emulator*
Nvim embeds a VT220/xterm terminal emulator based on libvterm. The terminal is
presented as a special buffer type, asynchronously updated from the virtual
@@ -43,7 +43,7 @@ restarting the {cmd} when the session is loaded.
==============================================================================
Input *terminal-emulator-input*
-To send input, enter terminal-mode using any command that would enter "insert
+To send input, enter |Terminal-mode| using any command that would enter "insert
mode" in a normal buffer, such as |i| or |:startinsert|. In this mode all keys
except <C-\><C-N> are sent to the underlying program. Use <C-\><C-N> to return
to normal-mode. |CTRL-\_CTRL-N|
@@ -89,6 +89,16 @@ Options: 'scrollback'
Events: |TermOpen|, |TermClose|
Highlight groups: |hl-TermCursor|, |hl-TermCursorNC|
+Terminal sets local defaults for some options, which may differ from your
+global configuration.
+
+- 'list' is disabled
+- 'wrap' is disabled
+- 'relativenumber' is disabled in |Terminal-mode| (and cannot be enabled)
+
+You can change the defaults with a TermOpen autocommand: >
+ au TermOpen * setlocal list
+
Terminal colors can be customized with these variables:
- `{g,b}:terminal_color_$NUM`: The terminal color palette, where `$NUM` is the
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 8949d27dd6..0e8feb6321 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -4044,7 +4044,7 @@ A jump table for the options with a short description can be found at |Q_op|.
listing continues until finished.
*'mouse'* *E538*
-'mouse' string (default "a")
+'mouse' string (default "")
global
Enable the use of the mouse. Only works for certain terminals.
diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt
index 9e7f06ab63..cb7bf94ddc 100644
--- a/runtime/doc/usr_05.txt
+++ b/runtime/doc/usr_05.txt
@@ -140,15 +140,13 @@ quite complicated things. Still, it is just a sequence of commands that are
executed like you typed them.
>
- if &t_Co > 2 || has("gui_running")
- syntax on
- set hlsearch
- endif
-
-This switches on syntax highlighting, but only if colors are available. And
-the 'hlsearch' option tells Vim to highlight matches with the last used search
-pattern. The "if" command is very useful to set options only when some
-condition is met. More about that in |usr_41.txt|.
+ syntax on
+ set hlsearch
+
+This switches on syntax highlighting. And the 'hlsearch' option tells Vim to
+highlight matches with the last used search pattern. The "if" command is very
+useful to set options only when some condition is met. More about that in
+|usr_41.txt|.
*vimrc-filetype* >
filetype plugin indent on
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index e8da7eeb89..2679c2dabb 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -208,15 +208,20 @@ g8 Print the hex values of the bytes used in the
:sh[ell] Removed. |vim-differences| {Nvim}
*:terminal* *:te*
-:te[rminal][!] {cmd} Execute {cmd} with 'shell' in a |terminal-emulator|
- buffer. Equivalent to: >
+:te[rminal][!] [{cmd}] Execute {cmd} with 'shell' in a new |terminal| buffer.
+ Equivalent to: >
:enew
:call termopen('{cmd}')
:startinsert
<
- See |jobstart()|.
+ See |termopen()|.
- To enter terminal mode automatically: >
+ Without {cmd}, start an interactive shell.
+
+ Creating the terminal buffer fails when changes have been
+ made to the current buffer, unless 'hidden' is set.
+
+ To enter |Terminal-mode| automatically: >
autocmd BufEnter term://* startinsert
autocmd BufLeave term://* stopinsert
<
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 0b8d39d0d2..fc11708bd6 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -453,6 +453,26 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err)
return buf->b_changedtick;
}
+/// Get a list of dictionaries describing buffer-local mappings
+/// Note that the buffer key in the dictionary will represent the buffer
+/// handle where the mapping is present
+///
+/// @param mode The abbreviation for the mode
+/// @param buffer_id Buffer handle
+/// @param[out] err Error details, if any
+/// @returns An array of maparg() like dictionaries describing mappings
+ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err)
+ FUNC_API_SINCE(3)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return (Array)ARRAY_DICT_INIT;
+ }
+
+ return keymap_array(mode, buf);
+}
+
/// Sets a buffer-scoped (b:) variable
///
/// @param buffer Buffer handle
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 1d7b305da3..ef789b3ed4 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -24,6 +24,7 @@
#include "nvim/option_defs.h"
#include "nvim/version.h"
#include "nvim/lib/kvec.h"
+#include "nvim/getchar.h"
/// Helper structure for vim_to_object
typedef struct {
@@ -1034,3 +1035,41 @@ void api_set_error(Error *err, ErrorType errType, const char *format, ...)
err->type = errType;
}
+
+/// Get an array containing dictionaries describing mappings
+/// based on mode and buffer id
+///
+/// @param mode The abbreviation for the mode
+/// @param buf The buffer to get the mapping array. NULL for global
+/// @returns An array of maparg() like dictionaries describing mappings
+ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
+{
+ Array mappings = ARRAY_DICT_INIT;
+ dict_T *const dict = tv_dict_alloc();
+
+ // Convert the string mode to the integer mode
+ // that is stored within each mapblock
+ char_u *p = (char_u *)mode.data;
+ int int_mode = get_map_mode(&p, 0);
+
+ // Determine the desired buffer value
+ long buffer_value = (buf == NULL) ? 0 : buf->handle;
+
+ for (int i = 0; i < MAX_MAPHASH; i++) {
+ for (const mapblock_T *current_maphash = get_maphash(i, buf);
+ current_maphash;
+ current_maphash = current_maphash->m_next) {
+ // 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 } }));
+
+ tv_dict_clear(dict);
+ }
+ }
+ }
+ tv_dict_free(dict);
+
+ return mappings;
+}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 53e5f71fd4..0cffb2c87d 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -742,6 +742,17 @@ Dictionary nvim_get_mode(void)
return rv;
}
+/// Get a list of dictionaries describing global (i.e. non-buffer) mappings
+/// Note that the "buffer" key will be 0 to represent false.
+///
+/// @param mode The abbreviation for the mode
+/// @returns An array of maparg() like dictionaries describing mappings
+ArrayOf(Dictionary) nvim_get_keymap(String mode)
+ FUNC_API_SINCE(3)
+{
+ return keymap_array(mode, NULL);
+}
+
Array nvim_get_api_info(uint64_t channel_id)
FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_REMOTE_ONLY
{
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 88fa9726a4..d96b9355f1 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -438,6 +438,9 @@ typedef TV_DICTITEM_STRUCT(sizeof("changedtick")) ChangedtickDictItem;
#define BUF_HAS_QF_ENTRY 1
#define BUF_HAS_LL_ENTRY 2
+// Maximum number of maphash blocks we will have
+#define MAX_MAPHASH 256
+
/*
* buffer: structure that holds information about one file
*
@@ -526,8 +529,8 @@ struct file_buffer {
*/
uint64_t b_chartab[4];
- /* Table used for mappings local to a buffer. */
- mapblock_T *(b_maphash[256]);
+ // Table used for mappings local to a buffer.
+ mapblock_T *(b_maphash[MAX_MAPHASH]);
/* First abbreviation local to a buffer. */
mapblock_T *b_first_abbr;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 0e538598e1..7bfd638e86 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -12102,22 +12102,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
tv_dict_alloc_ret(rettv);
if (rhs != NULL) {
// Return a dictionary.
- char_u *lhs = str2special_save(mp->m_keys, true);
- char *const mapmode = map_mode_to_chars(mp->m_mode);
- dict_T *dict = rettv->vval.v_dict;
-
- tv_dict_add_str(dict, S_LEN("lhs"), (const char *)lhs);
- tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str);
- tv_dict_add_nr(dict, S_LEN("noremap"), mp->m_noremap ? 1 : 0);
- tv_dict_add_nr(dict, S_LEN("expr"), mp->m_expr ? 1 : 0);
- tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0);
- tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ID);
- tv_dict_add_nr(dict, S_LEN("buffer"), (varnumber_T)buffer_local);
- tv_dict_add_nr(dict, S_LEN("nowait"), mp->m_nowait ? 1 : 0);
- tv_dict_add_str(dict, S_LEN("mode"), mapmode);
-
- xfree(lhs);
- xfree(mapmode);
+ mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true);
}
}
}
@@ -12134,6 +12119,46 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv);
}
+/// Fill a dictionary with all applicable maparg() like dictionaries
+///
+/// @param dict The dictionary to be filled
+/// @param mp The maphash that contains the mapping information
+/// @param buffer_value The "buffer" value
+/// @param compatible True for compatible with old maparg() dict
+void mapblock_fill_dict(dict_T *const dict,
+ const mapblock_T *const mp,
+ long buffer_value,
+ bool compatible)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char_u *lhs = str2special_save(mp->m_keys, true);
+ char *const mapmode = map_mode_to_chars(mp->m_mode);
+ varnumber_T noremap_value;
+
+ if (compatible) {
+ // Keep old compatible behavior
+ // This is unable to determine whether a mapping is a <script> mapping
+ noremap_value = !!mp->m_noremap;
+ } else {
+ // Distinguish between <script> mapping
+ // If it's not a <script> mapping, check if it's a noremap
+ noremap_value = mp->m_noremap == REMAP_SCRIPT ? 2 : !!mp->m_noremap;
+ }
+
+ tv_dict_add_str(dict, S_LEN("lhs"), (const char *)lhs);
+ tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str);
+ tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value);
+ tv_dict_add_nr(dict, S_LEN("expr"), mp->m_expr ? 1 : 0);
+ tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0);
+ tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ID);
+ tv_dict_add_nr(dict, S_LEN("buffer"), (varnumber_T)buffer_value);
+ tv_dict_add_nr(dict, S_LEN("nowait"), mp->m_nowait ? 1 : 0);
+ tv_dict_add_str(dict, S_LEN("mode"), mapmode);
+
+ xfree(lhs);
+ xfree(mapmode);
+}
+
/*
* "map()" function
*/
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index c3c393f1ec..382caa8548 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -18,6 +18,7 @@
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/getchar.h"
+#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
@@ -47,6 +48,7 @@
#include "nvim/event/loop.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
+#include "nvim/api/private/handle.h"
/*
* These buffers are used for storing:
@@ -102,11 +104,10 @@ static int block_redo = FALSE;
(NORMAL + VISUAL + SELECTMODE + \
OP_PENDING)) ? (c1) : ((c1) ^ 0x80))
-/*
- * Each mapping is put in one of the 256 hash lists, to speed up finding it.
- */
-static mapblock_T *(maphash[256]);
-static int maphash_valid = FALSE;
+// Each mapping is put in one of the MAX_MAPHASH hash lists,
+// to speed up finding it.
+static mapblock_T *(maphash[MAX_MAPHASH]);
+static bool maphash_valid = false;
/*
* List used for abbreviations.
@@ -4237,3 +4238,17 @@ static bool typebuf_match_len(const uint8_t *str, int *mlen)
*mlen = i;
return str[i] == NUL; // matched the whole string
}
+
+/// Retrieve the mapblock at the index either globally or for a certain buffer
+///
+/// @param index The index in the maphash[]
+/// @param buf The buffer to get the maphash from. NULL for global
+mapblock_T *get_maphash(int index, buf_T *buf)
+ FUNC_ATTR_PURE
+{
+ if (index > MAX_MAPHASH) {
+ return NULL;
+ }
+
+ return (buf == NULL) ? maphash[index] : buf->b_maphash[index];
+}
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
index eb9335b03c..ee3ab96a83 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_process_unix.c
@@ -12,7 +12,7 @@
#include <sys/ioctl.h>
// forkpty is not in POSIX, so headers are platform-specific
-#if defined(__FreeBSD__)
+#if defined(__FreeBSD__) || defined (__DragonFly__)
# include <libutil.h>
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
# include <util.h>
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 0ec0d5df9d..ff51bf289e 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -188,6 +188,11 @@ typedef struct {
*/
#define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? wp->w_llist_ref : wp->w_llist)
+// Looking up a buffer can be slow if there are many. Remember the last one
+// to make this a lot faster if there are multiple matches in the same file.
+static char_u *qf_last_bufname = NULL;
+static bufref_T qf_last_bufref = { NULL, 0 };
+
/*
* Read the errorfile "efile" into memory, line by line, building the error
* list. Set the error list's title to qf_title.
@@ -968,6 +973,10 @@ qf_init_ext(
int retval = -1; // default: return error flag
int status;
+ // Do not used the cached buffer, it may have been wiped out.
+ xfree(qf_last_bufname);
+ qf_last_bufname = NULL;
+
fields.namebuf = xmalloc(CMDBUFFSIZE + 1);
fields.errmsglen = CMDBUFFSIZE + 1;
fields.errmsg = xmalloc(fields.errmsglen);
@@ -1399,11 +1408,6 @@ void copy_loclist(win_T *from, win_T *to)
to->w_llist->qf_curlist = qi->qf_curlist; /* current list */
}
-// Looking up a buffer can be slow if there are many. Remember the last one to
-// make this a lot faster if there are multiple matches in the same file.
-static char_u *qf_last_bufname = NULL;
-static bufref_T qf_last_bufref = { NULL, 0 };
-
// Get buffer number for file "directory.fname".
// Also sets the b_has_qf_entry flag.
static int qf_get_fnum(qf_info_T *qi, char_u *directory, char_u *fname)
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index b8b7085c5e..5b250ebf54 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -237,8 +237,6 @@ Terminal *terminal_open(TerminalOptions opts)
curbuf->b_p_scbk = p_scbk; // 'scrollback'
curbuf->b_p_tw = 0; // 'textwidth'
set_option_value("wrap", false, NULL, OPT_LOCAL);
- set_option_value("number", false, NULL, OPT_LOCAL);
- set_option_value("relativenumber", false, NULL, OPT_LOCAL);
set_option_value("list", false, NULL, OPT_LOCAL);
buf_set_term_title(curbuf, (char *)curbuf->b_ffname);
RESET_BINDING(curwin);
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index a754e5fdfb..732b0aaf74 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -137,6 +137,7 @@ endif
let s:flaky = [
\ 'Test_with_partial_callback()',
\ 'Test_oneshot()'
+ \ 'Test_lambda_with_timer()'
\ ]
" Locate Test_ functions and execute them.
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 4ff7993f4a..736d50ee8b 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -53,6 +53,9 @@ typedef enum TermType {
kTermiTerm,
kTermKonsole,
kTermRxvt,
+ kTermDTTerm,
+ kTermXTerm,
+ kTermTeraTerm,
} TermType;
typedef struct {
@@ -79,7 +82,10 @@ typedef struct {
UGrid grid;
kvec_t(Rect) invalid_regions;
int out_fd;
- bool can_use_terminal_scroll;
+ bool scroll_region_is_full_screen;
+ bool can_change_scroll_region;
+ bool can_set_lr_margin;
+ bool can_set_left_right_margin;
bool mouse_enabled;
bool busy;
cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
@@ -89,9 +95,12 @@ typedef struct {
struct {
int enable_mouse, disable_mouse;
int enable_bracketed_paste, disable_bracketed_paste;
+ int enable_lr_margin, disable_lr_margin;
int set_rgb_foreground, set_rgb_background;
int set_cursor_color;
int enable_focus_reporting, disable_focus_reporting;
+ int resize_screen;
+ int reset_scroll_region;
} unibi_ext;
} TUIData;
@@ -143,7 +152,7 @@ UI *tui_start(void)
static void terminfo_start(UI *ui)
{
TUIData *data = ui->data;
- data->can_use_terminal_scroll = true;
+ data->scroll_region_is_full_screen = true;
data->bufpos = 0;
data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE;
data->showing_mode = SHAPE_IDX_N;
@@ -152,8 +161,12 @@ static void terminfo_start(UI *ui)
data->unibi_ext.set_cursor_color = -1;
data->unibi_ext.enable_bracketed_paste = -1;
data->unibi_ext.disable_bracketed_paste = -1;
+ data->unibi_ext.enable_lr_margin = -1;
+ data->unibi_ext.disable_lr_margin = -1;
data->unibi_ext.enable_focus_reporting = -1;
data->unibi_ext.disable_focus_reporting = -1;
+ data->unibi_ext.resize_screen = -1;
+ data->unibi_ext.reset_scroll_region = -1;
data->out_fd = 1;
data->out_isatty = os_isatty(data->out_fd);
// setup unibilium
@@ -164,6 +177,13 @@ static void terminfo_start(UI *ui)
data->ut = unibi_dummy();
}
fix_terminfo(data);
+ data->can_change_scroll_region =
+ !!unibi_get_str(data->ut, unibi_change_scroll_region);
+ data->can_set_lr_margin =
+ !!unibi_get_str(data->ut, unibi_set_lr_margin);
+ data->can_set_left_right_margin =
+ !!unibi_get_str(data->ut, unibi_set_left_margin_parm)
+ && !!unibi_get_str(data->ut, unibi_set_right_margin_parm);
// Set 't_Co' from the result of unibilium & fix_terminfo.
t_colors = unibi_get_num(data->ut, unibi_max_colors);
// Enter alternate screen and clear
@@ -216,11 +236,6 @@ static void tui_terminal_start(UI *ui)
terminfo_start(ui);
update_size(ui);
signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH);
-
-#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
- data->input.tk_ti_hook_fn = tui_tk_ti_getstr;
-#endif
- term_input_init(&data->input, data->loop);
term_input_start(&data->input);
}
@@ -255,8 +270,14 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
#ifdef UNIX
signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT);
#endif
+
+#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
+ data->input.tk_ti_hook_fn = tui_tk_ti_getstr;
+#endif
+ term_input_init(&data->input, &tui_loop);
tui_terminal_start(ui);
data->stop = false;
+
// allow the main thread to continue, we are ready to start handling UI
// callbacks
CONTINUE(bridge);
@@ -418,15 +439,83 @@ static void clear_region(UI *ui, int top, int bot, int left, int right)
unibi_goto(ui, grid->row, grid->col);
}
+static bool can_use_scroll(UI * ui)
+{
+ TUIData *data = ui->data;
+ UGrid *grid = &data->grid;
+
+ return data->scroll_region_is_full_screen
+ || (data->can_change_scroll_region
+ && ((grid->left == 0 && grid->right == ui->width - 1)
+ || data->can_set_lr_margin
+ || data->can_set_left_right_margin));
+}
+
+static void set_scroll_region(UI *ui)
+{
+ TUIData *data = ui->data;
+ UGrid *grid = &data->grid;
+
+ data->params[0].i = grid->top;
+ data->params[1].i = grid->bot;
+ unibi_out(ui, unibi_change_scroll_region);
+ if (grid->left != 0 || grid->right != ui->width - 1) {
+ unibi_out(ui, data->unibi_ext.enable_lr_margin);
+ if (data->can_set_lr_margin) {
+ data->params[0].i = grid->left;
+ data->params[1].i = grid->right;
+ unibi_out(ui, unibi_set_lr_margin);
+ } else {
+ data->params[0].i = grid->left;
+ unibi_out(ui, unibi_set_left_margin_parm);
+ data->params[0].i = grid->right;
+ unibi_out(ui, unibi_set_right_margin_parm);
+ }
+ }
+ unibi_goto(ui, grid->row, grid->col);
+}
+
+static void reset_scroll_region(UI *ui)
+{
+ TUIData *data = ui->data;
+ UGrid *grid = &data->grid;
+
+ if (0 <= data->unibi_ext.reset_scroll_region) {
+ unibi_out(ui, data->unibi_ext.reset_scroll_region);
+ } else {
+ data->params[0].i = 0;
+ data->params[1].i = ui->height - 1;
+ unibi_out(ui, unibi_change_scroll_region);
+ }
+ if (grid->left != 0 || grid->right != ui->width - 1) {
+ if (data->can_set_lr_margin) {
+ data->params[0].i = 0;
+ data->params[1].i = ui->width - 1;
+ unibi_out(ui, unibi_set_lr_margin);
+ } else {
+ data->params[0].i = 0;
+ unibi_out(ui, unibi_set_left_margin_parm);
+ data->params[0].i = ui->width - 1;
+ unibi_out(ui, unibi_set_right_margin_parm);
+ }
+ unibi_out(ui, data->unibi_ext.disable_lr_margin);
+ }
+ unibi_goto(ui, grid->row, grid->col);
+}
+
static void tui_resize(UI *ui, Integer width, Integer height)
{
TUIData *data = ui->data;
ugrid_resize(&data->grid, (int)width, (int)height);
if (!got_winch) { // Try to resize the terminal window.
- char r[16]; // enough for 9999x9999
- snprintf(r, sizeof(r), "\x1b[8;%d;%dt", (int)height, (int)width);
- out(ui, r, strlen(r));
+ data->params[0].i = (int)height;
+ data->params[1].i = (int)width;
+ unibi_out(ui, data->unibi_ext.resize_screen);
+ // DECSLPP does not reset the scroll region.
+ if (data->scroll_region_is_full_screen) {
+ reset_scroll_region(ui);
+ }
} else { // Already handled the SIGWINCH signal; avoid double-resize.
got_winch = false;
}
@@ -613,10 +702,9 @@ static void tui_set_scroll_region(UI *ui, Integer top, Integer bot,
TUIData *data = ui->data;
ugrid_set_scroll_region(&data->grid, (int)top, (int)bot,
(int)left, (int)right);
- data->can_use_terminal_scroll =
+ data->scroll_region_is_full_screen =
left == 0 && right == ui->width - 1
- && ((top == 0 && bot == ui->height - 1)
- || unibi_get_str(data->ut, unibi_change_scroll_region));
+ && top == 0 && bot == ui->height - 1;
}
static void tui_scroll(UI *ui, Integer count)
@@ -626,31 +714,31 @@ static void tui_scroll(UI *ui, Integer count)
int clear_top, clear_bot;
ugrid_scroll(grid, (int)count, &clear_top, &clear_bot);
- if (data->can_use_terminal_scroll) {
+ if (can_use_scroll(ui)) {
+ bool scroll_clears_to_current_colour =
+ unibi_get_bool(data->ut, unibi_back_color_erase);
+
// Change terminal scroll region and move cursor to the top
- data->params[0].i = grid->top;
- data->params[1].i = grid->bot;
- unibi_out(ui, unibi_change_scroll_region);
+ if (!data->scroll_region_is_full_screen) {
+ set_scroll_region(ui);
+ }
unibi_goto(ui, grid->top, grid->left);
// also set default color attributes or some terminals can become funny
- HlAttrs clear_attrs = EMPTY_ATTRS;
- clear_attrs.foreground = grid->fg;
- clear_attrs.background = grid->bg;
- update_attrs(ui, clear_attrs);
- }
+ if (scroll_clears_to_current_colour) {
+ HlAttrs clear_attrs = EMPTY_ATTRS;
+ clear_attrs.foreground = grid->fg;
+ clear_attrs.background = grid->bg;
+ update_attrs(ui, clear_attrs);
+ }
- if (count > 0) {
- if (data->can_use_terminal_scroll) {
+ if (count > 0) {
if (count == 1) {
unibi_out(ui, unibi_delete_line);
} else {
data->params[0].i = (int)count;
unibi_out(ui, unibi_parm_delete_line);
}
- }
-
- } else {
- if (data->can_use_terminal_scroll) {
+ } else {
if (count == -1) {
unibi_out(ui, unibi_insert_line);
} else {
@@ -658,20 +746,16 @@ static void tui_scroll(UI *ui, Integer count)
unibi_out(ui, unibi_parm_insert_line);
}
}
- }
- if (data->can_use_terminal_scroll) {
// Restore terminal scroll region and cursor
- data->params[0].i = 0;
- data->params[1].i = ui->height - 1;
- unibi_out(ui, unibi_change_scroll_region);
+ if (!data->scroll_region_is_full_screen) {
+ reset_scroll_region(ui);
+ }
unibi_goto(ui, grid->row, grid->col);
- if (grid->bg != -1) {
- // Update the cleared area of the terminal if its builtin scrolling
- // facility was used and the background color is not the default. This is
- // required because scrolling may leave wrong background in the cleared
- // area.
+ if (!scroll_clears_to_current_colour) {
+ // This is required because scrolling will leave wrong background in the
+ // cleared area on non-bge terminals.
clear_region(ui, clear_top, clear_bot, grid->left, grid->right);
}
} else {
@@ -956,6 +1040,15 @@ static TermType detect_term(const char *term, const char *colorterm)
if (colorterm && strstr(colorterm, "gnome-terminal")) {
return kTermGnome;
}
+ if (STARTS_WITH(term, "xterm")) {
+ return kTermXTerm;
+ }
+ if (STARTS_WITH(term, "dtterm")) {
+ return kTermDTTerm;
+ }
+ if (STARTS_WITH(term, "teraterm")) {
+ return kTermTeraTerm;
+ }
return kTermUnknown;
}
@@ -976,14 +1069,14 @@ static void fix_terminfo(TUIData *data)
unibi_set_if_empty(ut, unibi_flash_screen, "\x1b[?5h$<20/>\x1b[?5l");
unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m");
unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]2");
- } else if (STARTS_WITH(term, "xterm")) {
+ } else if (data->term == kTermXTerm) {
unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]0;");
} else if (STARTS_WITH(term, "screen") || STARTS_WITH(term, "tmux")) {
unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_");
unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\");
}
- if (STARTS_WITH(term, "xterm") || data->term == kTermRxvt) {
+ if (data->term == kTermXTerm || data->term == kTermRxvt) {
const char *normal = unibi_get_str(ut, unibi_cursor_normal);
if (!normal) {
unibi_set_str(ut, unibi_cursor_normal, "\x1b[?25h");
@@ -997,11 +1090,21 @@ static void fix_terminfo(TUIData *data)
unibi_set_if_empty(ut, unibi_cursor_invisible, "\x1b[?25l");
unibi_set_if_empty(ut, unibi_flash_screen, "\x1b[?5h$<100/>\x1b[?5l");
unibi_set_if_empty(ut, unibi_exit_attribute_mode, "\x1b(B\x1b[m");
+ unibi_set_if_empty(ut, unibi_set_tb_margin, "\x1b[%i%p1%d;%p2%dr");
+ unibi_set_if_empty(ut, unibi_set_lr_margin, "\x1b[%i%p1%d;%p2%ds");
+ unibi_set_if_empty(ut, unibi_set_left_margin_parm, "\x1b[%i%p1%ds");
+ unibi_set_if_empty(ut, unibi_set_right_margin_parm, "\x1b[%i;%p2%ds");
unibi_set_if_empty(ut, unibi_change_scroll_region, "\x1b[%i%p1%d;%p2%dr");
unibi_set_if_empty(ut, unibi_clear_screen, "\x1b[H\x1b[2J");
unibi_set_if_empty(ut, unibi_from_status_line, "\x07");
+ unibi_set_bool(ut, unibi_back_color_erase, true);
}
+ data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, NULL,
+ "\x1b[?69h");
+ data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, NULL,
+ "\x1b[?69l");
+
data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL,
"\x1b[?2004h");
data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL,
@@ -1028,6 +1131,21 @@ static void fix_terminfo(TUIData *data)
unibi_set_str(ut, unibi_set_a_background, XTERM_SETAB);
}
+ // Only define this capability for terminal types that we know understand it.
+ if (data->term == kTermDTTerm // originated this extension
+ || data->term == kTermXTerm // per xterm ctlseqs doc
+ || data->term == kTermKonsole // per commentary in VT102Emulation.cpp
+ || data->term == kTermTeraTerm // per "Supported Control Functions" doc
+ || data->term == kTermRxvt) { // per command.C
+ data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, NULL,
+ "\x1b[8;%p1%d;%p2%dt");
+ }
+
+ if (data->term == kTermXTerm || data->term == kTermRxvt) {
+ data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, NULL,
+ "\x1b[r");
+ }
+
end:
// Fill some empty slots with common terminal strings
if (data->term == kTermiTerm) {
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
new file mode 100644
index 0000000000..833e0d2f3c
--- /dev/null
+++ b/test/functional/api/keymap_spec.lua
@@ -0,0 +1,246 @@
+
+local helpers = require('test.functional.helpers')(after_each)
+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 function local_copy(t)
+ local copy = {}
+ for k,v in pairs(t) do
+ copy[k] = v
+ end
+ return copy
+end
+
+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 = local_copy(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 = local_copy(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 = local_copy(foo_bar_map_table)
+ foolong_bar_map_table['lhs'] = 'foo_longer'
+ foolong_bar_map_table['rhs'] = 'bar_longer'
+
+ local buffer_table = local_copy(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 = local_copy(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 = local_copy(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)
+end)
diff --git a/test/functional/eval/map_functions_spec.lua b/test/functional/eval/map_functions_spec.lua
new file mode 100644
index 0000000000..a260522aa3
--- /dev/null
+++ b/test/functional/eval/map_functions_spec.lua
@@ -0,0 +1,120 @@
+
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eq = helpers.eq
+local eval = helpers.eval
+local funcs = helpers.funcs
+local nvim = helpers.nvim
+local source = helpers.source
+
+describe('maparg()', function()
+ before_each(clear)
+
+ 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 a dictionary', function()
+ nvim('command', 'nnoremap foo bar')
+ eq('bar', funcs.maparg('foo'))
+ eq(foo_bar_map_table, funcs.maparg('foo', 'n', false, true))
+ end)
+
+ it('returns 1 for silent when <silent> is used', function()
+ nvim('command', 'nnoremap <silent> foo bar')
+ eq(1, funcs.maparg('foo', 'n', false, true)['silent'])
+
+ nvim('command', 'nnoremap baz bat')
+ eq(0, funcs.maparg('baz', 'n', false, true)['silent'])
+ end)
+
+ it('returns an empty string when no map is present', function()
+ eq('', funcs.maparg('not a mapping'))
+ end)
+
+ it('returns an empty dictionary when no map is present and dict is requested', function()
+ eq({}, funcs.maparg('not a mapping', 'n', false, true))
+ end)
+
+ it('returns the same value for noremap and <script>', function()
+ nvim('command', 'inoremap <script> hello world')
+ nvim('command', 'inoremap this that')
+ eq(
+ funcs.maparg('hello', 'i', false, true)['noremap'],
+ funcs.maparg('this', 'i', false, true)['noremap']
+ )
+ end)
+
+ it('returns a boolean for buffer', function()
+ -- Open enough windows to know we aren't on buffer number 1
+ nvim('command', 'new')
+ nvim('command', 'new')
+ nvim('command', 'new')
+ nvim('command', 'cnoremap <buffer> this that')
+ eq(1, funcs.maparg('this', 'c', false, true)['buffer'])
+
+ -- Global will return 0 always
+ nvim('command', 'nnoremap other another')
+ eq(0, funcs.maparg('other', 'n', false, true)['buffer'])
+ end)
+
+ it('returns script numbers', function()
+ source([[
+ function! s:maparg_test_function() abort
+ return 'testing'
+ endfunction
+
+ nnoremap fizz :call <SID>maparg_test_function()<CR>
+ ]])
+ eq(1, funcs.maparg('fizz', 'n', false, true)['sid'])
+ eq('testing', nvim('call_function', '<SNR>1_maparg_test_function', {}))
+ end)
+
+ it('works with <F12> and others', function()
+ source([[
+ let g:maparg_test_var = 0
+
+ nnoremap <F12> :let g:maparg_test_var = 1<CR>
+ ]])
+ eq(0, eval('g:maparg_test_var'))
+ source([[
+ call feedkeys("\<F12>")
+ ]])
+ eq(1, eval('g:maparg_test_var'))
+
+ eq(':let g:maparg_test_var = 1<CR>', funcs.maparg('<F12>', 'n', false, true)['rhs'])
+ end)
+
+ it('works with <expr>', function()
+ source([[
+ let counter = 0
+ inoremap <expr> <C-L> ListItem()
+ inoremap <expr> <C-R> ListReset()
+
+ func ListItem()
+ let g:counter += 1
+ return g:counter . '. '
+ endfunc
+
+ func ListReset()
+ let g:counter = 0
+ return ''
+ endfunc
+
+ call feedkeys("i\<C-L>")
+ ]])
+ eq(1, eval('g:counter'))
+
+ local map_dict = funcs.maparg('<C-L>', 'i', false, true)
+ eq(1, map_dict['expr'])
+ eq('i', map_dict['mode'])
+ end)
+end)