diff options
Diffstat (limited to 'src/nvim/api/options.c')
-rw-r--r-- | src/nvim/api/options.c | 554 |
1 files changed, 554 insertions, 0 deletions
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c new file mode 100644 index 0000000000..4ed676e613 --- /dev/null +++ b/src/nvim/api/options.c @@ -0,0 +1,554 @@ +// 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 + +static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_type, void **from, + Error *err) +{ + 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 FAIL; + } + } else if (HAS_KEY(opts->scope)) { + api_set_error(err, kErrorTypeValidation, "invalid value for key: scope"); + return FAIL; + } + + *opt_type = SREQ_GLOBAL; + + if (opts->win.type == kObjectTypeInteger) { + *opt_type = SREQ_WIN; + *from = find_window_by_handle((int)opts->win.data.integer, err); + if (ERROR_SET(err)) { + return FAIL; + } + } else if (HAS_KEY(opts->win)) { + api_set_error(err, kErrorTypeValidation, "invalid value for key: win"); + return FAIL; + } + + if (opts->buf.type == kObjectTypeInteger) { + *scope = OPT_LOCAL; + *opt_type = SREQ_BUF; + *from = find_buffer_by_handle((int)opts->buf.data.integer, err); + if (ERROR_SET(err)) { + return FAIL; + } + } else if (HAS_KEY(opts->buf)) { + api_set_error(err, kErrorTypeValidation, "invalid value for key: buf"); + return FAIL; + } + + if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) { + api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together"); + return FAIL; + } + + if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) { + api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together"); + return FAIL; + } + + return OK; +} + +/// 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, unless "buf" or "win" is set in {opts}. +/// +/// @param name Option name +/// @param opts Optional parameters +/// - scope: One of "global" or "local". Analogous to +/// |:setglobal| and |:setlocal|, respectively. +/// - win: |window-ID|. Used for getting window local options. +/// - buf: Buffer number. Used for getting buffer local options. +/// Implies {scope} is "local". +/// @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; + int opt_type = SREQ_GLOBAL; + void *from = NULL; + if (!validate_option_value_args(opts, &scope, &opt_type, &from, err)) { + return rv; + } + + long numval = 0; + char *stringval = NULL; + int result = access_option_value_for(name.data, &numval, &stringval, scope, opt_type, from, + true, err); + if (ERROR_SET(err)) { + return rv; + } + + switch (result) { + 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); + return rv; + } + + 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; + int opt_type = SREQ_GLOBAL; + void *to = NULL; + if (!validate_option_value_args(opts, &scope, &opt_type, &to, err)) { + return; + } + + // If: + // - window id is provided + // - scope is not provided + // - option is global or local to window (global-local) + // + // Then force scope to local since we don't want to change the global option + if (opt_type == SREQ_WIN && scope == 0) { + int flags = get_option_value_strict(name.data, NULL, NULL, opt_type, to); + if (flags & SOPT_GLOBAL) { + scope = OPT_LOCAL; + } + } + + 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; + } + + access_option_value_for(name.data, &numval, &stringval, scope, opt_type, to, false, 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; + } + } + + long 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; + } + + // For global-win-local options -> setlocal + // For win-local options -> setglobal and setlocal (opt_flags == 0) + const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) ? 0 : + (type == SREQ_GLOBAL) ? OPT_GLOBAL : OPT_LOCAL; + + WITH_SCRIPT_CONTEXT(channel_id, { + access_option_value_for(name.data, &numval, &stringval, opt_flags, type, to, false, err); + }); +} + +static int access_option_value(char *key, long *numval, char **stringval, int opt_flags, bool get, + Error *err) +{ + if (get) { + return get_option_value(key, numval, stringval, opt_flags); + } else { + char *errmsg; + if ((errmsg = set_option_value(key, *numval, *stringval, opt_flags))) { + if (try_end(err)) { + return 0; + } + + api_set_error(err, kErrorTypeException, "%s", errmsg); + } + return 0; + } +} + +static int access_option_value_for(char *key, long *numval, char **stringval, int opt_flags, + int opt_type, void *from, bool get, Error *err) +{ + bool need_switch = false; + switchwin_T switchwin; + aco_save_T aco; + int result = 0; + + try_start(); + switch (opt_type) { + case SREQ_WIN: + need_switch = (win_T *)from != curwin; + if (need_switch) { + 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 result; + } + api_set_error(err, kErrorTypeException, "Problem while switching windows"); + return result; + } + } + result = access_option_value(key, numval, stringval, opt_flags, get, err); + if (need_switch) { + restore_win_noblock(&switchwin, true); + } + break; + case SREQ_BUF: + need_switch = (buf_T *)from != curbuf; + if (need_switch) { + aucmd_prepbuf(&aco, (buf_T *)from); + } + result = access_option_value(key, numval, stringval, opt_flags, get, err); + if (need_switch) { + aucmd_restbuf(&aco); + } + break; + case SREQ_GLOBAL: + result = access_option_value(key, numval, stringval, opt_flags, get, err); + break; + } + + if (ERROR_SET(err)) { + return result; + } + + try_end(err); + + return result; +} |