aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api/options.c
diff options
context:
space:
mode:
authorJosh Rahm <rahm@google.com>2022-07-18 19:37:18 +0000
committerJosh Rahm <rahm@google.com>2022-07-18 19:37:18 +0000
commit308e1940dcd64aa6c344c403d4f9e0dda58d9c5c (patch)
tree35fe43e01755e0f312650667004487a44d6b7941 /src/nvim/api/options.c
parent96a00c7c588b2f38a2424aeeb4ea3581d370bf2d (diff)
parente8c94697bcbe23a5c7b07c292b90a6b70aadfa87 (diff)
downloadrneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.tar.gz
rneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.tar.bz2
rneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.zip
Merge remote-tracking branch 'upstream/master' into rahm
Diffstat (limited to 'src/nvim/api/options.c')
-rw-r--r--src/nvim/api/options.c554
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;
+}