diff options
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 |