diff options
author | bfredl <bjorn.linse@gmail.com> | 2022-06-12 00:37:39 +0200 |
---|---|---|
committer | bfredl <bjorn.linse@gmail.com> | 2022-06-12 16:09:42 +0200 |
commit | 4a275e3291d3eade38490801d9842640df3de202 (patch) | |
tree | 8aa5758bc82899645bdc799068b8eb3233a4cc69 /src/nvim/api/options.c | |
parent | 502f03fc064d1eb427d214521d5cb9f5425a15b4 (diff) | |
download | rneovim-4a275e3291d3eade38490801d9842640df3de202.tar.gz rneovim-4a275e3291d3eade38490801d9842640df3de202.tar.bz2 rneovim-4a275e3291d3eade38490801d9842640df3de202.zip |
refactor(api): move option code to own file
Diffstat (limited to 'src/nvim/api/options.c')
-rw-r--r-- | src/nvim/api/options.c | 509 |
1 files changed, 509 insertions, 0 deletions
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); + } +} |