mirror of
https://github.com/neovim/neovim.git
synced 2025-10-08 02:46:31 +00:00

Co-authored-by: zeertzjq <zeertzjq@outlook.com> Co-authored-by: Valery Viktorovsky <viktorovsky@gmail.com>
555 lines
16 KiB
C
555 lines
16 KiB
C
// 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;
|
|
getoption_T result = access_option_value_for(name.data, &numval, &stringval, scope, opt_type,
|
|
from, true, err);
|
|
if (ERROR_SET(err)) {
|
|
return rv;
|
|
}
|
|
|
|
switch (result) {
|
|
case gov_string:
|
|
rv = STRING_OBJ(cstr_as_string(stringval));
|
|
break;
|
|
case gov_number:
|
|
rv = INTEGER_OBJ(numval);
|
|
break;
|
|
case gov_bool:
|
|
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 getoption_T 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 getoption_T 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;
|
|
getoption_T 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;
|
|
}
|