aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt44
-rw-r--r--cmake/DefCmdTarget.cmake27
-rw-r--r--cmake/RunUncrustify.cmake5
-rw-r--r--cmake/lint.cmake34
-rwxr-xr-xcontrib/asan.sh28
-rw-r--r--runtime/doc/api.txt680
-rwxr-xr-xscripts/gen_vimdoc.py2
-rwxr-xr-xsrc/nvim/CMakeLists.txt14
-rw-r--r--src/nvim/api/buffer.c126
-rw-r--r--src/nvim/api/command.c1131
-rw-r--r--src/nvim/api/command.h11
-rw-r--r--src/nvim/api/extmark.c152
-rw-r--r--src/nvim/api/extmark.h1
-rw-r--r--src/nvim/api/options.c509
-rw-r--r--src/nvim/api/options.h9
-rw-r--r--src/nvim/api/private/dispatch.c2
-rw-r--r--src/nvim/api/private/helpers.c692
-rw-r--r--src/nvim/api/vim.c284
-rw-r--r--src/nvim/api/vimscript.c617
-rw-r--r--src/nvim/api/window.c38
-rw-r--r--src/nvim/buffer.c8
-rw-r--r--src/nvim/ex_docmd.c3
-rw-r--r--src/nvim/ex_docmd.h3
-rw-r--r--src/nvim/ex_getln.c17
-rw-r--r--src/nvim/window.c2
-rw-r--r--test/functional/lua/buffer_updates_spec.lua11
-rw-r--r--test/functional/ui/inccommand_spec.lua10
-rw-r--r--third-party/CMakeLists.txt24
-rw-r--r--third-party/cmake/BuildLibuv.cmake11
-rw-r--r--third-party/cmake/BuildLuajit.cmake23
-rw-r--r--third-party/cmake/BuildLuarocks.cmake8
-rw-r--r--third-party/cmake/BuildLuv.cmake11
-rw-r--r--third-party/cmake/BuildMsgpack.cmake13
33 files changed, 2302 insertions, 2248 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ee59d26a89..51124de96a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -71,13 +71,8 @@ else()
endif()
endif()
-if(CMAKE_CROSSCOMPILING AND NOT UNIX)
- list(INSERT CMAKE_FIND_ROOT_PATH 0 ${DEPS_PREFIX})
- list(INSERT CMAKE_PREFIX_PATH 0 ${DEPS_PREFIX}/../host/bin)
-else()
- list(INSERT CMAKE_PREFIX_PATH 0 ${DEPS_PREFIX})
- set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${DEPS_PREFIX}/lib/pkgconfig")
-endif()
+list(INSERT CMAKE_PREFIX_PATH 0 ${DEPS_PREFIX})
+set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${DEPS_PREFIX}/lib/pkgconfig")
# used for check_c_compiler_flag
include(CheckCCompilerFlag)
@@ -612,6 +607,33 @@ if(NOT BUSTED_OUTPUT_TYPE)
set(BUSTED_OUTPUT_TYPE "nvim")
endif()
+#
+# Lint
+#
+find_program(LUACHECK_PRG luacheck)
+find_program(STYLUA_PRG stylua)
+find_program(FLAKE8_PRG flake8)
+find_program(UNCRUSTIFY_PRG uncrustify)
+find_program(SHELLCHECK_PRG shellcheck)
+include(DefCmdTarget)
+def_cmd_target(lintlua ${LUACHECK_PRG} LUACHECK_PRG true)
+if(LUACHECK_PRG)
+ add_custom_command(OUTPUT lintlua-cmd APPEND COMMAND ${LUACHECK_PRG} -q runtime/ scripts/ src/ test/)
+endif()
+if(STYLUA_PRG)
+ add_custom_command(OUTPUT lintlua-cmd APPEND COMMAND ${STYLUA_PRG} --color=always --check runtime/)
+else()
+ add_custom_command(OUTPUT lintlua-cmd APPEND COMMAND ${CMAKE_COMMAND} -E echo "STYLUA_PRG not found")
+endif()
+def_cmd_target(lintpy ${FLAKE8_PRG} FLAKE8_PRG true)
+if(FLAKE8_PRG)
+ add_custom_command(OUTPUT lintpy-cmd APPEND COMMAND ${FLAKE8_PRG} contrib/ scripts/ src/ test/)
+endif()
+def_cmd_target(lintsh ${SHELLCHECK_PRG} SHELLCHECK_PRG true)
+if(SHELLCHECK_PRG)
+ add_custom_command(OUTPUT lintsh-cmd APPEND COMMAND ${SHELLCHECK_PRG} scripts/vim-patch.sh)
+endif()
+
include(InstallHelpers)
file(GLOB MANPAGES
@@ -745,14 +767,6 @@ if(BUSTED_LUA_PRG)
set_target_properties(functionaltest-lua PROPERTIES FOLDER test)
endif()
-foreach(TARGET IN ITEMS lintlua lintsh lintpy lintuncrustify)
- add_custom_target(${TARGET}
- COMMAND ${CMAKE_COMMAND}
- -DPROJECT_ROOT=${PROJECT_SOURCE_DIR}
- -DTARGET=${TARGET}
- -P ${PROJECT_SOURCE_DIR}/cmake/lint.cmake)
-endforeach()
-
#add uninstall target
if(NOT TARGET uninstall)
configure_file(
diff --git a/cmake/DefCmdTarget.cmake b/cmake/DefCmdTarget.cmake
new file mode 100644
index 0000000000..1ee5cdd60e
--- /dev/null
+++ b/cmake/DefCmdTarget.cmake
@@ -0,0 +1,27 @@
+# Defines a target named ${target} and a command with (symbolic) output
+# ${target}-cmd. If ${prg} is undefined the target prints "not found".
+#
+# - Use add_custom_command(…APPEND) to build the command after this.
+# - Use add_custom_target(…DEPENDS) to run the command from a target.
+function(def_cmd_target target prg prg_name prg_fatal)
+ # Define a mostly-empty command, which can be appended-to.
+ add_custom_command(OUTPUT ${target}-cmd
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ COMMAND ${CMAKE_COMMAND} -E echo "${target}"
+ )
+ # Symbolic (does not generate an artifact).
+ set_source_files_properties(${target}-cmd PROPERTIES SYMBOLIC "true")
+
+ if(prg OR NOT prg_fatal)
+ add_custom_target(${target}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ DEPENDS ${target}-cmd)
+ if(NOT prg)
+ add_custom_command(OUTPUT ${target}-cmd APPEND
+ COMMAND ${CMAKE_COMMAND} -E echo "${target}: SKIP: ${prg_name} not found")
+ endif()
+ else()
+ add_custom_target(${target} false
+ COMMENT "${target}: ${prg_name} not found")
+ endif()
+endfunction()
diff --git a/cmake/RunUncrustify.cmake b/cmake/RunUncrustify.cmake
new file mode 100644
index 0000000000..9ebbd6b77c
--- /dev/null
+++ b/cmake/RunUncrustify.cmake
@@ -0,0 +1,5 @@
+# HACK: This script is invoked with "cmake -P …" as a workaround to silence uncrustify.
+
+execute_process(
+ COMMAND ${UNCRUSTIFY_PRG} -c "${PROJECT_SOURCE_DIR}/src/uncrustify.cfg" -q --check ${LINT_NVIM_SOURCES}
+ OUTPUT_QUIET)
diff --git a/cmake/lint.cmake b/cmake/lint.cmake
deleted file mode 100644
index 1fb8c749a8..0000000000
--- a/cmake/lint.cmake
+++ /dev/null
@@ -1,34 +0,0 @@
-function(lint)
- cmake_parse_arguments(LINT "QUIET" "PROGRAM" "FLAGS;FILES" ${ARGN})
-
- if(LINT_QUIET)
- set(OUTPUT_QUIET OUTPUT_QUIET)
- elseif()
- set(OUTPUT_QUIET "")
- endif()
-
- find_program(PROGRAM_EXISTS ${LINT_PROGRAM})
- if(PROGRAM_EXISTS)
- execute_process(COMMAND ${LINT_PROGRAM} ${LINT_FLAGS} ${LINT_FILES}
- WORKING_DIRECTORY ${PROJECT_ROOT}
- RESULT_VARIABLE ret
- ${OUTPUT_QUIET})
- if(ret AND NOT ret EQUAL 0)
- message(FATAL_ERROR "FAILED: ${TARGET}")
- endif()
- else()
- message(STATUS "${TARGET}: ${LINT_PROGRAM} not found. SKIP.")
- endif()
-endfunction()
-
-if(${TARGET} STREQUAL "lintuncrustify")
- file(GLOB_RECURSE FILES ${PROJECT_ROOT}/src/nvim/*.[c,h])
- lint(PROGRAM uncrustify FLAGS -c src/uncrustify.cfg -q --check FILES ${FILES} QUIET)
-elseif(${TARGET} STREQUAL "lintpy")
- lint(PROGRAM flake8 FILES contrib/ scripts/ src/ test/)
-elseif(${TARGET} STREQUAL "lintsh")
- lint(PROGRAM shellcheck FILES scripts/vim-patch.sh)
-elseif(${TARGET} STREQUAL "lintlua")
- lint(PROGRAM luacheck FLAGS -q FILES runtime/ scripts/ src/ test/)
- lint(PROGRAM stylua FLAGS --color=always --check FILES runtime/)
-endif()
diff --git a/contrib/asan.sh b/contrib/asan.sh
new file mode 100755
index 0000000000..7e7dffa1af
--- /dev/null
+++ b/contrib/asan.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# Helper script to build and run neovim with Address Sanitizer enabled.
+# You may read more information in src/nvim/README.md in the section "Build
+# with ASAN".
+
+shopt -s nullglob
+
+root_path=$(git rev-parse --show-toplevel)
+log_path=$(mktemp -d)
+export CC='clang'
+
+# Change to detect_leaks=1 to detect memory leaks (slower).
+export ASAN_OPTIONS="detect_leaks=0:log_path=$log_path/asan"
+
+# Show backtraces in the logs.
+export UBSAN_OPTIONS="print_stacktrace=1"
+
+make -C "$root_path" CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON"
+VIMRUNTIME="$root_path"/runtime "$root_path"/build/bin/nvim
+
+# Need to manually reset terminal to avoid mangled output, nvim does not
+# properly restore the terminal when it crashes.
+tput reset
+
+for i in "$log_path"/*; do
+ cat "$i"
+done
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index e47c5cbd6f..331a4fe700 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -708,67 +708,6 @@ nvim_create_buf({listed}, {scratch}) *nvim_create_buf()*
See also: ~
buf_open_scratch
- *nvim_create_user_command()*
-nvim_create_user_command({name}, {command}, {*opts})
- Create a new user command |user-commands|
-
- {name} is the name of the new command. The name must begin
- with an uppercase letter.
-
- {command} is the replacement text or Lua function to execute.
-
- Example: >
- :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
- :SayHello
- Hello world!
-<
-
- Parameters: ~
- {name} Name of the new user command. Must begin with
- an uppercase letter.
- {command} Replacement command to execute when this user
- command is executed. When called from Lua, the
- command can also be a Lua function. The
- function is called with a single table argument
- that contains the following keys:
- • args: (string) The args passed to the
- command, if any |<args>|
- • fargs: (table) The args split by unescaped
- whitespace (when more than one argument is
- allowed), if any |<f-args>|
- • bang: (boolean) "true" if the command was
- executed with a ! modifier |<bang>|
- • line1: (number) The starting line of the
- command range |<line1>|
- • line2: (number) The final line of the command
- range |<line2>|
- • range: (number) The number of items in the
- command range: 0, 1, or 2 |<range>|
- • count: (number) Any count supplied |<count>|
- • reg: (string) The optional register, if
- specified |<reg>|
- • mods: (string) Command modifiers, if any
- |<mods>|
- • smods: (table) Command modifiers in a
- structured format. Has the same structure as
- the "mods" key of |nvim_parse_cmd()|.
- {opts} Optional command attributes. See
- |command-attributes| for more details. To use
- boolean attributes (such as |:command-bang| or
- |:command-bar|) set the value to "true". In
- addition to the string options listed in
- |:command-complete|, the "complete" key also
- accepts a Lua function which works like the
- "customlist" completion mode
- |:command-completion-customlist|. Additional
- parameters:
- • desc: (string) Used for listing the command
- when a Lua function is used for {command}.
- • force: (boolean, default true) Override any
- previous definition.
- • preview: (function) Preview callback for
- 'inccommand' |:command-preview|
-
nvim_del_current_line() *nvim_del_current_line()*
Deletes the current line.
@@ -800,12 +739,6 @@ nvim_del_mark({name}) *nvim_del_mark()*
|nvim_buf_del_mark()|
|nvim_get_mark()|
-nvim_del_user_command({name}) *nvim_del_user_command()*
- Delete a user-defined command.
-
- Parameters: ~
- {name} Name of the command to delete.
-
nvim_del_var({name}) *nvim_del_var()*
Removes a global (g:) variable.
@@ -923,15 +856,6 @@ nvim_feedkeys({keys}, {mode}, {escape_ks}) *nvim_feedkeys()*
feedkeys()
vim_strsave_escape_ks
-nvim_get_all_options_info() *nvim_get_all_options_info()*
- Gets the option information for all options.
-
- The dictionary has the full option names as keys and option
- metadata dictionaries as detailed at |nvim_get_option_info|.
-
- Return: ~
- dictionary of all options
-
nvim_get_api_info() *nvim_get_api_info()*
Returns a 2-tuple (Array), where item 0 is the current channel
id and item 1 is the |api-metadata| map (Dictionary).
@@ -996,19 +920,6 @@ nvim_get_color_map() *nvim_get_color_map()*
Return: ~
Map of color names and RGB values.
-nvim_get_commands({*opts}) *nvim_get_commands()*
- Gets a map of global (non-buffer-local) Ex commands.
-
- Currently only |user-commands| are supported, not builtin Ex
- commands.
-
- Parameters: ~
- {opts} Optional parameters. Currently only supports
- {"builtin":false}
-
- Return: ~
- Map of maps describing commands.
-
nvim_get_context({*opts}) *nvim_get_context()*
Gets a map of the current editor state.
@@ -1119,56 +1030,6 @@ nvim_get_mode() *nvim_get_mode()*
Attributes: ~
|api-fast|
-nvim_get_option({name}) *nvim_get_option()*
- Gets the global value of an option.
-
- Parameters: ~
- {name} Option name
-
- Return: ~
- Option value (global)
-
-nvim_get_option_info({name}) *nvim_get_option_info()*
- Gets the option information for one option
-
- Resulting dictionary has keys:
- • name: Name of the option (like 'filetype')
- • shortname: Shortened name of the option (like 'ft')
- • type: type of option ("string", "number" or "boolean")
- • default: The default value for the option
- • was_set: Whether the option was set.
- • last_set_sid: Last set script id (if any)
- • last_set_linenr: line number where option was set
- • last_set_chan: Channel where option was set (0 for local)
- • scope: one of "global", "win", or "buf"
- • global_local: whether win or buf option has a global value
- • commalist: List of comma separated values
- • flaglist: List of single char flags
-
- Parameters: ~
- {name} Option name
-
- Return: ~
- Option Information
-
-nvim_get_option_value({name}, {*opts}) *nvim_get_option_value()*
- Gets the value of an option. The behavior of this function
- matches that of |:set|: the local value of an option is
- returned if it exists; otherwise, the global value is
- returned. Local values always correspond to the current buffer
- or window. To get a buffer-local or window-local option for a
- specific buffer or window, use |nvim_buf_get_option()| or
- |nvim_win_get_option()|.
-
- Parameters: ~
- {name} Option name
- {opts} Optional parameters
- • scope: One of 'global' or 'local'. Analogous to
- |:setglobal| and |:setlocal|, respectively.
-
- Return: ~
- Option value
-
nvim_get_proc({pid}) *nvim_get_proc()*
Gets info describing process `pid`.
@@ -1670,33 +1531,6 @@ nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) *nvim_set_keymap()*
that takes a Lua function to call when the mapping
is executed.
-nvim_set_option({name}, {value}) *nvim_set_option()*
- Sets the global value of an option.
-
- Parameters: ~
- {name} Option name
- {value} New option value
-
- *nvim_set_option_value()*
-nvim_set_option_value({name}, {value}, {*opts})
- Sets the value of an option. The behavior of this function
- matches that of |:set|: for global-local options, both the
- global and local value are set unless otherwise specified with
- {scope}.
-
- Note the options {win} and {buf} cannot be used together.
-
- Parameters: ~
- {name} Option name
- {value} New option value
- {opts} Optional parameters
- • scope: One of 'global' or 'local'. Analogous to
- |:setglobal| and |:setlocal|, respectively.
- • win: |window-ID|. Used for setting window local
- option.
- • buf: Buffer number. Used for setting buffer
- local option.
-
nvim_set_var({name}, {value}) *nvim_set_var()*
Sets a global (g:) variable.
@@ -1770,36 +1604,6 @@ nvim_call_function({fn}, {args}) *nvim_call_function()*
Return: ~
Result of the function call
-nvim_cmd({*cmd}, {*opts}) *nvim_cmd()*
- Executes an Ex command.
-
- Unlike |nvim_command()| this command takes a structured
- Dictionary instead of a String. This allows for easier
- construction and manipulation of an Ex command. This also
- allows for things such as having spaces inside a command
- argument, expanding filenames in a command that otherwise
- doesn't expand filenames, etc.
-
- On execution error: fails with VimL error, updates v:errmsg.
-
- Parameters: ~
- {cmd} Command to execute. Must be a Dictionary that can
- contain the same values as the return value of
- |nvim_parse_cmd()| except "addr", "nargs" and
- "nextcmd" which are ignored if provided. All
- values except for "cmd" are optional.
- {opts} Optional parameters.
- • output: (boolean, default false) Whether to
- return command output.
-
- Return: ~
- Command output (non-error, non-shell |:!|) if `output` is
- true, else empty string.
-
- See also: ~
- |nvim_exec()|
- |nvim_command()|
-
nvim_command({command}) *nvim_command()*
Executes an Ex command.
@@ -1850,76 +1654,6 @@ nvim_exec({src}, {output}) *nvim_exec()*
|nvim_command()|
|nvim_cmd()|
-nvim_parse_cmd({str}, {opts}) *nvim_parse_cmd()*
- Parse command line.
-
- Doesn't check the validity of command arguments.
-
- Attributes: ~
- |api-fast|
-
- Parameters: ~
- {str} Command line string to parse. Cannot contain "\n".
- {opts} Optional parameters. Reserved for future use.
-
- Return: ~
- Dictionary containing command information, with these
- keys:
- • cmd: (string) Command name.
- • range: (array) Command <range>. Can have 0-2 elements
- depending on how many items the range contains. Has no
- elements if command doesn't accept a range or if no
- range was specified, one element if only a single range
- item was specified and two elements if both range items
- were specified.
- • count: (number) Any |<count>| that was supplied to the
- command. -1 if command cannot take a count.
- • reg: (number) The optional command |<register>|, if
- specified. Empty string if not specified or if command
- cannot take a register.
- • bang: (boolean) Whether command contains a |<bang>| (!)
- modifier.
- • args: (array) Command arguments.
- • addr: (string) Value of |:command-addr|. Uses short
- name.
- • nargs: (string) Value of |:command-nargs|.
- • nextcmd: (string) Next command if there are multiple
- commands separated by a |:bar|. Empty if there isn't a
- next command.
- • magic: (dictionary) Which characters have special
- meaning in the command arguments.
- • file: (boolean) The command expands filenames. Which
- means characters such as "%", "#" and wildcards are
- expanded.
- • bar: (boolean) The "|" character is treated as a
- command separator and the double quote character (")
- is treated as the start of a comment.
-
- • mods: (dictionary) |:command-modifiers|.
- • silent: (boolean) |:silent|.
- • emsg_silent: (boolean) |:silent!|.
- • sandbox: (boolean) |:sandbox|.
- • noautocmd: (boolean) |:noautocmd|.
- • browse: (boolean) |:browse|.
- • confirm: (boolean) |:confirm|.
- • hide: (boolean) |:hide|.
- • keepalt: (boolean) |:keepalt|.
- • keepjumps: (boolean) |:keepjumps|.
- • keepmarks: (boolean) |:keepmarks|.
- • keeppatterns: (boolean) |:keeppatterns|.
- • lockmarks: (boolean) |:lockmarks|.
- • noswapfile: (boolean) |:noswapfile|.
- • tab: (integer) |:tab|.
- • verbose: (integer) |:verbose|. -1 when omitted.
- • vertical: (boolean) |:vertical|.
- • split: (string) Split modifier string, is an empty
- string when there's no split modifier. If there is a
- split modifier it can be one of:
- • "aboveleft": |:aboveleft|.
- • "belowright": |:belowright|.
- • "topleft": |:topleft|.
- • "botright": |:botright|.
-
*nvim_parse_expression()*
nvim_parse_expression({expr}, {flags}, {highlight})
Parse a VimL expression.
@@ -2020,6 +1754,350 @@ nvim_parse_expression({expr}, {flags}, {highlight})
==============================================================================
+Command Functions *api-command*
+
+ *nvim_buf_create_user_command()*
+nvim_buf_create_user_command({buffer}, {name}, {command}, {*opts})
+ Create a new user command |user-commands| in the given buffer.
+
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer.
+
+ See also: ~
+ nvim_create_user_command
+
+ *nvim_buf_del_user_command()*
+nvim_buf_del_user_command({buffer}, {name})
+ Delete a buffer-local user-defined command.
+
+ Only commands created with |:command-buffer| or
+ |nvim_buf_create_user_command()| can be deleted with this
+ function.
+
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer.
+ {name} Name of the command to delete.
+
+nvim_buf_get_commands({buffer}, {*opts}) *nvim_buf_get_commands()*
+ Gets a map of buffer-local |user-commands|.
+
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
+ {opts} Optional parameters. Currently not used.
+
+ Return: ~
+ Map of maps describing commands.
+
+nvim_cmd({*cmd}, {*opts}) *nvim_cmd()*
+ Executes an Ex command.
+
+ Unlike |nvim_command()| this command takes a structured
+ Dictionary instead of a String. This allows for easier
+ construction and manipulation of an Ex command. This also
+ allows for things such as having spaces inside a command
+ argument, expanding filenames in a command that otherwise
+ doesn't expand filenames, etc.
+
+ On execution error: fails with VimL error, updates v:errmsg.
+
+ Parameters: ~
+ {cmd} Command to execute. Must be a Dictionary that can
+ contain the same values as the return value of
+ |nvim_parse_cmd()| except "addr", "nargs" and
+ "nextcmd" which are ignored if provided. All
+ values except for "cmd" are optional.
+ {opts} Optional parameters.
+ • output: (boolean, default false) Whether to
+ return command output.
+
+ Return: ~
+ Command output (non-error, non-shell |:!|) if `output` is
+ true, else empty string.
+
+ See also: ~
+ |nvim_exec()|
+ |nvim_command()|
+
+ *nvim_create_user_command()*
+nvim_create_user_command({name}, {command}, {*opts})
+ Create a new user command |user-commands|
+
+ {name} is the name of the new command. The name must begin
+ with an uppercase letter.
+
+ {command} is the replacement text or Lua function to execute.
+
+ Example: >
+ :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
+ :SayHello
+ Hello world!
+<
+
+ Parameters: ~
+ {name} Name of the new user command. Must begin with
+ an uppercase letter.
+ {command} Replacement command to execute when this user
+ command is executed. When called from Lua, the
+ command can also be a Lua function. The
+ function is called with a single table argument
+ that contains the following keys:
+ • args: (string) The args passed to the
+ command, if any |<args>|
+ • fargs: (table) The args split by unescaped
+ whitespace (when more than one argument is
+ allowed), if any |<f-args>|
+ • bang: (boolean) "true" if the command was
+ executed with a ! modifier |<bang>|
+ • line1: (number) The starting line of the
+ command range |<line1>|
+ • line2: (number) The final line of the command
+ range |<line2>|
+ • range: (number) The number of items in the
+ command range: 0, 1, or 2 |<range>|
+ • count: (number) Any count supplied |<count>|
+ • reg: (string) The optional register, if
+ specified |<reg>|
+ • mods: (string) Command modifiers, if any
+ |<mods>|
+ • smods: (table) Command modifiers in a
+ structured format. Has the same structure as
+ the "mods" key of |nvim_parse_cmd()|.
+ {opts} Optional command attributes. See
+ |command-attributes| for more details. To use
+ boolean attributes (such as |:command-bang| or
+ |:command-bar|) set the value to "true". In
+ addition to the string options listed in
+ |:command-complete|, the "complete" key also
+ accepts a Lua function which works like the
+ "customlist" completion mode
+ |:command-completion-customlist|. Additional
+ parameters:
+ • desc: (string) Used for listing the command
+ when a Lua function is used for {command}.
+ • force: (boolean, default true) Override any
+ previous definition.
+ • preview: (function) Preview callback for
+ 'inccommand' |:command-preview|
+
+nvim_del_user_command({name}) *nvim_del_user_command()*
+ Delete a user-defined command.
+
+ Parameters: ~
+ {name} Name of the command to delete.
+
+nvim_get_commands({*opts}) *nvim_get_commands()*
+ Gets a map of global (non-buffer-local) Ex commands.
+
+ Currently only |user-commands| are supported, not builtin Ex
+ commands.
+
+ Parameters: ~
+ {opts} Optional parameters. Currently only supports
+ {"builtin":false}
+
+ Return: ~
+ Map of maps describing commands.
+
+nvim_parse_cmd({str}, {opts}) *nvim_parse_cmd()*
+ Parse command line.
+
+ Doesn't check the validity of command arguments.
+
+ Attributes: ~
+ |api-fast|
+
+ Parameters: ~
+ {str} Command line string to parse. Cannot contain "\n".
+ {opts} Optional parameters. Reserved for future use.
+
+ Return: ~
+ Dictionary containing command information, with these
+ keys:
+ • cmd: (string) Command name.
+ • range: (array) Command <range>. Can have 0-2 elements
+ depending on how many items the range contains. Has no
+ elements if command doesn't accept a range or if no
+ range was specified, one element if only a single range
+ item was specified and two elements if both range items
+ were specified.
+ • count: (number) Any |<count>| that was supplied to the
+ command. -1 if command cannot take a count.
+ • reg: (number) The optional command |<register>|, if
+ specified. Empty string if not specified or if command
+ cannot take a register.
+ • bang: (boolean) Whether command contains a |<bang>| (!)
+ modifier.
+ • args: (array) Command arguments.
+ • addr: (string) Value of |:command-addr|. Uses short
+ name.
+ • nargs: (string) Value of |:command-nargs|.
+ • nextcmd: (string) Next command if there are multiple
+ commands separated by a |:bar|. Empty if there isn't a
+ next command.
+ • magic: (dictionary) Which characters have special
+ meaning in the command arguments.
+ • file: (boolean) The command expands filenames. Which
+ means characters such as "%", "#" and wildcards are
+ expanded.
+ • bar: (boolean) The "|" character is treated as a
+ command separator and the double quote character (")
+ is treated as the start of a comment.
+
+ • mods: (dictionary) |:command-modifiers|.
+ • silent: (boolean) |:silent|.
+ • emsg_silent: (boolean) |:silent!|.
+ • sandbox: (boolean) |:sandbox|.
+ • noautocmd: (boolean) |:noautocmd|.
+ • browse: (boolean) |:browse|.
+ • confirm: (boolean) |:confirm|.
+ • hide: (boolean) |:hide|.
+ • keepalt: (boolean) |:keepalt|.
+ • keepjumps: (boolean) |:keepjumps|.
+ • keepmarks: (boolean) |:keepmarks|.
+ • keeppatterns: (boolean) |:keeppatterns|.
+ • lockmarks: (boolean) |:lockmarks|.
+ • noswapfile: (boolean) |:noswapfile|.
+ • tab: (integer) |:tab|.
+ • verbose: (integer) |:verbose|. -1 when omitted.
+ • vertical: (boolean) |:vertical|.
+ • split: (string) Split modifier string, is an empty
+ string when there's no split modifier. If there is a
+ split modifier it can be one of:
+ • "aboveleft": |:aboveleft|.
+ • "belowright": |:belowright|.
+ • "topleft": |:topleft|.
+ • "botright": |:botright|.
+
+
+==============================================================================
+Options Functions *api-options*
+
+nvim_buf_get_option({buffer}, {name}) *nvim_buf_get_option()*
+ Gets a buffer option value
+
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
+ {name} Option name
+
+ Return: ~
+ Option value
+
+nvim_buf_set_option({buffer}, {name}, {value}) *nvim_buf_set_option()*
+ Sets a buffer option value. Passing 'nil' as value deletes the
+ option (only works if there's a global fallback)
+
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
+ {name} Option name
+ {value} Option value
+
+nvim_get_all_options_info() *nvim_get_all_options_info()*
+ Gets the option information for all options.
+
+ The dictionary has the full option names as keys and option
+ metadata dictionaries as detailed at |nvim_get_option_info|.
+
+ Return: ~
+ dictionary of all options
+
+nvim_get_option({name}) *nvim_get_option()*
+ Gets the global value of an option.
+
+ Parameters: ~
+ {name} Option name
+
+ Return: ~
+ Option value (global)
+
+nvim_get_option_info({name}) *nvim_get_option_info()*
+ Gets the option information for one option
+
+ Resulting dictionary has keys:
+ • name: Name of the option (like 'filetype')
+ • shortname: Shortened name of the option (like 'ft')
+ • type: type of option ("string", "number" or "boolean")
+ • default: The default value for the option
+ • was_set: Whether the option was set.
+ • last_set_sid: Last set script id (if any)
+ • last_set_linenr: line number where option was set
+ • last_set_chan: Channel where option was set (0 for local)
+ • scope: one of "global", "win", or "buf"
+ • global_local: whether win or buf option has a global value
+ • commalist: List of comma separated values
+ • flaglist: List of single char flags
+
+ Parameters: ~
+ {name} Option name
+
+ Return: ~
+ Option Information
+
+nvim_get_option_value({name}, {*opts}) *nvim_get_option_value()*
+ Gets the value of an option. The behavior of this function
+ matches that of |:set|: the local value of an option is
+ returned if it exists; otherwise, the global value is
+ returned. Local values always correspond to the current buffer
+ or window. To get a buffer-local or window-local option for a
+ specific buffer or window, use |nvim_buf_get_option()| or
+ |nvim_win_get_option()|.
+
+ Parameters: ~
+ {name} Option name
+ {opts} Optional parameters
+ • scope: One of 'global' or 'local'. Analogous to
+ |:setglobal| and |:setlocal|, respectively.
+
+ Return: ~
+ Option value
+
+nvim_set_option({name}, {value}) *nvim_set_option()*
+ Sets the global value of an option.
+
+ Parameters: ~
+ {name} Option name
+ {value} New option value
+
+ *nvim_set_option_value()*
+nvim_set_option_value({name}, {value}, {*opts})
+ Sets the value of an option. The behavior of this function
+ matches that of |:set|: for global-local options, both the
+ global and local value are set unless otherwise specified with
+ {scope}.
+
+ Note the options {win} and {buf} cannot be used together.
+
+ Parameters: ~
+ {name} Option name
+ {value} New option value
+ {opts} Optional parameters
+ • scope: One of 'global' or 'local'. Analogous to
+ |:setglobal| and |:setlocal|, respectively.
+ • win: |window-ID|. Used for setting window local
+ option.
+ • buf: Buffer number. Used for setting buffer
+ local option.
+
+nvim_win_get_option({window}, {name}) *nvim_win_get_option()*
+ Gets a window option value
+
+ Parameters: ~
+ {window} Window handle, or 0 for current window
+ {name} Option name
+
+ Return: ~
+ Option value
+
+nvim_win_set_option({window}, {name}, {value}) *nvim_win_set_option()*
+ Sets a window option value. Passing 'nil' as value deletes the
+ option(only works if there's a global fallback)
+
+ Parameters: ~
+ {window} Window handle, or 0 for current window
+ {name} Option name
+ {value} Option value
+
+
+==============================================================================
Buffer Functions *api-buffer*
@@ -2157,16 +2235,6 @@ nvim_buf_call({buffer}, {fun}) *nvim_buf_call()*
Return value of function. NB: will deepcopy lua values
currently, use upvalues to send lua references in and out.
- *nvim_buf_create_user_command()*
-nvim_buf_create_user_command({buffer}, {name}, {command}, {*opts})
- Create a new user command |user-commands| in the given buffer.
-
- Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer.
-
- See also: ~
- nvim_create_user_command
-
nvim_buf_del_keymap({buffer}, {mode}, {lhs}) *nvim_buf_del_keymap()*
Unmaps a buffer-local |mapping| for the given mode.
@@ -2194,18 +2262,6 @@ nvim_buf_del_mark({buffer}, {name}) *nvim_buf_del_mark()*
|nvim_buf_set_mark()|
|nvim_del_mark()|
- *nvim_buf_del_user_command()*
-nvim_buf_del_user_command({buffer}, {name})
- Delete a buffer-local user-defined command.
-
- Only commands created with |:command-buffer| or
- |nvim_buf_create_user_command()| can be deleted with this
- function.
-
- Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer.
- {name} Name of the command to delete.
-
nvim_buf_del_var({buffer}, {name}) *nvim_buf_del_var()*
Removes a buffer-scoped (b:) variable
@@ -2253,16 +2309,6 @@ nvim_buf_get_changedtick({buffer}) *nvim_buf_get_changedtick()*
Return: ~
`b:changedtick` value.
-nvim_buf_get_commands({buffer}, {*opts}) *nvim_buf_get_commands()*
- Gets a map of buffer-local |user-commands|.
-
- Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer
- {opts} Optional parameters. Currently not used.
-
- Return: ~
- Map of maps describing commands.
-
nvim_buf_get_keymap({buffer}, {mode}) *nvim_buf_get_keymap()*
Gets a list of buffer-local |mapping| definitions.
@@ -2341,16 +2387,6 @@ nvim_buf_get_offset({buffer}, {index}) *nvim_buf_get_offset()*
Return: ~
Integer byte offset, or -1 for unloaded buffer.
-nvim_buf_get_option({buffer}, {name}) *nvim_buf_get_option()*
- Gets a buffer option value
-
- Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer
- {name} Option name
-
- Return: ~
- Option value
-
*nvim_buf_get_text()*
nvim_buf_get_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col},
{opts})
@@ -2486,15 +2522,6 @@ nvim_buf_set_name({buffer}, {name}) *nvim_buf_set_name()*
{buffer} Buffer handle, or 0 for current buffer
{name} Buffer name
-nvim_buf_set_option({buffer}, {name}, {value}) *nvim_buf_set_option()*
- Sets a buffer option value. Passing 'nil' as value deletes the
- option (only works if there's a global fallback)
-
- Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer
- {name} Option name
- {value} Option value
-
*nvim_buf_set_text()*
nvim_buf_set_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col},
{replacement})
@@ -2968,16 +2995,6 @@ nvim_win_get_number({window}) *nvim_win_get_number()*
Return: ~
Window number
-nvim_win_get_option({window}, {name}) *nvim_win_get_option()*
- Gets a window option value
-
- Parameters: ~
- {window} Window handle, or 0 for current window
- {name} Option name
-
- Return: ~
- Option value
-
nvim_win_get_position({window}) *nvim_win_get_position()*
Gets the window position in display cells. First position is
zero.
@@ -3066,15 +3083,6 @@ nvim_win_set_height({window}, {height}) *nvim_win_set_height()*
{window} Window handle, or 0 for current window
{height} Height as a count of rows
-nvim_win_set_option({window}, {name}, {value}) *nvim_win_set_option()*
- Sets a window option value. Passing 'nil' as value deletes the
- option(only works if there's a global fallback)
-
- Parameters: ~
- {window} Window handle, or 0 for current window
- {name} Option name
- {value} Option value
-
nvim_win_set_var({window}, {name}, {value}) *nvim_win_set_var()*
Sets a window-scoped (w:) variable
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index 790c2ba52d..220b099df5 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -95,6 +95,8 @@ CONFIG = {
'section_order': [
'vim.c',
'vimscript.c',
+ 'command.c',
+ 'options.c',
'buffer.c',
'extmark.c',
'window.c',
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index c902ff6c50..1970a68393 100755
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -1,5 +1,7 @@
option(USE_GCOV "Enable gcov support" OFF)
+include(DefCmdTarget)
+
if(USE_GCOV)
if(CLANG_TSAN)
# GCOV and TSAN results in false data race reports
@@ -802,12 +804,22 @@ foreach(sfile ${LINT_NVIM_SOURCES})
endforeach()
add_custom_target(lintc DEPENDS ${LINT_TARGETS})
+def_cmd_target(lintuncrustify ${UNCRUSTIFY_PRG} UNCRUSTIFY_PRG false) # Non-fatal so that "lintc" target can depend on it.
+if(UNCRUSTIFY_PRG)
+ add_custom_command(OUTPUT lintuncrustify-cmd APPEND
+ COMMAND ${CMAKE_COMMAND}
+ -DUNCRUSTIFY_PRG=${UNCRUSTIFY_PRG}
+ -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}
+ -DLINT_NVIM_SOURCES=${LINT_NVIM_SOURCES}
+ -P ${PROJECT_SOURCE_DIR}/cmake/RunUncrustify.cmake)
+endif()
+
add_custom_target(
lintcfull
COMMAND
${LINT_PRG} --suppress-errors=${LINT_SUPPRESS_FILE} ${LINT_NVIM_REL_SOURCES}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
- DEPENDS ${LINT_PRG} ${LINT_NVIM_SOURCES} ${LINT_SUPPRESS_FILE}
+ DEPENDS ${LINT_PRG} ${LINT_NVIM_SOURCES} ${LINT_SUPPRESS_FILE} lintuncrustify
)
add_custom_target(generated-sources DEPENDS
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 536be1d832..9dc95de243 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -969,37 +969,6 @@ void nvim_buf_del_keymap(uint64_t channel_id, Buffer buffer, String mode, String
modify_keymap(channel_id, buffer, true, mode, lhs, rhs, NULL, err);
}
-/// Gets a map of buffer-local |user-commands|.
-///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param opts Optional parameters. Currently not used.
-/// @param[out] err Error details, if any.
-///
-/// @returns Map of maps describing commands.
-Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error *err)
- FUNC_API_SINCE(4)
-{
- bool global = (buffer == -1);
- bool builtin = api_object_to_bool(opts->builtin, "builtin", false, err);
- if (ERROR_SET(err)) {
- return (Dictionary)ARRAY_DICT_INIT;
- }
-
- if (global) {
- if (builtin) {
- api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
- return (Dictionary)ARRAY_DICT_INIT;
- }
- return commands_array(NULL);
- }
-
- buf_T *buf = find_buffer_by_handle(buffer, err);
- if (builtin || !buf) {
- return (Dictionary)ARRAY_DICT_INIT;
- }
- return commands_array(buf);
-}
-
/// Sets a buffer-scoped (b:) variable
///
/// @param buffer Buffer handle, or 0 for current buffer
@@ -1035,44 +1004,6 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err)
dict_set_var(buf->b_vars, name, NIL, true, false, err);
}
-/// Gets a buffer option value
-///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value
-Object nvim_buf_get_option(Buffer buffer, String name, Error *err)
- FUNC_API_SINCE(1)
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!buf) {
- return (Object)OBJECT_INIT;
- }
-
- return get_option_from(buf, SREQ_BUF, name, err);
-}
-
-/// Sets a buffer option value. Passing 'nil' as value deletes the option (only
-/// works if there's a global fallback)
-///
-/// @param channel_id
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param name Option name
-/// @param value Option value
-/// @param[out] err Error details, if any
-void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!buf) {
- return;
- }
-
- set_option_to(channel_id, buf, SREQ_BUF, name, value, err);
-}
-
/// Gets the full file name for the buffer
///
/// @param buffer Buffer handle, or 0 for current buffer
@@ -1369,63 +1300,6 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
return res;
}
-/// Create a new user command |user-commands| in the given buffer.
-///
-/// @param buffer Buffer handle, or 0 for current buffer.
-/// @param[out] err Error details, if any.
-/// @see nvim_create_user_command
-void nvim_buf_create_user_command(Buffer buffer, String name, Object command,
- Dict(user_command) *opts, Error *err)
- FUNC_API_SINCE(9)
-{
- buf_T *target_buf = find_buffer_by_handle(buffer, err);
- if (ERROR_SET(err)) {
- return;
- }
-
- buf_T *save_curbuf = curbuf;
- curbuf = target_buf;
- create_user_command(name, command, opts, UC_BUFFER, err);
- curbuf = save_curbuf;
-}
-
-/// Delete a buffer-local user-defined command.
-///
-/// Only commands created with |:command-buffer| or
-/// |nvim_buf_create_user_command()| can be deleted with this function.
-///
-/// @param buffer Buffer handle, or 0 for current buffer.
-/// @param name Name of the command to delete.
-/// @param[out] err Error details, if any.
-void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
- FUNC_API_SINCE(9)
-{
- garray_T *gap;
- if (buffer == -1) {
- gap = &ucmds;
- } else {
- buf_T *buf = find_buffer_by_handle(buffer, err);
- gap = &buf->b_ucmds;
- }
-
- for (int i = 0; i < gap->ga_len; i++) {
- ucmd_T *cmd = USER_CMD_GA(gap, i);
- if (!STRCMP(name.data, cmd->uc_name)) {
- free_ucmd(cmd);
-
- gap->ga_len -= 1;
-
- if (i < gap->ga_len) {
- memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T));
- }
-
- return;
- }
- }
-
- api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data);
-}
-
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
{
Dictionary rv = ARRAY_DICT_INIT;
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
new file mode 100644
index 0000000000..a7115c09a3
--- /dev/null
+++ b/src/nvim/api/command.c
@@ -0,0 +1,1131 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "nvim/api/command.h"
+#include "nvim/api/private/converter.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/autocmd.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/lua/executor.h"
+#include "nvim/ops.h"
+#include "nvim/window.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/command.c.generated.h"
+#endif
+
+/// Parse command line.
+///
+/// Doesn't check the validity of command arguments.
+///
+/// @param str Command line string to parse. Cannot contain "\n".
+/// @param opts Optional parameters. Reserved for future use.
+/// @param[out] err Error details, if any.
+/// @return Dictionary containing command information, with these keys:
+/// - cmd: (string) Command name.
+/// - range: (array) Command <range>. Can have 0-2 elements depending on how many items the
+/// range contains. Has no elements if command doesn't accept a range or if
+/// no range was specified, one element if only a single range item was
+/// specified and two elements if both range items were specified.
+/// - count: (number) Any |<count>| that was supplied to the command. -1 if command cannot
+/// take a count.
+/// - reg: (number) The optional command |<register>|, if specified. Empty string if not
+/// specified or if command cannot take a register.
+/// - bang: (boolean) Whether command contains a |<bang>| (!) modifier.
+/// - args: (array) Command arguments.
+/// - addr: (string) Value of |:command-addr|. Uses short name.
+/// - nargs: (string) Value of |:command-nargs|.
+/// - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|.
+/// Empty if there isn't a next command.
+/// - magic: (dictionary) Which characters have special meaning in the command arguments.
+/// - file: (boolean) The command expands filenames. Which means characters such as "%",
+/// "#" and wildcards are expanded.
+/// - bar: (boolean) The "|" character is treated as a command separator and the double
+/// quote character (\") is treated as the start of a comment.
+/// - mods: (dictionary) |:command-modifiers|.
+/// - silent: (boolean) |:silent|.
+/// - emsg_silent: (boolean) |:silent!|.
+/// - sandbox: (boolean) |:sandbox|.
+/// - noautocmd: (boolean) |:noautocmd|.
+/// - browse: (boolean) |:browse|.
+/// - confirm: (boolean) |:confirm|.
+/// - hide: (boolean) |:hide|.
+/// - keepalt: (boolean) |:keepalt|.
+/// - keepjumps: (boolean) |:keepjumps|.
+/// - keepmarks: (boolean) |:keepmarks|.
+/// - keeppatterns: (boolean) |:keeppatterns|.
+/// - lockmarks: (boolean) |:lockmarks|.
+/// - noswapfile: (boolean) |:noswapfile|.
+/// - tab: (integer) |:tab|.
+/// - verbose: (integer) |:verbose|. -1 when omitted.
+/// - vertical: (boolean) |:vertical|.
+/// - split: (string) Split modifier string, is an empty string when there's no split
+/// modifier. If there is a split modifier it can be one of:
+/// - "aboveleft": |:aboveleft|.
+/// - "belowright": |:belowright|.
+/// - "topleft": |:topleft|.
+/// - "botright": |:botright|.
+Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
+ FUNC_API_SINCE(10) FUNC_API_FAST
+{
+ Dictionary result = ARRAY_DICT_INIT;
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return result;
+ }
+
+ // Parse command line
+ exarg_T ea;
+ CmdParseInfo cmdinfo;
+ char *cmdline = string_to_cstr(str);
+ char *errormsg = NULL;
+
+ if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
+ if (errormsg != NULL) {
+ api_set_error(err, kErrorTypeException, "Error while parsing command line: %s", errormsg);
+ } else {
+ api_set_error(err, kErrorTypeException, "Error while parsing command line");
+ }
+ goto end;
+ }
+
+ // Parse arguments
+ Array args = ARRAY_DICT_INIT;
+ size_t length = STRLEN(ea.arg);
+
+ // For nargs = 1 or '?', pass the entire argument list as a single argument,
+ // otherwise split arguments by whitespace.
+ if (ea.argt & EX_NOSPC) {
+ if (*ea.arg != NUL) {
+ ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length)));
+ }
+ } else {
+ size_t end = 0;
+ size_t len = 0;
+ char *buf = xcalloc(length, sizeof(char));
+ bool done = false;
+
+ while (!done) {
+ done = uc_split_args_iter(ea.arg, length, &end, buf, &len);
+ if (len > 0) {
+ ADD(args, STRING_OBJ(cstrn_to_string(buf, len)));
+ }
+ }
+
+ xfree(buf);
+ }
+
+ ucmd_T *cmd = NULL;
+ if (ea.cmdidx == CMD_USER) {
+ cmd = USER_CMD(ea.useridx);
+ } else if (ea.cmdidx == CMD_USER_BUF) {
+ cmd = USER_CMD_GA(&curbuf->b_ucmds, ea.useridx);
+ }
+
+ if (cmd != NULL) {
+ PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name));
+ } else {
+ PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
+ }
+
+ if ((ea.argt & EX_RANGE) && ea.addr_count > 0) {
+ Array range = ARRAY_DICT_INIT;
+ if (ea.addr_count > 1) {
+ ADD(range, INTEGER_OBJ(ea.line1));
+ }
+ ADD(range, INTEGER_OBJ(ea.line2));
+ PUT(result, "range", ARRAY_OBJ(range));
+ } else {
+ PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT));
+ }
+
+ if (ea.argt & EX_COUNT) {
+ if (ea.addr_count > 0) {
+ PUT(result, "count", INTEGER_OBJ(ea.line2));
+ } else if (cmd != NULL) {
+ PUT(result, "count", INTEGER_OBJ(cmd->uc_def));
+ } else {
+ PUT(result, "count", INTEGER_OBJ(0));
+ }
+ } else {
+ PUT(result, "count", INTEGER_OBJ(-1));
+ }
+
+ char reg[2];
+ reg[0] = (char)ea.regname;
+ reg[1] = '\0';
+ PUT(result, "reg", CSTR_TO_OBJ(reg));
+
+ PUT(result, "bang", BOOLEAN_OBJ(ea.forceit));
+ PUT(result, "args", ARRAY_OBJ(args));
+
+ char nargs[2];
+ if (ea.argt & EX_EXTRA) {
+ if (ea.argt & EX_NOSPC) {
+ if (ea.argt & EX_NEEDARG) {
+ nargs[0] = '1';
+ } else {
+ nargs[0] = '?';
+ }
+ } else if (ea.argt & EX_NEEDARG) {
+ nargs[0] = '+';
+ } else {
+ nargs[0] = '*';
+ }
+ } else {
+ nargs[0] = '0';
+ }
+ nargs[1] = '\0';
+ PUT(result, "nargs", CSTR_TO_OBJ(nargs));
+
+ const char *addr;
+ switch (ea.addr_type) {
+ case ADDR_LINES:
+ addr = "line";
+ break;
+ case ADDR_ARGUMENTS:
+ addr = "arg";
+ break;
+ case ADDR_BUFFERS:
+ addr = "buf";
+ break;
+ case ADDR_LOADED_BUFFERS:
+ addr = "load";
+ break;
+ case ADDR_WINDOWS:
+ addr = "win";
+ break;
+ case ADDR_TABS:
+ addr = "tab";
+ break;
+ case ADDR_QUICKFIX:
+ addr = "qf";
+ break;
+ case ADDR_NONE:
+ addr = "none";
+ break;
+ default:
+ addr = "?";
+ break;
+ }
+ PUT(result, "addr", CSTR_TO_OBJ(addr));
+ PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd));
+
+ Dictionary mods = ARRAY_DICT_INIT;
+ PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.silent));
+ PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.emsg_silent));
+ PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.sandbox));
+ PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.noautocmd));
+ PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.tab));
+ PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose));
+ PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.browse));
+ PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.confirm));
+ PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.hide));
+ PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.keepalt));
+ PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.keepjumps));
+ PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.keepmarks));
+ PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.keeppatterns));
+ PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.lockmarks));
+ PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.noswapfile));
+ PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.split & WSP_VERT));
+
+ const char *split;
+ if (cmdinfo.cmdmod.split & WSP_BOT) {
+ split = "botright";
+ } else if (cmdinfo.cmdmod.split & WSP_TOP) {
+ split = "topleft";
+ } else if (cmdinfo.cmdmod.split & WSP_BELOW) {
+ split = "belowright";
+ } else if (cmdinfo.cmdmod.split & WSP_ABOVE) {
+ split = "aboveleft";
+ } else {
+ split = "";
+ }
+ PUT(mods, "split", CSTR_TO_OBJ(split));
+
+ PUT(result, "mods", DICTIONARY_OBJ(mods));
+
+ Dictionary magic = ARRAY_DICT_INIT;
+ PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file));
+ PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar));
+ PUT(result, "magic", DICTIONARY_OBJ(magic));
+end:
+ xfree(cmdline);
+ return result;
+}
+
+/// Executes an Ex command.
+///
+/// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This
+/// allows for easier construction and manipulation of an Ex command. This also allows for things
+/// such as having spaces inside a command argument, expanding filenames in a command that otherwise
+/// doesn't expand filenames, etc.
+///
+/// On execution error: fails with VimL error, updates v:errmsg.
+///
+/// @see |nvim_exec()|
+/// @see |nvim_command()|
+///
+/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as
+/// the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd"
+/// which are ignored if provided. All values except for "cmd" are optional.
+/// @param opts Optional parameters.
+/// - output: (boolean, default false) Whether to return command output.
+/// @param[out] err Error details, if any.
+/// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string.
+String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error *err)
+ FUNC_API_SINCE(10)
+{
+ exarg_T ea;
+ memset(&ea, 0, sizeof(ea));
+ ea.verbose_save = -1;
+ ea.save_msg_silent = -1;
+
+ CmdParseInfo cmdinfo;
+ memset(&cmdinfo, 0, sizeof(cmdinfo));
+ cmdinfo.verbose = -1;
+
+ char *cmdline = NULL;
+ char *cmdname = NULL;
+ char **args = NULL;
+ size_t argc = 0;
+
+ String retv = (String)STRING_INIT;
+
+#define OBJ_TO_BOOL(var, value, default, varname) \
+ do { \
+ var = api_object_to_bool(value, varname, default, err); \
+ if (ERROR_SET(err)) { \
+ goto end; \
+ } \
+ } while (0)
+
+#define VALIDATION_ERROR(...) \
+ do { \
+ api_set_error(err, kErrorTypeValidation, __VA_ARGS__); \
+ goto end; \
+ } while (0)
+
+ bool output;
+ OBJ_TO_BOOL(output, opts->output, false, "'output'");
+
+ // First, parse the command name and check if it exists and is valid.
+ if (!HAS_KEY(cmd->cmd) || cmd->cmd.type != kObjectTypeString
+ || cmd->cmd.data.string.data[0] == NUL) {
+ VALIDATION_ERROR("'cmd' must be a non-empty String");
+ }
+
+ cmdname = string_to_cstr(cmd->cmd.data.string);
+ ea.cmd = cmdname;
+
+ char *p = find_ex_command(&ea, NULL);
+
+ // If this looks like an undefined user command and there are CmdUndefined
+ // autocommands defined, trigger the matching autocommands.
+ if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd)
+ && has_event(EVENT_CMDUNDEFINED)) {
+ p = xstrdup(cmdname);
+ int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL);
+ xfree(p);
+ // If the autocommands did something and didn't cause an error, try
+ // finding the command again.
+ p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd;
+ }
+
+ if (p == NULL || ea.cmdidx == CMD_SIZE) {
+ VALIDATION_ERROR("Command not found: %s", cmdname);
+ }
+ if (is_cmd_ni(ea.cmdidx)) {
+ VALIDATION_ERROR("Command not implemented: %s", cmdname);
+ }
+
+ // Get the command flags so that we can know what type of arguments the command uses.
+ // Not required for a user command since `find_ex_command` already deals with it in that case.
+ if (!IS_USER_CMDIDX(ea.cmdidx)) {
+ ea.argt = get_cmd_argt(ea.cmdidx);
+ }
+
+ // Parse command arguments since it's needed to get the command address type.
+ if (HAS_KEY(cmd->args)) {
+ if (cmd->args.type != kObjectTypeArray) {
+ VALIDATION_ERROR("'args' must be an Array");
+ }
+ // Check if every argument is valid
+ for (size_t i = 0; i < cmd->args.data.array.size; i++) {
+ Object elem = cmd->args.data.array.items[i];
+ if (elem.type != kObjectTypeString) {
+ VALIDATION_ERROR("Command argument must be a String");
+ } else if (string_iswhite(elem.data.string)) {
+ VALIDATION_ERROR("Command argument must have non-whitespace characters");
+ }
+ }
+
+ argc = cmd->args.data.array.size;
+ bool argc_valid;
+
+ // Check if correct number of arguments is used.
+ switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
+ case EX_EXTRA | EX_NOSPC | EX_NEEDARG:
+ argc_valid = argc == 1;
+ break;
+ case EX_EXTRA | EX_NOSPC:
+ argc_valid = argc <= 1;
+ break;
+ case EX_EXTRA | EX_NEEDARG:
+ argc_valid = argc >= 1;
+ break;
+ case EX_EXTRA:
+ argc_valid = true;
+ break;
+ default:
+ argc_valid = argc == 0;
+ break;
+ }
+
+ if (!argc_valid) {
+ argc = 0; // Ensure that args array isn't erroneously freed at the end.
+ VALIDATION_ERROR("Incorrect number of arguments supplied");
+ }
+
+ if (argc != 0) {
+ args = xcalloc(argc, sizeof(char *));
+
+ for (size_t i = 0; i < argc; i++) {
+ args[i] = string_to_cstr(cmd->args.data.array.items[i].data.string);
+ }
+ }
+ }
+
+ // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()`
+ // since it only ever checks the first argument.
+ set_cmd_addr_type(&ea, argc > 0 ? (char_u *)args[0] : NULL);
+
+ if (HAS_KEY(cmd->range)) {
+ if (!(ea.argt & EX_RANGE)) {
+ VALIDATION_ERROR("Command cannot accept a range");
+ } else if (cmd->range.type != kObjectTypeArray) {
+ VALIDATION_ERROR("'range' must be an Array");
+ } else if (cmd->range.data.array.size > 2) {
+ VALIDATION_ERROR("'range' cannot contain more than two elements");
+ }
+
+ Array range = cmd->range.data.array;
+ ea.addr_count = (int)range.size;
+
+ for (size_t i = 0; i < range.size; i++) {
+ Object elem = range.items[i];
+ if (elem.type != kObjectTypeInteger || elem.data.integer < 0) {
+ VALIDATION_ERROR("'range' element must be a non-negative Integer");
+ }
+ }
+
+ if (range.size > 0) {
+ ea.line1 = (linenr_T)range.items[0].data.integer;
+ ea.line2 = (linenr_T)range.items[range.size - 1].data.integer;
+ }
+
+ if (invalid_range(&ea) != NULL) {
+ VALIDATION_ERROR("Invalid range provided");
+ }
+ }
+ if (ea.addr_count == 0) {
+ if (ea.argt & EX_DFLALL) {
+ set_cmd_dflall_range(&ea); // Default range for range=%
+ } else {
+ ea.line1 = ea.line2 = get_cmd_default_range(&ea); // Default range.
+
+ if (ea.addr_type == ADDR_OTHER) {
+ // Default is 1, not cursor.
+ ea.line2 = 1;
+ }
+ }
+ }
+
+ if (HAS_KEY(cmd->count)) {
+ if (!(ea.argt & EX_COUNT)) {
+ VALIDATION_ERROR("Command cannot accept a count");
+ } else if (cmd->count.type != kObjectTypeInteger || cmd->count.data.integer < 0) {
+ VALIDATION_ERROR("'count' must be a non-negative Integer");
+ }
+ set_cmd_count(&ea, cmd->count.data.integer, true);
+ }
+
+ if (HAS_KEY(cmd->reg)) {
+ if (!(ea.argt & EX_REGSTR)) {
+ VALIDATION_ERROR("Command cannot accept a register");
+ } else if (cmd->reg.type != kObjectTypeString || cmd->reg.data.string.size != 1) {
+ VALIDATION_ERROR("'reg' must be a single character");
+ }
+ char regname = cmd->reg.data.string.data[0];
+ if (regname == '=') {
+ VALIDATION_ERROR("Cannot use register \"=");
+ } else if (!valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx))) {
+ VALIDATION_ERROR("Invalid register: \"%c", regname);
+ }
+ ea.regname = (uint8_t)regname;
+ }
+
+ OBJ_TO_BOOL(ea.forceit, cmd->bang, false, "'bang'");
+ if (ea.forceit && !(ea.argt & EX_BANG)) {
+ VALIDATION_ERROR("Command cannot accept a bang");
+ }
+
+ if (HAS_KEY(cmd->magic)) {
+ if (cmd->magic.type != kObjectTypeDictionary) {
+ VALIDATION_ERROR("'magic' must be a Dictionary");
+ }
+
+ Dict(cmd_magic) magic = { 0 };
+ if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field,
+ cmd->magic.data.dictionary, err)) {
+ goto end;
+ }
+
+ OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'");
+ OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'");
+ } else {
+ cmdinfo.magic.file = ea.argt & EX_XFILE;
+ cmdinfo.magic.bar = ea.argt & EX_TRLBAR;
+ }
+
+ if (HAS_KEY(cmd->mods)) {
+ if (cmd->mods.type != kObjectTypeDictionary) {
+ VALIDATION_ERROR("'mods' must be a Dictionary");
+ }
+
+ Dict(cmd_mods) mods = { 0 };
+ if (!api_dict_to_keydict(&mods, KeyDict_cmd_mods_get_field, cmd->mods.data.dictionary, err)) {
+ goto end;
+ }
+
+ if (HAS_KEY(mods.tab)) {
+ if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) {
+ VALIDATION_ERROR("'mods.tab' must be a non-negative Integer");
+ }
+ cmdinfo.cmdmod.tab = (int)mods.tab.data.integer + 1;
+ }
+
+ if (HAS_KEY(mods.verbose)) {
+ if (mods.verbose.type != kObjectTypeInteger) {
+ VALIDATION_ERROR("'mods.verbose' must be a Integer");
+ } else if (mods.verbose.data.integer >= 0) {
+ // Silently ignore negative integers to allow mods.verbose to be set to -1.
+ cmdinfo.verbose = mods.verbose.data.integer;
+ }
+ }
+
+ bool vertical;
+ OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'");
+ cmdinfo.cmdmod.split |= (vertical ? WSP_VERT : 0);
+
+ if (HAS_KEY(mods.split)) {
+ if (mods.split.type != kObjectTypeString) {
+ VALIDATION_ERROR("'mods.split' must be a String");
+ }
+
+ if (*mods.split.data.string.data == NUL) {
+ // Empty string, do nothing.
+ } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0
+ || STRCMP(mods.split.data.string.data, "leftabove") == 0) {
+ cmdinfo.cmdmod.split |= WSP_ABOVE;
+ } else if (STRCMP(mods.split.data.string.data, "belowright") == 0
+ || STRCMP(mods.split.data.string.data, "rightbelow") == 0) {
+ cmdinfo.cmdmod.split |= WSP_BELOW;
+ } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) {
+ cmdinfo.cmdmod.split |= WSP_TOP;
+ } else if (STRCMP(mods.split.data.string.data, "botright") == 0) {
+ cmdinfo.cmdmod.split |= WSP_BOT;
+ } else {
+ VALIDATION_ERROR("Invalid value for 'mods.split'");
+ }
+ }
+
+ OBJ_TO_BOOL(cmdinfo.silent, mods.silent, false, "'mods.silent'");
+ OBJ_TO_BOOL(cmdinfo.emsg_silent, mods.emsg_silent, false, "'mods.emsg_silent'");
+ OBJ_TO_BOOL(cmdinfo.sandbox, mods.sandbox, false, "'mods.sandbox'");
+ OBJ_TO_BOOL(cmdinfo.noautocmd, mods.noautocmd, false, "'mods.noautocmd'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.browse, mods.browse, false, "'mods.browse'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.confirm, mods.confirm, false, "'mods.confirm'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.hide, mods.hide, false, "'mods.hide'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keepalt, mods.keepalt, false, "'mods.keepalt'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keepjumps, mods.keepjumps, false, "'mods.keepjumps'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keepmarks, mods.keepmarks, false, "'mods.keepmarks'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keeppatterns, mods.keeppatterns, false, "'mods.keeppatterns'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.lockmarks, mods.lockmarks, false, "'mods.lockmarks'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.noswapfile, mods.noswapfile, false, "'mods.noswapfile'");
+
+ if (cmdinfo.sandbox && !(ea.argt & EX_SBOXOK)) {
+ VALIDATION_ERROR("Command cannot be run in sandbox");
+ }
+ }
+
+ // Finally, build the command line string that will be stored inside ea.cmdlinep.
+ // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens.
+ build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc);
+ ea.cmdlinep = &cmdline;
+
+ garray_T capture_local;
+ const int save_msg_silent = msg_silent;
+ garray_T * const save_capture_ga = capture_ga;
+
+ if (output) {
+ ga_init(&capture_local, 1, 80);
+ capture_ga = &capture_local;
+ }
+
+ TRY_WRAP({
+ try_start();
+ if (output) {
+ msg_silent++;
+ }
+
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ execute_cmd(&ea, &cmdinfo, false);
+ });
+
+ if (output) {
+ capture_ga = save_capture_ga;
+ msg_silent = save_msg_silent;
+ }
+
+ try_end(err);
+ });
+
+ if (ERROR_SET(err)) {
+ goto clear_ga;
+ }
+
+ if (output && capture_local.ga_len > 1) {
+ retv = (String){
+ .data = capture_local.ga_data,
+ .size = (size_t)capture_local.ga_len,
+ };
+ // redir usually (except :echon) prepends a newline.
+ if (retv.data[0] == '\n') {
+ memmove(retv.data, retv.data + 1, retv.size - 1);
+ retv.data[retv.size - 1] = '\0';
+ retv.size = retv.size - 1;
+ }
+ goto end;
+ }
+clear_ga:
+ if (output) {
+ ga_clear(&capture_local);
+ }
+end:
+ xfree(cmdline);
+ xfree(cmdname);
+ xfree(ea.args);
+ xfree(ea.arglens);
+ for (size_t i = 0; i < argc; i++) {
+ xfree(args[i]);
+ }
+ xfree(args);
+
+ return retv;
+
+#undef OBJ_TO_BOOL
+#undef VALIDATION_ERROR
+}
+
+/// Check if a string contains only whitespace characters.
+static bool string_iswhite(String str)
+{
+ for (size_t i = 0; i < str.size; i++) {
+ if (!ascii_iswhite(str.data[i])) {
+ // Found a non-whitespace character
+ return false;
+ } else if (str.data[i] == NUL) {
+ // Terminate at first occurence of a NUL character
+ break;
+ }
+ }
+ return true;
+}
+
+/// Build cmdline string for command, used by `nvim_cmd()`.
+///
+/// @return OK or FAIL.
+static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args,
+ size_t argc)
+{
+ StringBuilder cmdline = KV_INITIAL_VALUE;
+
+ // Add command modifiers
+ if (cmdinfo->cmdmod.tab != 0) {
+ kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.tab - 1);
+ }
+ if (cmdinfo->verbose != -1) {
+ kv_printf(cmdline, "%ldverbose ", cmdinfo->verbose);
+ }
+
+ if (cmdinfo->emsg_silent) {
+ kv_concat(cmdline, "silent! ");
+ } else if (cmdinfo->silent) {
+ kv_concat(cmdline, "silent ");
+ }
+
+ switch (cmdinfo->cmdmod.split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) {
+ case WSP_ABOVE:
+ kv_concat(cmdline, "aboveleft ");
+ break;
+ case WSP_BELOW:
+ kv_concat(cmdline, "belowright ");
+ break;
+ case WSP_TOP:
+ kv_concat(cmdline, "topleft ");
+ break;
+ case WSP_BOT:
+ kv_concat(cmdline, "botright ");
+ break;
+ default:
+ break;
+ }
+
+#define CMDLINE_APPEND_IF(cond, str) \
+ do { \
+ if (cond) { \
+ kv_concat(cmdline, str); \
+ } \
+ } while (0)
+
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical ");
+ CMDLINE_APPEND_IF(cmdinfo->sandbox, "sandbox ");
+ CMDLINE_APPEND_IF(cmdinfo->noautocmd, "noautocmd ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.browse, "browse ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.confirm, "confirm ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.hide, "hide ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepalt, "keepalt ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepjumps, "keepjumps ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepmarks, "keepmarks ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.lockmarks, "lockmarks ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.noswapfile, "noswapfile ");
+#undef CMDLINE_APPEND_IF
+
+ // Command range / count.
+ if (eap->argt & EX_RANGE) {
+ if (eap->addr_count == 1) {
+ kv_printf(cmdline, "%" PRIdLINENR, eap->line2);
+ } else if (eap->addr_count > 1) {
+ kv_printf(cmdline, "%" PRIdLINENR ",%" PRIdLINENR, eap->line1, eap->line2);
+ eap->addr_count = 2; // Make sure address count is not greater than 2
+ }
+ }
+
+ // Keep the index of the position where command name starts, so eap->cmd can point to it.
+ size_t cmdname_idx = cmdline.size;
+ kv_printf(cmdline, "%s", eap->cmd);
+
+ // Command bang.
+ if (eap->argt & EX_BANG && eap->forceit) {
+ kv_printf(cmdline, "!");
+ }
+
+ // Command register.
+ if (eap->argt & EX_REGSTR && eap->regname) {
+ kv_printf(cmdline, " %c", eap->regname);
+ }
+
+ // Iterate through each argument and store the starting index and length of each argument
+ size_t *argidx = xcalloc(argc, sizeof(size_t));
+ eap->argc = argc;
+ eap->arglens = xcalloc(argc, sizeof(size_t));
+ for (size_t i = 0; i < argc; i++) {
+ argidx[i] = cmdline.size + 1; // add 1 to account for the space.
+ eap->arglens[i] = STRLEN(args[i]);
+ kv_printf(cmdline, " %s", args[i]);
+ }
+
+ // Now that all the arguments are appended, use the command index and argument indices to set the
+ // values of eap->cmd, eap->arg and eap->args.
+ eap->cmd = cmdline.items + cmdname_idx;
+ eap->args = xcalloc(argc, sizeof(char *));
+ for (size_t i = 0; i < argc; i++) {
+ eap->args[i] = cmdline.items + argidx[i];
+ }
+ // If there isn't an argument, make eap->arg point to end of cmdline.
+ eap->arg = argc > 0 ? eap->args[0] : cmdline.items + cmdline.size;
+
+ // Finally, make cmdlinep point to the cmdline string.
+ *cmdlinep = cmdline.items;
+ xfree(argidx);
+
+ // Replace, :make and :grep with 'makeprg' and 'grepprg'.
+ char *p = replace_makeprg(eap, eap->arg, cmdlinep);
+ if (p != eap->arg) {
+ // If replace_makeprg modified the cmdline string, correct the argument pointers.
+ assert(argc == 1);
+ eap->arg = p;
+ eap->args[0] = p;
+ }
+}
+
+/// Create a new user command |user-commands|
+///
+/// {name} is the name of the new command. The name must begin with an uppercase letter.
+///
+/// {command} is the replacement text or Lua function to execute.
+///
+/// Example:
+/// <pre>
+/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
+/// :SayHello
+/// Hello world!
+/// </pre>
+///
+/// @param name Name of the new user command. Must begin with an uppercase letter.
+/// @param command Replacement command to execute when this user command is executed. When called
+/// from Lua, the command can also be a Lua function. The function is called with a
+/// single table argument that contains the following keys:
+/// - args: (string) The args passed to the command, if any |<args>|
+/// - fargs: (table) The args split by unescaped whitespace (when more than one
+/// argument is allowed), if any |<f-args>|
+/// - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>|
+/// - line1: (number) The starting line of the command range |<line1>|
+/// - line2: (number) The final line of the command range |<line2>|
+/// - range: (number) The number of items in the command range: 0, 1, or 2 |<range>|
+/// - count: (number) Any count supplied |<count>|
+/// - reg: (string) The optional register, if specified |<reg>|
+/// - mods: (string) Command modifiers, if any |<mods>|
+/// - smods: (table) Command modifiers in a structured format. Has the same
+/// structure as the "mods" key of |nvim_parse_cmd()|.
+/// @param opts Optional command attributes. See |command-attributes| for more details. To use
+/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to
+/// "true". In addition to the string options listed in |:command-complete|, the
+/// "complete" key also accepts a Lua function which works like the "customlist"
+/// completion mode |:command-completion-customlist|. Additional parameters:
+/// - desc: (string) Used for listing the command when a Lua function is used for
+/// {command}.
+/// - force: (boolean, default true) Override any previous definition.
+/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
+/// @param[out] err Error details, if any.
+void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ create_user_command(name, command, opts, 0, err);
+}
+
+/// Delete a user-defined command.
+///
+/// @param name Name of the command to delete.
+/// @param[out] err Error details, if any.
+void nvim_del_user_command(String name, Error *err)
+ FUNC_API_SINCE(9)
+{
+ nvim_buf_del_user_command(-1, name, err);
+}
+
+/// Create a new user command |user-commands| in the given buffer.
+///
+/// @param buffer Buffer handle, or 0 for current buffer.
+/// @param[out] err Error details, if any.
+/// @see nvim_create_user_command
+void nvim_buf_create_user_command(Buffer buffer, String name, Object command,
+ Dict(user_command) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ buf_T *target_buf = find_buffer_by_handle(buffer, err);
+ if (ERROR_SET(err)) {
+ return;
+ }
+
+ buf_T *save_curbuf = curbuf;
+ curbuf = target_buf;
+ create_user_command(name, command, opts, UC_BUFFER, err);
+ curbuf = save_curbuf;
+}
+
+/// Delete a buffer-local user-defined command.
+///
+/// Only commands created with |:command-buffer| or
+/// |nvim_buf_create_user_command()| can be deleted with this function.
+///
+/// @param buffer Buffer handle, or 0 for current buffer.
+/// @param name Name of the command to delete.
+/// @param[out] err Error details, if any.
+void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
+ FUNC_API_SINCE(9)
+{
+ garray_T *gap;
+ if (buffer == -1) {
+ gap = &ucmds;
+ } else {
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ gap = &buf->b_ucmds;
+ }
+
+ for (int i = 0; i < gap->ga_len; i++) {
+ ucmd_T *cmd = USER_CMD_GA(gap, i);
+ if (!STRCMP(name.data, cmd->uc_name)) {
+ free_ucmd(cmd);
+
+ gap->ga_len -= 1;
+
+ if (i < gap->ga_len) {
+ memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T));
+ }
+
+ return;
+ }
+ }
+
+ api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data);
+}
+
+void create_user_command(String name, Object command, Dict(user_command) *opts, int flags,
+ Error *err)
+{
+ uint32_t argt = 0;
+ long def = -1;
+ cmd_addr_T addr_type_arg = ADDR_NONE;
+ int compl = EXPAND_NOTHING;
+ char *compl_arg = NULL;
+ char *rep = NULL;
+ LuaRef luaref = LUA_NOREF;
+ LuaRef compl_luaref = LUA_NOREF;
+ LuaRef preview_luaref = LUA_NOREF;
+
+ if (!uc_validate_name(name.data)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid command name");
+ goto err;
+ }
+
+ if (mb_islower(name.data[0])) {
+ api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
+ goto err;
+ }
+
+ if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) {
+ api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
+ goto err;
+ }
+
+ if (opts->nargs.type == kObjectTypeInteger) {
+ switch (opts->nargs.data.integer) {
+ case 0:
+ // Default value, nothing to do
+ break;
+ case 1:
+ argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+ } else if (opts->nargs.type == kObjectTypeString) {
+ if (opts->nargs.data.string.size > 1) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+
+ switch (opts->nargs.data.string.data[0]) {
+ case '*':
+ argt |= EX_EXTRA;
+ break;
+ case '?':
+ argt |= EX_EXTRA | EX_NOSPC;
+ break;
+ case '+':
+ argt |= EX_EXTRA | EX_NEEDARG;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+ } else if (HAS_KEY(opts->nargs)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+
+ if (HAS_KEY(opts->complete) && !argt) {
+ api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'");
+ goto err;
+ }
+
+ if (opts->range.type == kObjectTypeBoolean) {
+ if (opts->range.data.boolean) {
+ argt |= EX_RANGE;
+ addr_type_arg = ADDR_LINES;
+ }
+ } else if (opts->range.type == kObjectTypeString) {
+ if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) {
+ argt |= EX_RANGE | EX_DFLALL;
+ addr_type_arg = ADDR_LINES;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
+ goto err;
+ }
+ } else if (opts->range.type == kObjectTypeInteger) {
+ argt |= EX_RANGE | EX_ZEROR;
+ def = opts->range.data.integer;
+ addr_type_arg = ADDR_LINES;
+ } else if (HAS_KEY(opts->range)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
+ goto err;
+ }
+
+ if (opts->count.type == kObjectTypeBoolean) {
+ if (opts->count.data.boolean) {
+ argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
+ addr_type_arg = ADDR_OTHER;
+ def = 0;
+ }
+ } else if (opts->count.type == kObjectTypeInteger) {
+ argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
+ addr_type_arg = ADDR_OTHER;
+ def = opts->count.data.integer;
+ } else if (HAS_KEY(opts->count)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'");
+ goto err;
+ }
+
+ if (opts->addr.type == kObjectTypeString) {
+ if (parse_addr_type_arg(opts->addr.data.string.data, (int)opts->addr.data.string.size,
+ &addr_type_arg) != OK) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
+ goto err;
+ }
+
+ if (addr_type_arg != ADDR_LINES) {
+ argt |= EX_ZEROR;
+ }
+ } else if (HAS_KEY(opts->addr)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
+ goto err;
+ }
+
+ if (api_object_to_bool(opts->bang, "bang", false, err)) {
+ argt |= EX_BANG;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ if (api_object_to_bool(opts->bar, "bar", false, err)) {
+ argt |= EX_TRLBAR;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ if (api_object_to_bool(opts->register_, "register", false, err)) {
+ argt |= EX_REGSTR;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) {
+ argt |= EX_KEEPSCRIPT;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ bool force = api_object_to_bool(opts->force, "force", true, err);
+ if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ if (opts->complete.type == kObjectTypeLuaRef) {
+ compl = EXPAND_USER_LUA;
+ compl_luaref = api_new_luaref(opts->complete.data.luaref);
+ } else if (opts->complete.type == kObjectTypeString) {
+ if (parse_compl_arg(opts->complete.data.string.data,
+ (int)opts->complete.data.string.size, &compl, &argt,
+ &compl_arg) != OK) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
+ goto err;
+ }
+ } else if (HAS_KEY(opts->complete)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
+ goto err;
+ }
+
+ if (opts->preview.type == kObjectTypeLuaRef) {
+ argt |= EX_PREVIEW;
+ preview_luaref = api_new_luaref(opts->preview.data.luaref);
+ } else if (HAS_KEY(opts->preview)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'");
+ goto err;
+ }
+
+ switch (command.type) {
+ case kObjectTypeLuaRef:
+ luaref = api_new_luaref(command.data.luaref);
+ if (opts->desc.type == kObjectTypeString) {
+ rep = opts->desc.data.string.data;
+ } else {
+ snprintf((char *)IObuff, IOSIZE, "<Lua function %d>", luaref);
+ rep = (char *)IObuff;
+ }
+ break;
+ case kObjectTypeString:
+ rep = command.data.string.data;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function");
+ goto err;
+ }
+
+ if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
+ preview_luaref, addr_type_arg, luaref, force) != OK) {
+ api_set_error(err, kErrorTypeException, "Failed to create user command");
+ // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
+ }
+
+ return;
+
+err:
+ NLUA_CLEAR_REF(luaref);
+ NLUA_CLEAR_REF(compl_luaref);
+ xfree(compl_arg);
+}
+/// Gets a map of global (non-buffer-local) Ex commands.
+///
+/// Currently only |user-commands| are supported, not builtin Ex commands.
+///
+/// @param opts Optional parameters. Currently only supports
+/// {"builtin":false}
+/// @param[out] err Error details, if any.
+///
+/// @returns Map of maps describing commands.
+Dictionary nvim_get_commands(Dict(get_commands) *opts, Error *err)
+ FUNC_API_SINCE(4)
+{
+ return nvim_buf_get_commands(-1, opts, err);
+}
+
+/// Gets a map of buffer-local |user-commands|.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param opts Optional parameters. Currently not used.
+/// @param[out] err Error details, if any.
+///
+/// @returns Map of maps describing commands.
+Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error *err)
+ FUNC_API_SINCE(4)
+{
+ bool global = (buffer == -1);
+ bool builtin = api_object_to_bool(opts->builtin, "builtin", false, err);
+ if (ERROR_SET(err)) {
+ return (Dictionary)ARRAY_DICT_INIT;
+ }
+
+ if (global) {
+ if (builtin) {
+ api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
+ return (Dictionary)ARRAY_DICT_INIT;
+ }
+ return commands_array(NULL);
+ }
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (builtin || !buf) {
+ return (Dictionary)ARRAY_DICT_INIT;
+ }
+ return commands_array(buf);
+}
diff --git a/src/nvim/api/command.h b/src/nvim/api/command.h
new file mode 100644
index 0000000000..b1c9230551
--- /dev/null
+++ b/src/nvim/api/command.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_API_COMMAND_H
+#define NVIM_API_COMMAND_H
+
+#include "nvim/api/private/defs.h"
+#include "nvim/decoration.h"
+#include "nvim/ex_cmds.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/command.h.generated.h"
+#endif
+#endif // NVIM_API_COMMAND_H
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 9d80a5be5f..da1b6beeda 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -8,6 +8,7 @@
#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/charset.h"
#include "nvim/decoration_provider.h"
#include "nvim/extmark.h"
#include "nvim/highlight_group.h"
@@ -1033,3 +1034,154 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro
error:
decor_provider_clear(p);
}
+
+/// Gets the line and column of an extmark.
+///
+/// Extmarks may be queried by position, name or even special names
+/// in the future such as "cursor".
+///
+/// @param[out] lnum extmark line
+/// @param[out] colnr extmark column
+///
+/// @return true if the extmark was found, else false
+static bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int *row,
+ colnr_T *col, Error *err)
+{
+ // Check if it is mark id
+ if (obj.type == kObjectTypeInteger) {
+ Integer id = obj.data.integer;
+ if (id == 0) {
+ *row = 0;
+ *col = 0;
+ return true;
+ } else if (id == -1) {
+ *row = MAXLNUM;
+ *col = MAXCOL;
+ return true;
+ } else if (id < 0) {
+ api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
+ return false;
+ }
+
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
+ if (extmark.row >= 0) {
+ *row = extmark.row;
+ *col = extmark.col;
+ return true;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "No mark with requested id");
+ return false;
+ }
+
+ // Check if it is a position
+ } else if (obj.type == kObjectTypeArray) {
+ Array pos = obj.data.array;
+ if (pos.size != 2
+ || pos.items[0].type != kObjectTypeInteger
+ || pos.items[1].type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "Position must have 2 integer elements");
+ return false;
+ }
+ Integer pos_row = pos.items[0].data.integer;
+ Integer pos_col = pos.items[1].data.integer;
+ *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
+ *col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL);
+ return true;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "Position must be a mark id Integer or position Array");
+ return false;
+ }
+}
+// adapted from sign.c:sign_define_init_text.
+// TODO(lewis6991): Consider merging
+static int init_sign_text(char **sign_text, char *text)
+{
+ char *s;
+
+ char *endp = text + (int)STRLEN(text);
+
+ // Count cells and check for non-printable chars
+ int cells = 0;
+ for (s = text; s < endp; s += utfc_ptr2len(s)) {
+ if (!vim_isprintc(utf_ptr2char(s))) {
+ break;
+ }
+ cells += utf_ptr2cells(s);
+ }
+ // Currently must be empty, one or two display cells
+ if (s != endp || cells > 2) {
+ return FAIL;
+ }
+ if (cells < 1) {
+ return OK;
+ }
+
+ // Allocate one byte more if we need to pad up
+ // with a space.
+ size_t len = (size_t)(endp - text + ((cells == 1) ? 1 : 0));
+ *sign_text = xstrnsave(text, len);
+
+ if (cells == 1) {
+ STRCPY(*sign_text + len - 1, " ");
+ }
+
+ return OK;
+}
+
+VirtText parse_virt_text(Array chunks, Error *err, int *width)
+{
+ VirtText virt_text = KV_INITIAL_VALUE;
+ int w = 0;
+ for (size_t i = 0; i < chunks.size; i++) {
+ if (chunks.items[i].type != kObjectTypeArray) {
+ api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
+ goto free_exit;
+ }
+ Array chunk = chunks.items[i].data.array;
+ if (chunk.size == 0 || chunk.size > 2
+ || chunk.items[0].type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation,
+ "Chunk is not an array with one or two strings");
+ goto free_exit;
+ }
+
+ String str = chunk.items[0].data.string;
+
+ int hl_id = 0;
+ if (chunk.size == 2) {
+ Object hl = chunk.items[1];
+ if (hl.type == kObjectTypeArray) {
+ Array arr = hl.data.array;
+ for (size_t j = 0; j < arr.size; j++) {
+ hl_id = object_to_hl_id(arr.items[j], "virt_text highlight", err);
+ if (ERROR_SET(err)) {
+ goto free_exit;
+ }
+ if (j < arr.size - 1) {
+ kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
+ .hl_id = hl_id }));
+ }
+ }
+ } else {
+ hl_id = object_to_hl_id(hl, "virt_text highlight", err);
+ if (ERROR_SET(err)) {
+ goto free_exit;
+ }
+ }
+ }
+
+ char *text = transstr(str.size > 0 ? str.data : "", false); // allocates
+ w += (int)mb_string2cells(text);
+
+ kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
+ }
+
+ *width = w;
+ return virt_text;
+
+free_exit:
+ clear_virttext(&virt_text);
+ return virt_text;
+}
diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h
index c5e463cd86..74802c6efb 100644
--- a/src/nvim/api/extmark.h
+++ b/src/nvim/api/extmark.h
@@ -2,6 +2,7 @@
#define NVIM_API_EXTMARK_H
#include "nvim/api/private/defs.h"
+#include "nvim/decoration.h"
#include "nvim/map.h"
EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT);
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
new file mode 100644
index 0000000000..61d4becd32
--- /dev/null
+++ b/src/nvim/api/options.c
@@ -0,0 +1,509 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "nvim/api/options.h"
+#include "nvim/api/private/converter.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/autocmd.h"
+#include "nvim/buffer.h"
+#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/window.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/options.c.generated.h"
+#endif
+
+/// Gets the value of an option. The behavior of this function matches that of
+/// |:set|: the local value of an option is returned if it exists; otherwise,
+/// the global value is returned. Local values always correspond to the current
+/// buffer or window. To get a buffer-local or window-local option for a
+/// specific buffer or window, use |nvim_buf_get_option()| or
+/// |nvim_win_get_option()|.
+///
+/// @param name Option name
+/// @param opts Optional parameters
+/// - scope: One of 'global' or 'local'. Analogous to
+/// |:setglobal| and |:setlocal|, respectively.
+/// @param[out] err Error details, if any
+/// @return Option value
+Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ Object rv = OBJECT_INIT;
+
+ int scope = 0;
+ if (opts->scope.type == kObjectTypeString) {
+ if (!strcmp(opts->scope.data.string.data, "local")) {
+ scope = OPT_LOCAL;
+ } else if (!strcmp(opts->scope.data.string.data, "global")) {
+ scope = OPT_GLOBAL;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
+ goto end;
+ }
+ } else if (HAS_KEY(opts->scope)) {
+ api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
+ goto end;
+ }
+
+ long numval = 0;
+ char *stringval = NULL;
+ switch (get_option_value(name.data, &numval, &stringval, scope)) {
+ case 0:
+ rv = STRING_OBJ(cstr_as_string(stringval));
+ break;
+ case 1:
+ rv = INTEGER_OBJ(numval);
+ break;
+ case 2:
+ switch (numval) {
+ case 0:
+ case 1:
+ rv = BOOLEAN_OBJ(numval);
+ break;
+ default:
+ // Boolean options that return something other than 0 or 1 should return nil. Currently this
+ // only applies to 'autoread' which uses -1 as a local value to indicate "unset"
+ rv = NIL;
+ break;
+ }
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data);
+ goto end;
+ }
+
+end:
+ return rv;
+}
+
+/// Sets the value of an option. The behavior of this function matches that of
+/// |:set|: for global-local options, both the global and local value are set
+/// unless otherwise specified with {scope}.
+///
+/// Note the options {win} and {buf} cannot be used together.
+///
+/// @param name Option name
+/// @param value New option value
+/// @param opts Optional parameters
+/// - scope: One of 'global' or 'local'. Analogous to
+/// |:setglobal| and |:setlocal|, respectively.
+/// - win: |window-ID|. Used for setting window local option.
+/// - buf: Buffer number. Used for setting buffer local option.
+/// @param[out] err Error details, if any
+void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ int scope = 0;
+ if (opts->scope.type == kObjectTypeString) {
+ if (!strcmp(opts->scope.data.string.data, "local")) {
+ scope = OPT_LOCAL;
+ } else if (!strcmp(opts->scope.data.string.data, "global")) {
+ scope = OPT_GLOBAL;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
+ return;
+ }
+ } else if (HAS_KEY(opts->scope)) {
+ api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
+ return;
+ }
+
+ int opt_type = SREQ_GLOBAL;
+ void *to = NULL;
+
+ if (opts->win.type == kObjectTypeInteger) {
+ opt_type = SREQ_WIN;
+ to = find_window_by_handle((int)opts->win.data.integer, err);
+ } else if (HAS_KEY(opts->win)) {
+ api_set_error(err, kErrorTypeValidation, "invalid value for key: win");
+ return;
+ }
+
+ if (opts->buf.type == kObjectTypeInteger) {
+ scope = OPT_LOCAL;
+ opt_type = SREQ_BUF;
+ to = find_buffer_by_handle((int)opts->buf.data.integer, err);
+ } else if (HAS_KEY(opts->buf)) {
+ api_set_error(err, kErrorTypeValidation, "invalid value for key: buf");
+ return;
+ }
+
+ if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) {
+ api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together");
+ return;
+ }
+
+ if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) {
+ api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together");
+ return;
+ }
+
+ long numval = 0;
+ char *stringval = NULL;
+
+ switch (value.type) {
+ case kObjectTypeInteger:
+ numval = value.data.integer;
+ break;
+ case kObjectTypeBoolean:
+ numval = value.data.boolean ? 1 : 0;
+ break;
+ case kObjectTypeString:
+ stringval = value.data.string.data;
+ break;
+ case kObjectTypeNil:
+ scope |= OPT_CLEAR;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "invalid value for option");
+ return;
+ }
+
+ set_option_value_for(name.data, numval, stringval, scope, opt_type, to, err);
+}
+
+/// Gets the option information for all options.
+///
+/// The dictionary has the full option names as keys and option metadata
+/// dictionaries as detailed at |nvim_get_option_info|.
+///
+/// @return dictionary of all options
+Dictionary nvim_get_all_options_info(Error *err)
+ FUNC_API_SINCE(7)
+{
+ return get_all_vimoptions();
+}
+
+/// Gets the option information for one option
+///
+/// Resulting dictionary has keys:
+/// - name: Name of the option (like 'filetype')
+/// - shortname: Shortened name of the option (like 'ft')
+/// - type: type of option ("string", "number" or "boolean")
+/// - default: The default value for the option
+/// - was_set: Whether the option was set.
+///
+/// - last_set_sid: Last set script id (if any)
+/// - last_set_linenr: line number where option was set
+/// - last_set_chan: Channel where option was set (0 for local)
+///
+/// - scope: one of "global", "win", or "buf"
+/// - global_local: whether win or buf option has a global value
+///
+/// - commalist: List of comma separated values
+/// - flaglist: List of single char flags
+///
+///
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option Information
+Dictionary nvim_get_option_info(String name, Error *err)
+ FUNC_API_SINCE(7)
+{
+ return get_vimoption(name, err);
+}
+/// Sets the global value of an option.
+///
+/// @param channel_id
+/// @param name Option name
+/// @param value New option value
+/// @param[out] err Error details, if any
+void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+{
+ set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err);
+}
+
+/// Gets the global value of an option.
+///
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value (global)
+Object nvim_get_option(String name, Error *err)
+ FUNC_API_SINCE(1)
+{
+ return get_option_from(NULL, SREQ_GLOBAL, name, err);
+}
+
+/// Gets a buffer option value
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value
+Object nvim_buf_get_option(Buffer buffer, String name, Error *err)
+ FUNC_API_SINCE(1)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return (Object)OBJECT_INIT;
+ }
+
+ return get_option_from(buf, SREQ_BUF, name, err);
+}
+
+/// Sets a buffer option value. Passing 'nil' as value deletes the option (only
+/// works if there's a global fallback)
+///
+/// @param channel_id
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param name Option name
+/// @param value Option value
+/// @param[out] err Error details, if any
+void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return;
+ }
+
+ set_option_to(channel_id, buf, SREQ_BUF, name, value, err);
+}
+
+/// Gets a window option value
+///
+/// @param window Window handle, or 0 for current window
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value
+Object nvim_win_get_option(Window window, String name, Error *err)
+ FUNC_API_SINCE(1)
+{
+ win_T *win = find_window_by_handle(window, err);
+
+ if (!win) {
+ return (Object)OBJECT_INIT;
+ }
+
+ return get_option_from(win, SREQ_WIN, name, err);
+}
+
+/// Sets a window option value. Passing 'nil' as value deletes the option(only
+/// works if there's a global fallback)
+///
+/// @param channel_id
+/// @param window Window handle, or 0 for current window
+/// @param name Option name
+/// @param value Option value
+/// @param[out] err Error details, if any
+void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+{
+ win_T *win = find_window_by_handle(window, err);
+
+ if (!win) {
+ return;
+ }
+
+ set_option_to(channel_id, win, SREQ_WIN, name, value, err);
+}
+
+/// Gets the value of a global or local(buffer, window) option.
+///
+/// @param from If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
+/// to the window or buffer.
+/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
+/// @param name The option name
+/// @param[out] err Details of an error that may have occurred
+/// @return the option value
+Object get_option_from(void *from, int type, String name, Error *err)
+{
+ Object rv = OBJECT_INIT;
+
+ if (name.size == 0) {
+ api_set_error(err, kErrorTypeValidation, "Empty option name");
+ return rv;
+ }
+
+ // Return values
+ int64_t numval;
+ char *stringval = NULL;
+ int flags = get_option_value_strict(name.data, &numval, &stringval,
+ type, from);
+
+ if (!flags) {
+ api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'",
+ name.data);
+ return rv;
+ }
+
+ if (flags & SOPT_BOOL) {
+ rv.type = kObjectTypeBoolean;
+ rv.data.boolean = numval ? true : false;
+ } else if (flags & SOPT_NUM) {
+ rv.type = kObjectTypeInteger;
+ rv.data.integer = numval;
+ } else if (flags & SOPT_STRING) {
+ if (stringval) {
+ rv.type = kObjectTypeString;
+ rv.data.string.data = stringval;
+ rv.data.string.size = strlen(stringval);
+ } else {
+ api_set_error(err, kErrorTypeException,
+ "Failed to get value for option '%s'",
+ name.data);
+ }
+ } else {
+ api_set_error(err,
+ kErrorTypeException,
+ "Unknown type for option '%s'",
+ name.data);
+ }
+
+ return rv;
+}
+
+/// Sets the value of a global or local(buffer, window) option.
+///
+/// @param to If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
+/// to the window or buffer.
+/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
+/// @param name The option name
+/// @param[out] err Details of an error that may have occurred
+void set_option_to(uint64_t channel_id, void *to, int type, String name, Object value, Error *err)
+{
+ if (name.size == 0) {
+ api_set_error(err, kErrorTypeValidation, "Empty option name");
+ return;
+ }
+
+ int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
+
+ if (flags == 0) {
+ api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
+ name.data);
+ return;
+ }
+
+ if (value.type == kObjectTypeNil) {
+ if (type == SREQ_GLOBAL) {
+ api_set_error(err, kErrorTypeException, "Cannot unset option '%s'",
+ name.data);
+ return;
+ } else if (!(flags & SOPT_GLOBAL)) {
+ api_set_error(err,
+ kErrorTypeException,
+ "Cannot unset option '%s' "
+ "because it doesn't have a global value",
+ name.data);
+ return;
+ } else {
+ unset_global_local_option(name.data, to);
+ return;
+ }
+ }
+
+ int numval = 0;
+ char *stringval = NULL;
+
+ if (flags & SOPT_BOOL) {
+ if (value.type != kObjectTypeBoolean) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "Option '%s' requires a Boolean value",
+ name.data);
+ return;
+ }
+
+ numval = value.data.boolean;
+ } else if (flags & SOPT_NUM) {
+ if (value.type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "Option '%s' requires an integer value",
+ name.data);
+ return;
+ }
+
+ if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
+ api_set_error(err, kErrorTypeValidation,
+ "Value for option '%s' is out of range",
+ name.data);
+ return;
+ }
+
+ numval = (int)value.data.integer;
+ } else {
+ if (value.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation,
+ "Option '%s' requires a string value",
+ name.data);
+ return;
+ }
+
+ stringval = value.data.string.data;
+ }
+
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL))
+ ? 0 : (type == SREQ_GLOBAL)
+ ? OPT_GLOBAL : OPT_LOCAL;
+
+ set_option_value_for(name.data, numval, stringval,
+ opt_flags, type, to, err);
+ });
+}
+
+void set_option_value_for(char *key, long numval, char *stringval, int opt_flags, int opt_type,
+ void *from, Error *err)
+{
+ switchwin_T switchwin;
+ aco_save_T aco;
+
+ try_start();
+ switch (opt_type) {
+ case SREQ_WIN:
+ if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true)
+ == FAIL) {
+ restore_win_noblock(&switchwin, true);
+ if (try_end(err)) {
+ return;
+ }
+ api_set_error(err,
+ kErrorTypeException,
+ "Problem while switching windows");
+ return;
+ }
+ set_option_value_err(key, numval, stringval, opt_flags, err);
+ restore_win_noblock(&switchwin, true);
+ break;
+ case SREQ_BUF:
+ aucmd_prepbuf(&aco, (buf_T *)from);
+ set_option_value_err(key, numval, stringval, opt_flags, err);
+ aucmd_restbuf(&aco);
+ break;
+ case SREQ_GLOBAL:
+ set_option_value_err(key, numval, stringval, opt_flags, err);
+ break;
+ }
+
+ if (ERROR_SET(err)) {
+ return;
+ }
+
+ try_end(err);
+}
+
+static void set_option_value_err(char *key, long numval, char *stringval, int opt_flags, Error *err)
+{
+ char *errmsg;
+
+ if ((errmsg = set_option_value(key, numval, stringval, opt_flags))) {
+ if (try_end(err)) {
+ return;
+ }
+
+ api_set_error(err, kErrorTypeException, "%s", errmsg);
+ }
+}
diff --git a/src/nvim/api/options.h b/src/nvim/api/options.h
new file mode 100644
index 0000000000..efbfec3a6c
--- /dev/null
+++ b/src/nvim/api/options.h
@@ -0,0 +1,9 @@
+#ifndef NVIM_API_OPTIONS_H
+#define NVIM_API_OPTIONS_H
+
+#include "nvim/api/private/defs.h"
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/options.h.generated.h"
+#endif
+
+#endif // NVIM_API_OPTIONS_H
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index 3da2c2cde4..d6a6fc1219 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -23,7 +23,9 @@
// ===========================================================================
#include "nvim/api/autocmd.h"
#include "nvim/api/buffer.h"
+#include "nvim/api/command.h"
#include "nvim/api/extmark.h"
+#include "nvim/api/options.h"
#include "nvim/api/tabpage.h"
#include "nvim/api/ui.h"
#include "nvim/api/vim.h"
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index bdbbe9aa88..ff6a4c37e8 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -16,7 +16,6 @@
#include "nvim/assert.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
-#include "nvim/decoration.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds_defs.h"
@@ -32,8 +31,6 @@
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/option.h"
-#include "nvim/option_defs.h"
#include "nvim/ui.h"
#include "nvim/version.h"
#include "nvim/vim.h"
@@ -262,151 +259,6 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva
return rv;
}
-/// Gets the value of a global or local(buffer, window) option.
-///
-/// @param from If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
-/// to the window or buffer.
-/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
-/// @param name The option name
-/// @param[out] err Details of an error that may have occurred
-/// @return the option value
-Object get_option_from(void *from, int type, String name, Error *err)
-{
- Object rv = OBJECT_INIT;
-
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
- return rv;
- }
-
- // Return values
- int64_t numval;
- char *stringval = NULL;
- int flags = get_option_value_strict(name.data, &numval, &stringval,
- type, from);
-
- if (!flags) {
- api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'",
- name.data);
- return rv;
- }
-
- if (flags & SOPT_BOOL) {
- rv.type = kObjectTypeBoolean;
- rv.data.boolean = numval ? true : false;
- } else if (flags & SOPT_NUM) {
- rv.type = kObjectTypeInteger;
- rv.data.integer = numval;
- } else if (flags & SOPT_STRING) {
- if (stringval) {
- rv.type = kObjectTypeString;
- rv.data.string.data = stringval;
- rv.data.string.size = strlen(stringval);
- } else {
- api_set_error(err, kErrorTypeException,
- "Failed to get value for option '%s'",
- name.data);
- }
- } else {
- api_set_error(err,
- kErrorTypeException,
- "Unknown type for option '%s'",
- name.data);
- }
-
- return rv;
-}
-
-/// Sets the value of a global or local(buffer, window) option.
-///
-/// @param to If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
-/// to the window or buffer.
-/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
-/// @param name The option name
-/// @param[out] err Details of an error that may have occurred
-void set_option_to(uint64_t channel_id, void *to, int type, String name, Object value, Error *err)
-{
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
- return;
- }
-
- int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
-
- if (flags == 0) {
- api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
- name.data);
- return;
- }
-
- if (value.type == kObjectTypeNil) {
- if (type == SREQ_GLOBAL) {
- api_set_error(err, kErrorTypeException, "Cannot unset option '%s'",
- name.data);
- return;
- } else if (!(flags & SOPT_GLOBAL)) {
- api_set_error(err,
- kErrorTypeException,
- "Cannot unset option '%s' "
- "because it doesn't have a global value",
- name.data);
- return;
- } else {
- unset_global_local_option(name.data, to);
- return;
- }
- }
-
- int numval = 0;
- char *stringval = NULL;
-
- if (flags & SOPT_BOOL) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(err,
- kErrorTypeValidation,
- "Option '%s' requires a Boolean value",
- name.data);
- return;
- }
-
- numval = value.data.boolean;
- } else if (flags & SOPT_NUM) {
- if (value.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation,
- "Option '%s' requires an integer value",
- name.data);
- return;
- }
-
- if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
- api_set_error(err, kErrorTypeValidation,
- "Value for option '%s' is out of range",
- name.data);
- return;
- }
-
- numval = (int)value.data.integer;
- } else {
- if (value.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Option '%s' requires a string value",
- name.data);
- return;
- }
-
- stringval = value.data.string.data;
- }
-
- WITH_SCRIPT_CONTEXT(channel_id, {
- const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL))
- ? 0 : (type == SREQ_GLOBAL)
- ? OPT_GLOBAL : OPT_LOCAL;
-
- set_option_value_for(name.data, numval, stringval,
- opt_flags, type, to, err);
- });
-}
-
buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
{
if (buffer == 0) {
@@ -1035,59 +887,6 @@ Object copy_object(Object obj)
}
}
-void set_option_value_for(char *key, long numval, char *stringval, int opt_flags, int opt_type,
- void *from, Error *err)
-{
- switchwin_T switchwin;
- aco_save_T aco;
-
- try_start();
- switch (opt_type) {
- case SREQ_WIN:
- if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true)
- == FAIL) {
- restore_win_noblock(&switchwin, true);
- if (try_end(err)) {
- return;
- }
- api_set_error(err,
- kErrorTypeException,
- "Problem while switching windows");
- return;
- }
- set_option_value_err(key, numval, stringval, opt_flags, err);
- restore_win_noblock(&switchwin, true);
- break;
- case SREQ_BUF:
- aucmd_prepbuf(&aco, (buf_T *)from);
- set_option_value_err(key, numval, stringval, opt_flags, err);
- aucmd_restbuf(&aco);
- break;
- case SREQ_GLOBAL:
- set_option_value_err(key, numval, stringval, opt_flags, err);
- break;
- }
-
- if (ERROR_SET(err)) {
- return;
- }
-
- try_end(err);
-}
-
-static void set_option_value_err(char *key, long numval, char *stringval, int opt_flags, Error *err)
-{
- char *errmsg;
-
- if ((errmsg = set_option_value(key, numval, stringval, opt_flags))) {
- if (try_end(err)) {
- return;
- }
-
- api_set_error(err, kErrorTypeException, "%s", errmsg);
- }
-}
-
void api_set_error(Error *err, ErrorType errType, const char *format, ...)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PRINTF(3, 4)
{
@@ -1160,122 +959,6 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua)
return mappings;
}
-/// Gets the line and column of an extmark.
-///
-/// Extmarks may be queried by position, name or even special names
-/// in the future such as "cursor".
-///
-/// @param[out] lnum extmark line
-/// @param[out] colnr extmark column
-///
-/// @return true if the extmark was found, else false
-bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
- *row, colnr_T *col, Error *err)
-{
- // Check if it is mark id
- if (obj.type == kObjectTypeInteger) {
- Integer id = obj.data.integer;
- if (id == 0) {
- *row = 0;
- *col = 0;
- return true;
- } else if (id == -1) {
- *row = MAXLNUM;
- *col = MAXCOL;
- return true;
- } else if (id < 0) {
- api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
- return false;
- }
-
- ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
- if (extmark.row >= 0) {
- *row = extmark.row;
- *col = extmark.col;
- return true;
- } else {
- api_set_error(err, kErrorTypeValidation, "No mark with requested id");
- return false;
- }
-
- // Check if it is a position
- } else if (obj.type == kObjectTypeArray) {
- Array pos = obj.data.array;
- if (pos.size != 2
- || pos.items[0].type != kObjectTypeInteger
- || pos.items[1].type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation,
- "Position must have 2 integer elements");
- return false;
- }
- Integer pos_row = pos.items[0].data.integer;
- Integer pos_col = pos.items[1].data.integer;
- *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
- *col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL);
- return true;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "Position must be a mark id Integer or position Array");
- return false;
- }
-}
-
-VirtText parse_virt_text(Array chunks, Error *err, int *width)
-{
- VirtText virt_text = KV_INITIAL_VALUE;
- int w = 0;
- for (size_t i = 0; i < chunks.size; i++) {
- if (chunks.items[i].type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
- goto free_exit;
- }
- Array chunk = chunks.items[i].data.array;
- if (chunk.size == 0 || chunk.size > 2
- || chunk.items[0].type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Chunk is not an array with one or two strings");
- goto free_exit;
- }
-
- String str = chunk.items[0].data.string;
-
- int hl_id = 0;
- if (chunk.size == 2) {
- Object hl = chunk.items[1];
- if (hl.type == kObjectTypeArray) {
- Array arr = hl.data.array;
- for (size_t j = 0; j < arr.size; j++) {
- hl_id = object_to_hl_id(arr.items[j], "virt_text highlight", err);
- if (ERROR_SET(err)) {
- goto free_exit;
- }
- if (j < arr.size - 1) {
- kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
- .hl_id = hl_id }));
- }
- }
- } else {
- hl_id = object_to_hl_id(hl, "virt_text highlight", err);
- if (ERROR_SET(err)) {
- goto free_exit;
- }
- }
- }
-
- char *text = transstr(str.size > 0 ? str.data : "", false); // allocates
- w += (int)mb_string2cells(text);
-
- kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
- }
-
- *width = w;
- return virt_text;
-
-free_exit:
- clear_virttext(&virt_text);
- return virt_text;
-}
-
/// Force obj to bool.
/// If it fails, returns false and sets err
/// @param obj The object to coerce to a boolean
@@ -1424,212 +1107,6 @@ const char *get_default_stl_hl(win_T *wp, bool use_winbar)
}
}
-void create_user_command(String name, Object command, Dict(user_command) *opts, int flags,
- Error *err)
-{
- uint32_t argt = 0;
- long def = -1;
- cmd_addr_T addr_type_arg = ADDR_NONE;
- int compl = EXPAND_NOTHING;
- char *compl_arg = NULL;
- char *rep = NULL;
- LuaRef luaref = LUA_NOREF;
- LuaRef compl_luaref = LUA_NOREF;
- LuaRef preview_luaref = LUA_NOREF;
-
- if (!uc_validate_name(name.data)) {
- api_set_error(err, kErrorTypeValidation, "Invalid command name");
- goto err;
- }
-
- if (mb_islower(name.data[0])) {
- api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
- goto err;
- }
-
- if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) {
- api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
- goto err;
- }
-
- if (opts->nargs.type == kObjectTypeInteger) {
- switch (opts->nargs.data.integer) {
- case 0:
- // Default value, nothing to do
- break;
- case 1:
- argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
- }
- } else if (opts->nargs.type == kObjectTypeString) {
- if (opts->nargs.data.string.size > 1) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
- }
-
- switch (opts->nargs.data.string.data[0]) {
- case '*':
- argt |= EX_EXTRA;
- break;
- case '?':
- argt |= EX_EXTRA | EX_NOSPC;
- break;
- case '+':
- argt |= EX_EXTRA | EX_NEEDARG;
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
- }
- } else if (HAS_KEY(opts->nargs)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
- }
-
- if (HAS_KEY(opts->complete) && !argt) {
- api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'");
- goto err;
- }
-
- if (opts->range.type == kObjectTypeBoolean) {
- if (opts->range.data.boolean) {
- argt |= EX_RANGE;
- addr_type_arg = ADDR_LINES;
- }
- } else if (opts->range.type == kObjectTypeString) {
- if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) {
- argt |= EX_RANGE | EX_DFLALL;
- addr_type_arg = ADDR_LINES;
- } else {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
- goto err;
- }
- } else if (opts->range.type == kObjectTypeInteger) {
- argt |= EX_RANGE | EX_ZEROR;
- def = opts->range.data.integer;
- addr_type_arg = ADDR_LINES;
- } else if (HAS_KEY(opts->range)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
- goto err;
- }
-
- if (opts->count.type == kObjectTypeBoolean) {
- if (opts->count.data.boolean) {
- argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
- addr_type_arg = ADDR_OTHER;
- def = 0;
- }
- } else if (opts->count.type == kObjectTypeInteger) {
- argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
- addr_type_arg = ADDR_OTHER;
- def = opts->count.data.integer;
- } else if (HAS_KEY(opts->count)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'");
- goto err;
- }
-
- if (opts->addr.type == kObjectTypeString) {
- if (parse_addr_type_arg(opts->addr.data.string.data, (int)opts->addr.data.string.size,
- &addr_type_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
- goto err;
- }
-
- if (addr_type_arg != ADDR_LINES) {
- argt |= EX_ZEROR;
- }
- } else if (HAS_KEY(opts->addr)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
- goto err;
- }
-
- if (api_object_to_bool(opts->bang, "bang", false, err)) {
- argt |= EX_BANG;
- } else if (ERROR_SET(err)) {
- goto err;
- }
-
- if (api_object_to_bool(opts->bar, "bar", false, err)) {
- argt |= EX_TRLBAR;
- } else if (ERROR_SET(err)) {
- goto err;
- }
-
- if (api_object_to_bool(opts->register_, "register", false, err)) {
- argt |= EX_REGSTR;
- } else if (ERROR_SET(err)) {
- goto err;
- }
-
- if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) {
- argt |= EX_KEEPSCRIPT;
- } else if (ERROR_SET(err)) {
- goto err;
- }
-
- bool force = api_object_to_bool(opts->force, "force", true, err);
- if (ERROR_SET(err)) {
- goto err;
- }
-
- if (opts->complete.type == kObjectTypeLuaRef) {
- compl = EXPAND_USER_LUA;
- compl_luaref = api_new_luaref(opts->complete.data.luaref);
- } else if (opts->complete.type == kObjectTypeString) {
- if (parse_compl_arg(opts->complete.data.string.data,
- (int)opts->complete.data.string.size, &compl, &argt,
- &compl_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
- goto err;
- }
- } else if (HAS_KEY(opts->complete)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
- goto err;
- }
-
- if (opts->preview.type == kObjectTypeLuaRef) {
- argt |= EX_PREVIEW;
- preview_luaref = api_new_luaref(opts->preview.data.luaref);
- } else if (HAS_KEY(opts->preview)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'");
- goto err;
- }
-
- switch (command.type) {
- case kObjectTypeLuaRef:
- luaref = api_new_luaref(command.data.luaref);
- if (opts->desc.type == kObjectTypeString) {
- rep = opts->desc.data.string.data;
- } else {
- snprintf((char *)IObuff, IOSIZE, "<Lua function %d>", luaref);
- rep = (char *)IObuff;
- }
- break;
- case kObjectTypeString:
- rep = command.data.string.data;
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function");
- goto err;
- }
-
- if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
- preview_luaref, addr_type_arg, luaref, force) != OK) {
- api_set_error(err, kErrorTypeException, "Failed to create user command");
- // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
- }
-
- return;
-
-err:
- NLUA_CLEAR_REF(luaref);
- NLUA_CLEAR_REF(compl_luaref);
- xfree(compl_arg);
-}
-
int find_sid(uint64_t channel_id)
{
switch (channel_id) {
@@ -1659,172 +1136,3 @@ sctx_T api_set_sctx(uint64_t channel_id)
}
return old_current_sctx;
}
-
-// adapted from sign.c:sign_define_init_text.
-// TODO(lewis6991): Consider merging
-int init_sign_text(char **sign_text, char *text)
-{
- char *s;
-
- char *endp = text + (int)STRLEN(text);
-
- // Count cells and check for non-printable chars
- int cells = 0;
- for (s = text; s < endp; s += utfc_ptr2len(s)) {
- if (!vim_isprintc(utf_ptr2char(s))) {
- break;
- }
- cells += utf_ptr2cells(s);
- }
- // Currently must be empty, one or two display cells
- if (s != endp || cells > 2) {
- return FAIL;
- }
- if (cells < 1) {
- return OK;
- }
-
- // Allocate one byte more if we need to pad up
- // with a space.
- size_t len = (size_t)(endp - text + ((cells == 1) ? 1 : 0));
- *sign_text = xstrnsave(text, len);
-
- if (cells == 1) {
- STRCPY(*sign_text + len - 1, " ");
- }
-
- return OK;
-}
-
-/// Check if a string contains only whitespace characters.
-bool string_iswhite(String str)
-{
- for (size_t i = 0; i < str.size; i++) {
- if (!ascii_iswhite(str.data[i])) {
- // Found a non-whitespace character
- return false;
- } else if (str.data[i] == NUL) {
- // Terminate at first occurence of a NUL character
- break;
- }
- }
- return true;
-}
-
-/// Build cmdline string for command, used by `nvim_cmd()`.
-///
-/// @return OK or FAIL.
-void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args,
- size_t argc)
-{
- StringBuilder cmdline = KV_INITIAL_VALUE;
-
- // Add command modifiers
- if (cmdinfo->cmdmod.tab != 0) {
- kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.tab - 1);
- }
- if (cmdinfo->verbose != -1) {
- kv_printf(cmdline, "%ldverbose ", cmdinfo->verbose);
- }
-
- if (cmdinfo->emsg_silent) {
- kv_concat(cmdline, "silent! ");
- } else if (cmdinfo->silent) {
- kv_concat(cmdline, "silent ");
- }
-
- switch (cmdinfo->cmdmod.split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) {
- case WSP_ABOVE:
- kv_concat(cmdline, "aboveleft ");
- break;
- case WSP_BELOW:
- kv_concat(cmdline, "belowright ");
- break;
- case WSP_TOP:
- kv_concat(cmdline, "topleft ");
- break;
- case WSP_BOT:
- kv_concat(cmdline, "botright ");
- break;
- default:
- break;
- }
-
-#define CMDLINE_APPEND_IF(cond, str) \
- do { \
- if (cond) { \
- kv_concat(cmdline, str); \
- } \
- } while (0)
-
- CMDLINE_APPEND_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical ");
- CMDLINE_APPEND_IF(cmdinfo->sandbox, "sandbox ");
- CMDLINE_APPEND_IF(cmdinfo->noautocmd, "noautocmd ");
- CMDLINE_APPEND_IF(cmdinfo->cmdmod.browse, "browse ");
- CMDLINE_APPEND_IF(cmdinfo->cmdmod.confirm, "confirm ");
- CMDLINE_APPEND_IF(cmdinfo->cmdmod.hide, "hide ");
- CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepalt, "keepalt ");
- CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepjumps, "keepjumps ");
- CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepmarks, "keepmarks ");
- CMDLINE_APPEND_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns ");
- CMDLINE_APPEND_IF(cmdinfo->cmdmod.lockmarks, "lockmarks ");
- CMDLINE_APPEND_IF(cmdinfo->cmdmod.noswapfile, "noswapfile ");
-#undef CMDLINE_APPEND_IF
-
- // Command range / count.
- if (eap->argt & EX_RANGE) {
- if (eap->addr_count == 1) {
- kv_printf(cmdline, "%" PRIdLINENR, eap->line2);
- } else if (eap->addr_count > 1) {
- kv_printf(cmdline, "%" PRIdLINENR ",%" PRIdLINENR, eap->line1, eap->line2);
- eap->addr_count = 2; // Make sure address count is not greater than 2
- }
- }
-
- // Keep the index of the position where command name starts, so eap->cmd can point to it.
- size_t cmdname_idx = cmdline.size;
- kv_printf(cmdline, "%s", eap->cmd);
-
- // Command bang.
- if (eap->argt & EX_BANG && eap->forceit) {
- kv_printf(cmdline, "!");
- }
-
- // Command register.
- if (eap->argt & EX_REGSTR && eap->regname) {
- kv_printf(cmdline, " %c", eap->regname);
- }
-
- // Iterate through each argument and store the starting index and length of each argument
- size_t *argidx = xcalloc(argc, sizeof(size_t));
- eap->argc = argc;
- eap->arglens = xcalloc(argc, sizeof(size_t));
- for (size_t i = 0; i < argc; i++) {
- argidx[i] = cmdline.size + 1; // add 1 to account for the space.
- eap->arglens[i] = STRLEN(args[i]);
- kv_printf(cmdline, " %s", args[i]);
- }
-
- // Now that all the arguments are appended, use the command index and argument indices to set the
- // values of eap->cmd, eap->arg and eap->args.
- eap->cmd = cmdline.items + cmdname_idx;
- eap->args = xcalloc(argc, sizeof(char *));
- for (size_t i = 0; i < argc; i++) {
- eap->args[i] = cmdline.items + argidx[i];
- }
- // If there isn't an argument, make eap->arg point to end of cmdline.
- eap->arg = argc > 0 ? eap->args[0] : cmdline.items + cmdline.size;
-
- // Finally, make cmdlinep point to the cmdline string.
- *cmdlinep = cmdline.items;
- xfree(argidx);
-
- // Replace, :make and :grep with 'makeprg' and 'grepprg'.
- char *p = replace_makeprg(eap, eap->arg, cmdlinep);
- if (p != eap->arg) {
- // If replace_makeprg modified the cmdline string, correct the argument pointers.
- assert(argc == 1);
- eap->arg = p;
- eap->args[0] = p;
- }
-}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 5f7162cdd6..77b4900f4f 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -708,220 +708,6 @@ void nvim_set_vvar(String name, Object value, Error *err)
dict_set_var(&vimvardict, name, value, false, false, err);
}
-/// Gets the global value of an option.
-///
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value (global)
-Object nvim_get_option(String name, Error *err)
- FUNC_API_SINCE(1)
-{
- return get_option_from(NULL, SREQ_GLOBAL, name, err);
-}
-
-/// Gets the value of an option. The behavior of this function matches that of
-/// |:set|: the local value of an option is returned if it exists; otherwise,
-/// the global value is returned. Local values always correspond to the current
-/// buffer or window. To get a buffer-local or window-local option for a
-/// specific buffer or window, use |nvim_buf_get_option()| or
-/// |nvim_win_get_option()|.
-///
-/// @param name Option name
-/// @param opts Optional parameters
-/// - scope: One of 'global' or 'local'. Analogous to
-/// |:setglobal| and |:setlocal|, respectively.
-/// @param[out] err Error details, if any
-/// @return Option value
-Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
- FUNC_API_SINCE(9)
-{
- Object rv = OBJECT_INIT;
-
- int scope = 0;
- if (opts->scope.type == kObjectTypeString) {
- if (!strcmp(opts->scope.data.string.data, "local")) {
- scope = OPT_LOCAL;
- } else if (!strcmp(opts->scope.data.string.data, "global")) {
- scope = OPT_GLOBAL;
- } else {
- api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
- goto end;
- }
- } else if (HAS_KEY(opts->scope)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
- goto end;
- }
-
- long numval = 0;
- char *stringval = NULL;
- switch (get_option_value(name.data, &numval, &stringval, scope)) {
- case 0:
- rv = STRING_OBJ(cstr_as_string(stringval));
- break;
- case 1:
- rv = INTEGER_OBJ(numval);
- break;
- case 2:
- switch (numval) {
- case 0:
- case 1:
- rv = BOOLEAN_OBJ(numval);
- break;
- default:
- // Boolean options that return something other than 0 or 1 should return nil. Currently this
- // only applies to 'autoread' which uses -1 as a local value to indicate "unset"
- rv = NIL;
- break;
- }
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data);
- goto end;
- }
-
-end:
- return rv;
-}
-
-/// Sets the value of an option. The behavior of this function matches that of
-/// |:set|: for global-local options, both the global and local value are set
-/// unless otherwise specified with {scope}.
-///
-/// Note the options {win} and {buf} cannot be used together.
-///
-/// @param name Option name
-/// @param value New option value
-/// @param opts Optional parameters
-/// - scope: One of 'global' or 'local'. Analogous to
-/// |:setglobal| and |:setlocal|, respectively.
-/// - win: |window-ID|. Used for setting window local option.
-/// - buf: Buffer number. Used for setting buffer local option.
-/// @param[out] err Error details, if any
-void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err)
- FUNC_API_SINCE(9)
-{
- int scope = 0;
- if (opts->scope.type == kObjectTypeString) {
- if (!strcmp(opts->scope.data.string.data, "local")) {
- scope = OPT_LOCAL;
- } else if (!strcmp(opts->scope.data.string.data, "global")) {
- scope = OPT_GLOBAL;
- } else {
- api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
- return;
- }
- } else if (HAS_KEY(opts->scope)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
- return;
- }
-
- int opt_type = SREQ_GLOBAL;
- void *to = NULL;
-
- if (opts->win.type == kObjectTypeInteger) {
- opt_type = SREQ_WIN;
- to = find_window_by_handle((int)opts->win.data.integer, err);
- } else if (HAS_KEY(opts->win)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: win");
- return;
- }
-
- if (opts->buf.type == kObjectTypeInteger) {
- scope = OPT_LOCAL;
- opt_type = SREQ_BUF;
- to = find_buffer_by_handle((int)opts->buf.data.integer, err);
- } else if (HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: buf");
- return;
- }
-
- if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together");
- return;
- }
-
- if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together");
- return;
- }
-
- long numval = 0;
- char *stringval = NULL;
-
- switch (value.type) {
- case kObjectTypeInteger:
- numval = value.data.integer;
- break;
- case kObjectTypeBoolean:
- numval = value.data.boolean ? 1 : 0;
- break;
- case kObjectTypeString:
- stringval = value.data.string.data;
- break;
- case kObjectTypeNil:
- scope |= OPT_CLEAR;
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "invalid value for option");
- return;
- }
-
- set_option_value_for(name.data, numval, stringval, scope, opt_type, to, err);
-}
-
-/// Gets the option information for all options.
-///
-/// The dictionary has the full option names as keys and option metadata
-/// dictionaries as detailed at |nvim_get_option_info|.
-///
-/// @return dictionary of all options
-Dictionary nvim_get_all_options_info(Error *err)
- FUNC_API_SINCE(7)
-{
- return get_all_vimoptions();
-}
-
-/// Gets the option information for one option
-///
-/// Resulting dictionary has keys:
-/// - name: Name of the option (like 'filetype')
-/// - shortname: Shortened name of the option (like 'ft')
-/// - type: type of option ("string", "number" or "boolean")
-/// - default: The default value for the option
-/// - was_set: Whether the option was set.
-///
-/// - last_set_sid: Last set script id (if any)
-/// - last_set_linenr: line number where option was set
-/// - last_set_chan: Channel where option was set (0 for local)
-///
-/// - scope: one of "global", "win", or "buf"
-/// - global_local: whether win or buf option has a global value
-///
-/// - commalist: List of comma separated values
-/// - flaglist: List of single char flags
-///
-///
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option Information
-Dictionary nvim_get_option_info(String name, Error *err)
- FUNC_API_SINCE(7)
-{
- return get_vimoption(name, err);
-}
-
-/// Sets the global value of an option.
-///
-/// @param channel_id
-/// @param name Option name
-/// @param value New option value
-/// @param[out] err Error details, if any
-void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
-{
- set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err);
-}
-
/// Echo a message.
///
/// @param chunks A list of [text, hl_group] arrays, each representing a
@@ -1679,21 +1465,6 @@ void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err)
nvim_buf_del_keymap(channel_id, -1, mode, lhs, err);
}
-/// Gets a map of global (non-buffer-local) Ex commands.
-///
-/// Currently only |user-commands| are supported, not builtin Ex commands.
-///
-/// @param opts Optional parameters. Currently only supports
-/// {"builtin":false}
-/// @param[out] err Error details, if any.
-///
-/// @returns Map of maps describing commands.
-Dictionary nvim_get_commands(Dict(get_commands) *opts, Error *err)
- FUNC_API_SINCE(4)
-{
- return nvim_buf_get_commands(-1, opts, err);
-}
-
/// Returns a 2-tuple (Array), where item 0 is the current channel id and item
/// 1 is the |api-metadata| map (Dictionary).
///
@@ -2483,58 +2254,3 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
return result;
}
-
-/// Create a new user command |user-commands|
-///
-/// {name} is the name of the new command. The name must begin with an uppercase letter.
-///
-/// {command} is the replacement text or Lua function to execute.
-///
-/// Example:
-/// <pre>
-/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
-/// :SayHello
-/// Hello world!
-/// </pre>
-///
-/// @param name Name of the new user command. Must begin with an uppercase letter.
-/// @param command Replacement command to execute when this user command is executed. When called
-/// from Lua, the command can also be a Lua function. The function is called with a
-/// single table argument that contains the following keys:
-/// - args: (string) The args passed to the command, if any |<args>|
-/// - fargs: (table) The args split by unescaped whitespace (when more than one
-/// argument is allowed), if any |<f-args>|
-/// - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>|
-/// - line1: (number) The starting line of the command range |<line1>|
-/// - line2: (number) The final line of the command range |<line2>|
-/// - range: (number) The number of items in the command range: 0, 1, or 2 |<range>|
-/// - count: (number) Any count supplied |<count>|
-/// - reg: (string) The optional register, if specified |<reg>|
-/// - mods: (string) Command modifiers, if any |<mods>|
-/// - smods: (table) Command modifiers in a structured format. Has the same
-/// structure as the "mods" key of |nvim_parse_cmd()|.
-/// @param opts Optional command attributes. See |command-attributes| for more details. To use
-/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to
-/// "true". In addition to the string options listed in |:command-complete|, the
-/// "complete" key also accepts a Lua function which works like the "customlist"
-/// completion mode |:command-completion-customlist|. Additional parameters:
-/// - desc: (string) Used for listing the command when a Lua function is used for
-/// {command}.
-/// - force: (boolean, default true) Override any previous definition.
-/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
-/// @param[out] err Error details, if any.
-void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
- FUNC_API_SINCE(9)
-{
- create_user_command(name, command, opts, 0, err);
-}
-
-/// Delete a user-defined command.
-///
-/// @param name Name of the command to delete.
-/// @param[out] err Error details, if any.
-void nvim_del_user_command(String name, Error *err)
- FUNC_API_SINCE(9)
-{
- nvim_buf_del_user_command(-1, name, err);
-}
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 6d0b9c7d99..478e146781 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -26,8 +26,6 @@
# include "api/vimscript.c.generated.h"
#endif
-#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
-
/// Executes Vimscript (multiline block of Ex commands), like anonymous
/// |:source|.
///
@@ -747,618 +745,3 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
viml_parser_destroy(&pstate);
return ret;
}
-
-/// Parse command line.
-///
-/// Doesn't check the validity of command arguments.
-///
-/// @param str Command line string to parse. Cannot contain "\n".
-/// @param opts Optional parameters. Reserved for future use.
-/// @param[out] err Error details, if any.
-/// @return Dictionary containing command information, with these keys:
-/// - cmd: (string) Command name.
-/// - range: (array) Command <range>. Can have 0-2 elements depending on how many items the
-/// range contains. Has no elements if command doesn't accept a range or if
-/// no range was specified, one element if only a single range item was
-/// specified and two elements if both range items were specified.
-/// - count: (number) Any |<count>| that was supplied to the command. -1 if command cannot
-/// take a count.
-/// - reg: (number) The optional command |<register>|, if specified. Empty string if not
-/// specified or if command cannot take a register.
-/// - bang: (boolean) Whether command contains a |<bang>| (!) modifier.
-/// - args: (array) Command arguments.
-/// - addr: (string) Value of |:command-addr|. Uses short name.
-/// - nargs: (string) Value of |:command-nargs|.
-/// - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|.
-/// Empty if there isn't a next command.
-/// - magic: (dictionary) Which characters have special meaning in the command arguments.
-/// - file: (boolean) The command expands filenames. Which means characters such as "%",
-/// "#" and wildcards are expanded.
-/// - bar: (boolean) The "|" character is treated as a command separator and the double
-/// quote character (\") is treated as the start of a comment.
-/// - mods: (dictionary) |:command-modifiers|.
-/// - silent: (boolean) |:silent|.
-/// - emsg_silent: (boolean) |:silent!|.
-/// - sandbox: (boolean) |:sandbox|.
-/// - noautocmd: (boolean) |:noautocmd|.
-/// - browse: (boolean) |:browse|.
-/// - confirm: (boolean) |:confirm|.
-/// - hide: (boolean) |:hide|.
-/// - keepalt: (boolean) |:keepalt|.
-/// - keepjumps: (boolean) |:keepjumps|.
-/// - keepmarks: (boolean) |:keepmarks|.
-/// - keeppatterns: (boolean) |:keeppatterns|.
-/// - lockmarks: (boolean) |:lockmarks|.
-/// - noswapfile: (boolean) |:noswapfile|.
-/// - tab: (integer) |:tab|.
-/// - verbose: (integer) |:verbose|. -1 when omitted.
-/// - vertical: (boolean) |:vertical|.
-/// - split: (string) Split modifier string, is an empty string when there's no split
-/// modifier. If there is a split modifier it can be one of:
-/// - "aboveleft": |:aboveleft|.
-/// - "belowright": |:belowright|.
-/// - "topleft": |:topleft|.
-/// - "botright": |:botright|.
-Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
- FUNC_API_SINCE(10) FUNC_API_FAST
-{
- Dictionary result = ARRAY_DICT_INIT;
-
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
- return result;
- }
-
- // Parse command line
- exarg_T ea;
- CmdParseInfo cmdinfo;
- char *cmdline = string_to_cstr(str);
- char *errormsg = NULL;
-
- if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
- if (errormsg != NULL) {
- api_set_error(err, kErrorTypeException, "Error while parsing command line: %s", errormsg);
- } else {
- api_set_error(err, kErrorTypeException, "Error while parsing command line");
- }
- goto end;
- }
-
- // Parse arguments
- Array args = ARRAY_DICT_INIT;
- size_t length = STRLEN(ea.arg);
-
- // For nargs = 1 or '?', pass the entire argument list as a single argument,
- // otherwise split arguments by whitespace.
- if (ea.argt & EX_NOSPC) {
- if (*ea.arg != NUL) {
- ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length)));
- }
- } else {
- size_t end = 0;
- size_t len = 0;
- char *buf = xcalloc(length, sizeof(char));
- bool done = false;
-
- while (!done) {
- done = uc_split_args_iter(ea.arg, length, &end, buf, &len);
- if (len > 0) {
- ADD(args, STRING_OBJ(cstrn_to_string(buf, len)));
- }
- }
-
- xfree(buf);
- }
-
- ucmd_T *cmd = NULL;
- if (ea.cmdidx == CMD_USER) {
- cmd = USER_CMD(ea.useridx);
- } else if (ea.cmdidx == CMD_USER_BUF) {
- cmd = USER_CMD_GA(&curbuf->b_ucmds, ea.useridx);
- }
-
- if (cmd != NULL) {
- PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name));
- } else {
- PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
- }
-
- if ((ea.argt & EX_RANGE) && ea.addr_count > 0) {
- Array range = ARRAY_DICT_INIT;
- if (ea.addr_count > 1) {
- ADD(range, INTEGER_OBJ(ea.line1));
- }
- ADD(range, INTEGER_OBJ(ea.line2));
- PUT(result, "range", ARRAY_OBJ(range));
- } else {
- PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT));
- }
-
- if (ea.argt & EX_COUNT) {
- if (ea.addr_count > 0) {
- PUT(result, "count", INTEGER_OBJ(ea.line2));
- } else if (cmd != NULL) {
- PUT(result, "count", INTEGER_OBJ(cmd->uc_def));
- } else {
- PUT(result, "count", INTEGER_OBJ(0));
- }
- } else {
- PUT(result, "count", INTEGER_OBJ(-1));
- }
-
- char reg[2];
- reg[0] = (char)ea.regname;
- reg[1] = '\0';
- PUT(result, "reg", CSTR_TO_OBJ(reg));
-
- PUT(result, "bang", BOOLEAN_OBJ(ea.forceit));
- PUT(result, "args", ARRAY_OBJ(args));
-
- char nargs[2];
- if (ea.argt & EX_EXTRA) {
- if (ea.argt & EX_NOSPC) {
- if (ea.argt & EX_NEEDARG) {
- nargs[0] = '1';
- } else {
- nargs[0] = '?';
- }
- } else if (ea.argt & EX_NEEDARG) {
- nargs[0] = '+';
- } else {
- nargs[0] = '*';
- }
- } else {
- nargs[0] = '0';
- }
- nargs[1] = '\0';
- PUT(result, "nargs", CSTR_TO_OBJ(nargs));
-
- const char *addr;
- switch (ea.addr_type) {
- case ADDR_LINES:
- addr = "line";
- break;
- case ADDR_ARGUMENTS:
- addr = "arg";
- break;
- case ADDR_BUFFERS:
- addr = "buf";
- break;
- case ADDR_LOADED_BUFFERS:
- addr = "load";
- break;
- case ADDR_WINDOWS:
- addr = "win";
- break;
- case ADDR_TABS:
- addr = "tab";
- break;
- case ADDR_QUICKFIX:
- addr = "qf";
- break;
- case ADDR_NONE:
- addr = "none";
- break;
- default:
- addr = "?";
- break;
- }
- PUT(result, "addr", CSTR_TO_OBJ(addr));
- PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd));
-
- Dictionary mods = ARRAY_DICT_INIT;
- PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.silent));
- PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.emsg_silent));
- PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.sandbox));
- PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.noautocmd));
- PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.tab));
- PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose));
- PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.browse));
- PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.confirm));
- PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.hide));
- PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.keepalt));
- PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.keepjumps));
- PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.keepmarks));
- PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.keeppatterns));
- PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.lockmarks));
- PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.noswapfile));
- PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.split & WSP_VERT));
-
- const char *split;
- if (cmdinfo.cmdmod.split & WSP_BOT) {
- split = "botright";
- } else if (cmdinfo.cmdmod.split & WSP_TOP) {
- split = "topleft";
- } else if (cmdinfo.cmdmod.split & WSP_BELOW) {
- split = "belowright";
- } else if (cmdinfo.cmdmod.split & WSP_ABOVE) {
- split = "aboveleft";
- } else {
- split = "";
- }
- PUT(mods, "split", CSTR_TO_OBJ(split));
-
- PUT(result, "mods", DICTIONARY_OBJ(mods));
-
- Dictionary magic = ARRAY_DICT_INIT;
- PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file));
- PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar));
- PUT(result, "magic", DICTIONARY_OBJ(magic));
-end:
- xfree(cmdline);
- return result;
-}
-
-/// Executes an Ex command.
-///
-/// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This
-/// allows for easier construction and manipulation of an Ex command. This also allows for things
-/// such as having spaces inside a command argument, expanding filenames in a command that otherwise
-/// doesn't expand filenames, etc.
-///
-/// On execution error: fails with VimL error, updates v:errmsg.
-///
-/// @see |nvim_exec()|
-/// @see |nvim_command()|
-///
-/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as
-/// the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd"
-/// which are ignored if provided. All values except for "cmd" are optional.
-/// @param opts Optional parameters.
-/// - output: (boolean, default false) Whether to return command output.
-/// @param[out] err Error details, if any.
-/// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string.
-String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error *err)
- FUNC_API_SINCE(10)
-{
- exarg_T ea;
- memset(&ea, 0, sizeof(ea));
- ea.verbose_save = -1;
- ea.save_msg_silent = -1;
-
- CmdParseInfo cmdinfo;
- memset(&cmdinfo, 0, sizeof(cmdinfo));
- cmdinfo.verbose = -1;
-
- char *cmdline = NULL;
- char *cmdname = NULL;
- char **args = NULL;
- size_t argc = 0;
-
- String retv = (String)STRING_INIT;
-
-#define OBJ_TO_BOOL(var, value, default, varname) \
- do { \
- var = api_object_to_bool(value, varname, default, err); \
- if (ERROR_SET(err)) { \
- goto end; \
- } \
- } while (0)
-
-#define VALIDATION_ERROR(...) \
- do { \
- api_set_error(err, kErrorTypeValidation, __VA_ARGS__); \
- goto end; \
- } while (0)
-
- bool output;
- OBJ_TO_BOOL(output, opts->output, false, "'output'");
-
- // First, parse the command name and check if it exists and is valid.
- if (!HAS_KEY(cmd->cmd) || cmd->cmd.type != kObjectTypeString
- || cmd->cmd.data.string.data[0] == NUL) {
- VALIDATION_ERROR("'cmd' must be a non-empty String");
- }
-
- cmdname = string_to_cstr(cmd->cmd.data.string);
- ea.cmd = cmdname;
-
- char *p = find_ex_command(&ea, NULL);
-
- // If this looks like an undefined user command and there are CmdUndefined
- // autocommands defined, trigger the matching autocommands.
- if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd)
- && has_event(EVENT_CMDUNDEFINED)) {
- p = xstrdup(cmdname);
- int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL);
- xfree(p);
- // If the autocommands did something and didn't cause an error, try
- // finding the command again.
- p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd;
- }
-
- if (p == NULL || ea.cmdidx == CMD_SIZE) {
- VALIDATION_ERROR("Command not found: %s", cmdname);
- }
- if (is_cmd_ni(ea.cmdidx)) {
- VALIDATION_ERROR("Command not implemented: %s", cmdname);
- }
-
- // Get the command flags so that we can know what type of arguments the command uses.
- // Not required for a user command since `find_ex_command` already deals with it in that case.
- if (!IS_USER_CMDIDX(ea.cmdidx)) {
- ea.argt = get_cmd_argt(ea.cmdidx);
- }
-
- // Parse command arguments since it's needed to get the command address type.
- if (HAS_KEY(cmd->args)) {
- if (cmd->args.type != kObjectTypeArray) {
- VALIDATION_ERROR("'args' must be an Array");
- }
- // Check if every argument is valid
- for (size_t i = 0; i < cmd->args.data.array.size; i++) {
- Object elem = cmd->args.data.array.items[i];
- if (elem.type != kObjectTypeString) {
- VALIDATION_ERROR("Command argument must be a String");
- } else if (string_iswhite(elem.data.string)) {
- VALIDATION_ERROR("Command argument must have non-whitespace characters");
- }
- }
-
- argc = cmd->args.data.array.size;
- bool argc_valid;
-
- // Check if correct number of arguments is used.
- switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
- case EX_EXTRA | EX_NOSPC | EX_NEEDARG:
- argc_valid = argc == 1;
- break;
- case EX_EXTRA | EX_NOSPC:
- argc_valid = argc <= 1;
- break;
- case EX_EXTRA | EX_NEEDARG:
- argc_valid = argc >= 1;
- break;
- case EX_EXTRA:
- argc_valid = true;
- break;
- default:
- argc_valid = argc == 0;
- break;
- }
-
- if (!argc_valid) {
- argc = 0; // Ensure that args array isn't erroneously freed at the end.
- VALIDATION_ERROR("Incorrect number of arguments supplied");
- }
-
- if (argc != 0) {
- args = xcalloc(argc, sizeof(char *));
-
- for (size_t i = 0; i < argc; i++) {
- args[i] = string_to_cstr(cmd->args.data.array.items[i].data.string);
- }
- }
- }
-
- // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()`
- // since it only ever checks the first argument.
- set_cmd_addr_type(&ea, argc > 0 ? (char_u *)args[0] : NULL);
-
- if (HAS_KEY(cmd->range)) {
- if (!(ea.argt & EX_RANGE)) {
- VALIDATION_ERROR("Command cannot accept a range");
- } else if (cmd->range.type != kObjectTypeArray) {
- VALIDATION_ERROR("'range' must be an Array");
- } else if (cmd->range.data.array.size > 2) {
- VALIDATION_ERROR("'range' cannot contain more than two elements");
- }
-
- Array range = cmd->range.data.array;
- ea.addr_count = (int)range.size;
-
- for (size_t i = 0; i < range.size; i++) {
- Object elem = range.items[i];
- if (elem.type != kObjectTypeInteger || elem.data.integer < 0) {
- VALIDATION_ERROR("'range' element must be a non-negative Integer");
- }
- }
-
- if (range.size > 0) {
- ea.line1 = (linenr_T)range.items[0].data.integer;
- ea.line2 = (linenr_T)range.items[range.size - 1].data.integer;
- }
-
- if (invalid_range(&ea) != NULL) {
- VALIDATION_ERROR("Invalid range provided");
- }
- }
- if (ea.addr_count == 0) {
- if (ea.argt & EX_DFLALL) {
- set_cmd_dflall_range(&ea); // Default range for range=%
- } else {
- ea.line1 = ea.line2 = get_cmd_default_range(&ea); // Default range.
-
- if (ea.addr_type == ADDR_OTHER) {
- // Default is 1, not cursor.
- ea.line2 = 1;
- }
- }
- }
-
- if (HAS_KEY(cmd->count)) {
- if (!(ea.argt & EX_COUNT)) {
- VALIDATION_ERROR("Command cannot accept a count");
- } else if (cmd->count.type != kObjectTypeInteger || cmd->count.data.integer < 0) {
- VALIDATION_ERROR("'count' must be a non-negative Integer");
- }
- set_cmd_count(&ea, cmd->count.data.integer, true);
- }
-
- if (HAS_KEY(cmd->reg)) {
- if (!(ea.argt & EX_REGSTR)) {
- VALIDATION_ERROR("Command cannot accept a register");
- } else if (cmd->reg.type != kObjectTypeString || cmd->reg.data.string.size != 1) {
- VALIDATION_ERROR("'reg' must be a single character");
- }
- char regname = cmd->reg.data.string.data[0];
- if (regname == '=') {
- VALIDATION_ERROR("Cannot use register \"=");
- } else if (!valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx))) {
- VALIDATION_ERROR("Invalid register: \"%c", regname);
- }
- ea.regname = (uint8_t)regname;
- }
-
- OBJ_TO_BOOL(ea.forceit, cmd->bang, false, "'bang'");
- if (ea.forceit && !(ea.argt & EX_BANG)) {
- VALIDATION_ERROR("Command cannot accept a bang");
- }
-
- if (HAS_KEY(cmd->magic)) {
- if (cmd->magic.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'magic' must be a Dictionary");
- }
-
- Dict(cmd_magic) magic = { 0 };
- if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field,
- cmd->magic.data.dictionary, err)) {
- goto end;
- }
-
- OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'");
- OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'");
- } else {
- cmdinfo.magic.file = ea.argt & EX_XFILE;
- cmdinfo.magic.bar = ea.argt & EX_TRLBAR;
- }
-
- if (HAS_KEY(cmd->mods)) {
- if (cmd->mods.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'mods' must be a Dictionary");
- }
-
- Dict(cmd_mods) mods = { 0 };
- if (!api_dict_to_keydict(&mods, KeyDict_cmd_mods_get_field, cmd->mods.data.dictionary, err)) {
- goto end;
- }
-
- if (HAS_KEY(mods.tab)) {
- if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) {
- VALIDATION_ERROR("'mods.tab' must be a non-negative Integer");
- }
- cmdinfo.cmdmod.tab = (int)mods.tab.data.integer + 1;
- }
-
- if (HAS_KEY(mods.verbose)) {
- if (mods.verbose.type != kObjectTypeInteger) {
- VALIDATION_ERROR("'mods.verbose' must be a Integer");
- } else if (mods.verbose.data.integer >= 0) {
- // Silently ignore negative integers to allow mods.verbose to be set to -1.
- cmdinfo.verbose = mods.verbose.data.integer;
- }
- }
-
- bool vertical;
- OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'");
- cmdinfo.cmdmod.split |= (vertical ? WSP_VERT : 0);
-
- if (HAS_KEY(mods.split)) {
- if (mods.split.type != kObjectTypeString) {
- VALIDATION_ERROR("'mods.split' must be a String");
- }
-
- if (*mods.split.data.string.data == NUL) {
- // Empty string, do nothing.
- } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0
- || STRCMP(mods.split.data.string.data, "leftabove") == 0) {
- cmdinfo.cmdmod.split |= WSP_ABOVE;
- } else if (STRCMP(mods.split.data.string.data, "belowright") == 0
- || STRCMP(mods.split.data.string.data, "rightbelow") == 0) {
- cmdinfo.cmdmod.split |= WSP_BELOW;
- } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) {
- cmdinfo.cmdmod.split |= WSP_TOP;
- } else if (STRCMP(mods.split.data.string.data, "botright") == 0) {
- cmdinfo.cmdmod.split |= WSP_BOT;
- } else {
- VALIDATION_ERROR("Invalid value for 'mods.split'");
- }
- }
-
- OBJ_TO_BOOL(cmdinfo.silent, mods.silent, false, "'mods.silent'");
- OBJ_TO_BOOL(cmdinfo.emsg_silent, mods.emsg_silent, false, "'mods.emsg_silent'");
- OBJ_TO_BOOL(cmdinfo.sandbox, mods.sandbox, false, "'mods.sandbox'");
- OBJ_TO_BOOL(cmdinfo.noautocmd, mods.noautocmd, false, "'mods.noautocmd'");
- OBJ_TO_BOOL(cmdinfo.cmdmod.browse, mods.browse, false, "'mods.browse'");
- OBJ_TO_BOOL(cmdinfo.cmdmod.confirm, mods.confirm, false, "'mods.confirm'");
- OBJ_TO_BOOL(cmdinfo.cmdmod.hide, mods.hide, false, "'mods.hide'");
- OBJ_TO_BOOL(cmdinfo.cmdmod.keepalt, mods.keepalt, false, "'mods.keepalt'");
- OBJ_TO_BOOL(cmdinfo.cmdmod.keepjumps, mods.keepjumps, false, "'mods.keepjumps'");
- OBJ_TO_BOOL(cmdinfo.cmdmod.keepmarks, mods.keepmarks, false, "'mods.keepmarks'");
- OBJ_TO_BOOL(cmdinfo.cmdmod.keeppatterns, mods.keeppatterns, false, "'mods.keeppatterns'");
- OBJ_TO_BOOL(cmdinfo.cmdmod.lockmarks, mods.lockmarks, false, "'mods.lockmarks'");
- OBJ_TO_BOOL(cmdinfo.cmdmod.noswapfile, mods.noswapfile, false, "'mods.noswapfile'");
-
- if (cmdinfo.sandbox && !(ea.argt & EX_SBOXOK)) {
- VALIDATION_ERROR("Command cannot be run in sandbox");
- }
- }
-
- // Finally, build the command line string that will be stored inside ea.cmdlinep.
- // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens.
- build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc);
- ea.cmdlinep = &cmdline;
-
- garray_T capture_local;
- const int save_msg_silent = msg_silent;
- garray_T * const save_capture_ga = capture_ga;
-
- if (output) {
- ga_init(&capture_local, 1, 80);
- capture_ga = &capture_local;
- }
-
- TRY_WRAP({
- try_start();
- if (output) {
- msg_silent++;
- }
-
- WITH_SCRIPT_CONTEXT(channel_id, {
- execute_cmd(&ea, &cmdinfo, false);
- });
-
- if (output) {
- capture_ga = save_capture_ga;
- msg_silent = save_msg_silent;
- }
-
- try_end(err);
- });
-
- if (ERROR_SET(err)) {
- goto clear_ga;
- }
-
- if (output && capture_local.ga_len > 1) {
- retv = (String){
- .data = capture_local.ga_data,
- .size = (size_t)capture_local.ga_len,
- };
- // redir usually (except :echon) prepends a newline.
- if (retv.data[0] == '\n') {
- memmove(retv.data, retv.data + 1, retv.size - 1);
- retv.data[retv.size - 1] = '\0';
- retv.size = retv.size - 1;
- }
- goto end;
- }
-clear_ga:
- if (output) {
- ga_clear(&capture_local);
- }
-end:
- xfree(cmdline);
- xfree(cmdname);
- xfree(ea.args);
- xfree(ea.arglens);
- for (size_t i = 0; i < argc; i++) {
- xfree(args[i]);
- }
- xfree(args);
-
- return retv;
-
-#undef OBJ_TO_BOOL
-#undef VALIDATION_ERROR
-}
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 9f2afc67a6..5a4ff70257 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -264,44 +264,6 @@ void nvim_win_del_var(Window window, String name, Error *err)
dict_set_var(win->w_vars, name, NIL, true, false, err);
}
-/// Gets a window option value
-///
-/// @param window Window handle, or 0 for current window
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value
-Object nvim_win_get_option(Window window, String name, Error *err)
- FUNC_API_SINCE(1)
-{
- win_T *win = find_window_by_handle(window, err);
-
- if (!win) {
- return (Object)OBJECT_INIT;
- }
-
- return get_option_from(win, SREQ_WIN, name, err);
-}
-
-/// Sets a window option value. Passing 'nil' as value deletes the option(only
-/// works if there's a global fallback)
-///
-/// @param channel_id
-/// @param window Window handle, or 0 for current window
-/// @param name Option name
-/// @param value Option value
-/// @param[out] err Error details, if any
-void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
-{
- win_T *win = find_window_by_handle(window, err);
-
- if (!win) {
- return;
- }
-
- set_option_to(channel_id, win, SREQ_WIN, name, value, err);
-}
-
/// Gets the window position in display cells. First position is zero.
///
/// @param window Window handle, or 0 for current window
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 4c3f1308b3..921cd20943 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -575,6 +575,10 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i
return false;
}
+ // Disable buffer-updates for the current buffer.
+ // No need to check `unload_buf`: in that case the function returned above.
+ buf_updates_unload(buf, false);
+
if (win != NULL // Avoid bogus clang warning.
&& win_valid_any_tab(win)
&& win->w_buffer == buf) {
@@ -587,10 +591,6 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i
buf->b_nwindows--;
}
- // Disable buffer-updates for the current buffer.
- // No need to check `unload_buf`: in that case the function returned above.
- buf_updates_unload(buf, false);
-
// Remove the buffer from the list.
if (wipe_buf) {
// Do not wipe out the buffer if it is used in a window.
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 0d69578c26..8c6a32ee01 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -90,9 +90,6 @@ static bool ex_pressedreturn = false;
garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL };
-// Whether a command index indicates a user command.
-#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
-
// Struct for storing a line inside a while/for loop
typedef struct {
char *line; // command line
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index dbe095ab13..7e0d3016bc 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -17,6 +17,9 @@
#define VALID_PATH 1
#define VALID_HEAD 2
+// Whether a command index indicates a user command.
+#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
+
// Structure used to save the current state. Used when executing Normal mode
// commands while in any other mode.
typedef struct {
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 5bca6b5f81..94ce1ec495 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -2325,19 +2325,9 @@ static buf_T *cmdpreview_open_buf(void)
static win_T *cmdpreview_open_win(buf_T *cmdpreview_buf)
{
win_T *save_curwin = curwin;
- bool win_found = false;
- // Try to find an existing preview window.
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == cmdpreview_buf) {
- win_enter(wp, false);
- win_found = true;
- break;
- }
- }
-
- // If an existing window is not found, create one.
- if (!win_found && win_split((int)p_cwh, WSP_BOT) == FAIL) {
+ // Open preview window.
+ if (win_split((int)p_cwh, WSP_BOT) == FAIL) {
return NULL;
}
@@ -2459,7 +2449,8 @@ static void cmdpreview_show(CommandLineState *s)
// If inccommand=split and preview callback returns 2, open preview window.
if (icm_split && cmdpreview_type == 2
&& (cmdpreview_win = cmdpreview_open_win(cmdpreview_buf)) == NULL) {
- abort();
+ // If there's not enough room to open the preview window, just preview without the window.
+ cmdpreview_type = 1;
}
// If preview callback is nonzero, update screen now.
diff --git a/src/nvim/window.c b/src/nvim/window.c
index f429c493a7..e09a7cd97e 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -7297,7 +7297,7 @@ void win_findbuf(typval_T *argvars, list_T *list)
int bufnr = tv_get_number(&argvars[0]);
FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (!wp->w_closing && wp->w_buffer->b_fnum == bufnr) {
+ if (wp->w_buffer->b_fnum == bufnr) {
tv_list_append_number(list, wp->handle);
}
}
diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua
index 2009d5f4ba..10de45274c 100644
--- a/test/functional/lua/buffer_updates_spec.lua
+++ b/test/functional/lua/buffer_updates_spec.lua
@@ -252,9 +252,8 @@ describe('lua buffer event callbacks: on_lines', function()
eq(2, meths.win_get_cursor(0)[1])
end)
- it('does not SEGFAULT when calling win_findbuf in on_detach', function()
-
- exec_lua[[
+ it('does not SEGFAULT when accessing window buffer info in on_detach #14998', function()
+ local code = [[
local buf = vim.api.nvim_create_buf(false, false)
vim.cmd"split"
@@ -262,13 +261,19 @@ describe('lua buffer event callbacks: on_lines', function()
vim.api.nvim_buf_attach(buf, false, {
on_detach = function(_, buf)
+ vim.fn.tabpagebuflist()
vim.fn.win_findbuf(buf)
end
})
]]
+ exec_lua(code)
command("q!")
helpers.assert_alive()
+
+ exec_lua(code)
+ command("bd!")
+ helpers.assert_alive()
end)
it('#12718 lnume', function()
diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua
index b01504db4f..a310069636 100644
--- a/test/functional/ui/inccommand_spec.lua
+++ b/test/functional/ui/inccommand_spec.lua
@@ -2051,6 +2051,16 @@ describe("'inccommand' split windows", function()
end
end)
+ it("don't open if there's not enough room", function()
+ refresh()
+ screen:try_resize(40, 3)
+ feed("gg:%s/tw")
+ screen:expect([[
+ Inc substitution on |
+ {12:tw}o lines |
+ :%s/tw^ |
+ ]])
+ end)
end)
describe("'inccommand' with 'gdefault'", function()
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index 84b360b9d9..9f1b3f0706 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -119,23 +119,11 @@ if(CMAKE_OSX_SYSROOT)
endif()
endif()
-# Cross compiling: use these for dependencies built for the
-# HOST system, when not crosscompiling these should be the
-# same as DEPS_*. Except when targeting Unix in which case
-# want all the dependencies to use the same compiler.
-if(CMAKE_CROSSCOMPILING AND NOT UNIX)
- set(HOSTDEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/host")
- set(HOSTDEPS_BIN_DIR "${HOSTDEPS_INSTALL_DIR}/bin")
- set(HOSTDEPS_LIB_DIR "${HOSTDEPS_INSTALL_DIR}/lib")
- set(HOSTDEPS_C_COMPILER "${HOST_C_COMPILER}")
- set(HOSTDEPS_CXX_COMPILER "${HOST_CXX_COMPILER}")
-else()
- set(HOSTDEPS_INSTALL_DIR "${DEPS_INSTALL_DIR}")
- set(HOSTDEPS_BIN_DIR "${DEPS_BIN_DIR}")
- set(HOSTDEPS_LIB_DIR "${DEPS_LIB_DIR}")
- set(HOSTDEPS_C_COMPILER "${DEPS_C_COMPILER}")
- set(HOSTDEPS_CXX_COMPILER "${DEPS_CXX_COMPILER}")
-endif()
+set(HOSTDEPS_INSTALL_DIR "${DEPS_INSTALL_DIR}")
+set(HOSTDEPS_BIN_DIR "${DEPS_BIN_DIR}")
+set(HOSTDEPS_LIB_DIR "${DEPS_LIB_DIR}")
+set(HOSTDEPS_C_COMPILER "${DEPS_C_COMPILER}")
+set(HOSTDEPS_CXX_COMPILER "${DEPS_CXX_COMPILER}")
include(ExternalProject)
@@ -222,7 +210,7 @@ if(USE_BUNDLED_LUAJIT)
include(BuildLuajit)
endif()
-if(USE_BUNDLED_LUA AND NOT CMAKE_CROSSCOMPILING)
+if(USE_BUNDLED_LUA)
include(BuildLua)
endif()
diff --git a/third-party/cmake/BuildLibuv.cmake b/third-party/cmake/BuildLibuv.cmake
index 42650308a8..ba5de38714 100644
--- a/third-party/cmake/BuildLibuv.cmake
+++ b/third-party/cmake/BuildLibuv.cmake
@@ -45,17 +45,6 @@ if(UNIX)
CONFIGURE_COMMAND ${UNIX_CFGCMD} MAKE=${MAKE_PRG}
INSTALL_COMMAND ${MAKE_PRG} V=1 install)
-elseif(MINGW AND CMAKE_CROSSCOMPILING)
- # Build libuv for the host
- BuildLibuv(TARGET libuv_host
- CONFIGURE_COMMAND sh ${DEPS_BUILD_DIR}/src/libuv_host/autogen.sh && ${DEPS_BUILD_DIR}/src/libuv_host/configure --with-pic --disable-shared --prefix=${HOSTDEPS_INSTALL_DIR} CC=${HOST_C_COMPILER}
- INSTALL_COMMAND ${MAKE_PRG} V=1 install)
-
- # Build libuv for the target
- BuildLibuv(
- CONFIGURE_COMMAND ${UNIX_CFGCMD} --host=${CROSS_TARGET}
- INSTALL_COMMAND ${MAKE_PRG} V=1 install)
-
elseif(WIN32)
set(UV_OUTPUT_DIR ${DEPS_BUILD_DIR}/src/libuv/${CMAKE_BUILD_TYPE})
diff --git a/third-party/cmake/BuildLuajit.cmake b/third-party/cmake/BuildLuajit.cmake
index e02d7fe609..fc8558588b 100644
--- a/third-party/cmake/BuildLuajit.cmake
+++ b/third-party/cmake/BuildLuajit.cmake
@@ -76,29 +76,6 @@ if(UNIX)
CC=${DEPS_C_COMPILER} PREFIX=${DEPS_INSTALL_DIR}
${DEPLOYMENT_TARGET})
-elseif(MINGW AND CMAKE_CROSSCOMPILING)
-
- # Build luajit for the host
- BuildLuaJit(TARGET luajit_host
- CONFIGURE_COMMAND ""
- BUILD_COMMAND ""
- INSTALL_COMMAND ${INSTALLCMD_UNIX}
- CC=${HOST_C_COMPILER} PREFIX=${HOSTDEPS_INSTALL_DIR})
-
- # Build luajit for the target
- BuildLuaJit(
- # Similar to Unix + cross - fPIC
- INSTALL_COMMAND
- ${MAKE_PRG} PREFIX=${DEPS_INSTALL_DIR}
- BUILDMODE=static install
- TARGET_SYS=${CMAKE_SYSTEM_NAME}
- CROSS=${CROSS_TARGET}-
- HOST_CC=${HOST_C_COMPILER} HOST_CFLAGS=${HOST_C_FLAGS}
- HOST_LDFLAGS=${HOST_EXE_LINKER_FLAGS}
- FILE_T=luajit.exe
- Q=
- INSTALL_TSYMNAME=luajit.exe)
-
elseif(MINGW)
if(CMAKE_GENERATOR MATCHES "Ninja")
diff --git a/third-party/cmake/BuildLuarocks.cmake b/third-party/cmake/BuildLuarocks.cmake
index 244d1d9fb8..6d73a02b5a 100644
--- a/third-party/cmake/BuildLuarocks.cmake
+++ b/third-party/cmake/BuildLuarocks.cmake
@@ -56,7 +56,7 @@ endif()
# Defaults to 5.1 for bundled LuaJIT/Lua.
set(LUA_VERSION "5.1")
-if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING))
+if(UNIX)
if(USE_BUNDLED_LUAJIT)
list(APPEND LUAROCKS_OPTS
@@ -123,9 +123,6 @@ list(APPEND THIRD_PARTY_DEPS luarocks)
if(USE_BUNDLED_LUAJIT)
add_dependencies(luarocks luajit)
- if(MINGW AND CMAKE_CROSSCOMPILING)
- add_dependencies(luarocks luajit_host)
- endif()
elseif(USE_BUNDLED_LUA)
add_dependencies(luarocks lua)
endif()
@@ -196,9 +193,6 @@ if(USE_BUNDLED_BUSTED)
set(LUV_DEPS luacheck)
if(USE_BUNDLED_LUV)
list(APPEND LUV_DEPS luv-static lua-compat-5.3)
- if(MINGW AND CMAKE_CROSSCOMPILING)
- list(APPEND LUV_DEPS libuv_host)
- endif()
set(LUV_ARGS "CFLAGS=-O0 -g3 -fPIC")
if(USE_BUNDLED_LIBUV)
list(APPEND LUV_ARGS LIBUV_DIR=${HOSTDEPS_INSTALL_DIR})
diff --git a/third-party/cmake/BuildLuv.cmake b/third-party/cmake/BuildLuv.cmake
index 99822249c2..001f5a325a 100644
--- a/third-party/cmake/BuildLuv.cmake
+++ b/third-party/cmake/BuildLuv.cmake
@@ -91,16 +91,7 @@ if(USE_BUNDLED_LIBUV)
-DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR})
endif()
-if(MINGW AND CMAKE_CROSSCOMPILING)
- get_filename_component(TOOLCHAIN ${CMAKE_TOOLCHAIN_FILE} REALPATH)
- set(LUV_CONFIGURE_COMMAND
- ${LUV_CONFIGURE_COMMAND_COMMON}
- # Pass toolchain
- -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN}
- "-DCMAKE_C_FLAGS:STRING=${LUV_INCLUDE_FLAGS} -D_WIN32_WINNT=0x0600"
- # Hack to avoid -rdynamic in Mingw
- -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="")
-elseif(MSVC)
+if(MSVC)
set(LUV_CONFIGURE_COMMAND
${LUV_CONFIGURE_COMMAND_COMMON}
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
diff --git a/third-party/cmake/BuildMsgpack.cmake b/third-party/cmake/BuildMsgpack.cmake
index f66a3bdd32..a89c1e34d0 100644
--- a/third-party/cmake/BuildMsgpack.cmake
+++ b/third-party/cmake/BuildMsgpack.cmake
@@ -42,18 +42,7 @@ set(MSGPACK_CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/msgpack
set(MSGPACK_BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE})
set(MSGPACK_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE})
-if(MINGW AND CMAKE_CROSSCOMPILING)
- get_filename_component(TOOLCHAIN ${CMAKE_TOOLCHAIN_FILE} REALPATH)
- set(MSGPACK_CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/msgpack
- -DMSGPACK_BUILD_TESTS=OFF
- -DMSGPACK_BUILD_EXAMPLES=OFF
- -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
- # Pass toolchain
- -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN}
- -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
- # Hack to avoid -rdynamic in Mingw
- -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="")
-elseif(MSVC)
+if(MSVC)
# Same as Unix without fPIC
set(MSGPACK_CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/msgpack
-DMSGPACK_BUILD_TESTS=OFF