options.c (12093B)
1 #include <assert.h> 2 #include <stdbool.h> 3 #include <string.h> 4 5 #include "nvim/api/keysets_defs.h" 6 #include "nvim/api/options.h" 7 #include "nvim/api/private/defs.h" 8 #include "nvim/api/private/dispatch.h" 9 #include "nvim/api/private/helpers.h" 10 #include "nvim/api/private/validate.h" 11 #include "nvim/autocmd.h" 12 #include "nvim/autocmd_defs.h" 13 #include "nvim/buffer.h" 14 #include "nvim/buffer_defs.h" 15 #include "nvim/globals.h" 16 #include "nvim/memline.h" 17 #include "nvim/memory.h" 18 #include "nvim/memory_defs.h" 19 #include "nvim/option.h" 20 #include "nvim/types_defs.h" 21 #include "nvim/vim_defs.h" 22 #include "nvim/window.h" 23 24 #include "api/options.c.generated.h" 25 26 static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex *opt_idxp, 27 int *opt_flags, OptScope *scope, void **from, char **filetype, 28 Error *err) 29 { 30 #define HAS_KEY_X(d, v) HAS_KEY(d, option, v) 31 if (HAS_KEY_X(opts, scope)) { 32 if (!strcmp(opts->scope.data, "local")) { 33 *opt_flags = OPT_LOCAL; 34 } else if (!strcmp(opts->scope.data, "global")) { 35 *opt_flags = OPT_GLOBAL; 36 } else { 37 VALIDATE_EXP(false, "scope", "'local' or 'global'", NULL, { 38 return FAIL; 39 }); 40 } 41 } 42 43 *scope = kOptScopeGlobal; 44 45 if (filetype != NULL && HAS_KEY_X(opts, filetype)) { 46 *filetype = opts->filetype.data; 47 } 48 49 if (HAS_KEY_X(opts, win)) { 50 *scope = kOptScopeWin; 51 *from = find_window_by_handle(opts->win, err); 52 if (ERROR_SET(err)) { 53 return FAIL; 54 } 55 } 56 57 if (HAS_KEY_X(opts, buf)) { 58 VALIDATE(!(HAS_KEY_X(opts, scope) && *opt_flags == OPT_GLOBAL), "%s", 59 "cannot use both global 'scope' and 'buf'", { 60 return FAIL; 61 }); 62 *opt_flags = OPT_LOCAL; 63 *scope = kOptScopeBuf; 64 *from = find_buffer_by_handle(opts->buf, err); 65 if (ERROR_SET(err)) { 66 return FAIL; 67 } 68 } 69 70 VALIDATE((!HAS_KEY_X(opts, filetype) 71 || !(HAS_KEY_X(opts, buf) || HAS_KEY_X(opts, scope) || HAS_KEY_X(opts, win))), 72 "%s", "cannot use 'filetype' with 'scope', 'buf' or 'win'", { 73 return FAIL; 74 }); 75 76 VALIDATE((!HAS_KEY_X(opts, win) || !HAS_KEY_X(opts, buf)), 77 "%s", "cannot use both 'buf' and 'win'", { 78 return FAIL; 79 }); 80 81 *opt_idxp = find_option(name); 82 if (*opt_idxp == kOptInvalid) { 83 // unknown option 84 api_set_error(err, kErrorTypeValidation, "Unknown option '%s'", name); 85 } else if (*scope == kOptScopeBuf || *scope == kOptScopeWin) { 86 // if 'buf' or 'win' is passed, make sure the option supports it 87 if (!option_has_scope(*opt_idxp, *scope)) { 88 char *tgt = *scope == kOptScopeBuf ? "buf" : "win"; 89 char *global = option_has_scope(*opt_idxp, kOptScopeGlobal) ? "global " : ""; 90 char *req = option_has_scope(*opt_idxp, kOptScopeBuf) 91 ? "buffer-local " 92 : (option_has_scope(*opt_idxp, kOptScopeWin) ? "window-local " : ""); 93 94 api_set_error(err, kErrorTypeValidation, "'%s' cannot be passed for %s%soption '%s'", 95 tgt, global, req, name); 96 } 97 } 98 99 return ERROR_SET(err) ? FAIL : OK; 100 #undef HAS_KEY_X 101 } 102 103 /// Create a dummy buffer and run the FileType autocmd on it. 104 static buf_T *do_ft_buf(const char *filetype, aco_save_T *aco, bool *aco_used, Error *err) 105 FUNC_ATTR_NONNULL_ARG(2, 3, 4) 106 { 107 *aco_used = false; 108 if (filetype == NULL) { 109 return NULL; 110 } 111 112 // Allocate a buffer without putting it in the buffer list. 113 buf_T *ftbuf = buflist_new(NULL, NULL, 1, BLN_DUMMY); 114 if (ftbuf == NULL) { 115 api_set_error(err, kErrorTypeException, "Could not create internal buffer"); 116 return NULL; 117 } 118 119 // Open a memline for use by autocommands. 120 if (ml_open(ftbuf) == FAIL) { 121 api_set_error(err, kErrorTypeException, "Could not load internal buffer"); 122 return ftbuf; 123 } 124 125 bufref_T bufref; 126 set_bufref(&bufref, ftbuf); 127 128 // Set curwin/curbuf to buf and save a few things. 129 aucmd_prepbuf(aco, ftbuf); 130 *aco_used = true; 131 132 set_option_direct(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, SID_NONE); 133 set_option_direct(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, SID_NONE); 134 assert(ftbuf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already 135 ftbuf->b_p_swf = false; 136 ftbuf->b_p_ml = false; 137 ftbuf->b_p_ft = xstrdup(filetype); 138 139 TRY_WRAP(err, { 140 do_filetype_autocmd(ftbuf, false); 141 }); 142 143 if (!bufref_valid(&bufref)) { 144 if (!ERROR_SET(err)) { 145 api_set_error(err, kErrorTypeException, "Internal buffer was deleted"); 146 } 147 return NULL; 148 } 149 150 return ftbuf; 151 } 152 153 static void wipe_ft_buf(buf_T *buf) 154 FUNC_ATTR_NONNULL_ALL 155 { 156 block_autocmds(); 157 158 bufref_T bufref; 159 set_bufref(&bufref, buf); 160 161 close_windows(buf, false); 162 // Autocommands are blocked, but 'bufhidden' may have wiped it already. 163 // Also can't wipe if the buffer is somehow still in a window or current. 164 if (bufref_valid(&bufref) && buf != curbuf && buf->b_nwindows == 0) { 165 wipe_buffer(buf, false); 166 } 167 if (bufref_valid(&bufref)) { 168 buf->b_flags &= ~BF_DUMMY; // Couldn't wipe; keep it instead. 169 } 170 171 unblock_autocmds(); 172 } 173 174 /// Gets the value of an option. The behavior of this function matches that of 175 /// |:set|: the local value of an option is returned if it exists; otherwise, 176 /// the global value is returned. Local values always correspond to the current 177 /// buffer or window, unless "buf" or "win" is set in {opts}. 178 /// 179 /// @param name Option name 180 /// @param opts Optional parameters 181 /// - scope: One of "global" or "local". Analogous to 182 /// |:setglobal| and |:setlocal|, respectively. 183 /// - win: |window-ID|. Used for getting window local options. 184 /// - buf: Buffer number. Used for getting buffer local options. 185 /// Implies {scope} is "local". 186 /// - filetype: |filetype|. Used to get the default option for a 187 /// specific filetype. Cannot be used with any other option. 188 /// Note: this will trigger |ftplugin| and all |FileType| 189 /// autocommands for the corresponding filetype. 190 /// @param[out] err Error details, if any 191 /// @return Option value 192 Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) 193 FUNC_API_SINCE(9) FUNC_API_RET_ALLOC 194 { 195 OptIndex opt_idx = 0; 196 int opt_flags = 0; 197 OptScope scope = kOptScopeGlobal; 198 void *from = NULL; 199 char *filetype = NULL; 200 201 if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, 202 &filetype, err)) { 203 return (Object)OBJECT_INIT; 204 } 205 206 aco_save_T aco; 207 bool aco_used; 208 209 buf_T *ftbuf = do_ft_buf(filetype, &aco, &aco_used, err); 210 if (ERROR_SET(err)) { 211 if (aco_used) { 212 // restore curwin/curbuf and a few other things 213 aucmd_restbuf(&aco); 214 } 215 if (ftbuf != NULL) { 216 wipe_ft_buf(ftbuf); 217 } 218 return (Object)OBJECT_INIT; 219 } 220 221 if (ftbuf != NULL) { 222 assert(!from); 223 from = ftbuf; 224 } 225 226 OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err); 227 228 if (ftbuf != NULL) { 229 if (aco_used) { 230 // restore curwin/curbuf and a few other things 231 aucmd_restbuf(&aco); 232 } 233 wipe_ft_buf(ftbuf); 234 } 235 236 if (ERROR_SET(err)) { 237 goto err; 238 } 239 240 VALIDATE_S(value.type != kOptValTypeNil, "option", name.data, { 241 goto err; 242 }); 243 244 return optval_as_object(value); 245 err: 246 optval_free(value); 247 return (Object)OBJECT_INIT; 248 } 249 250 /// Sets the value of an option. The behavior of this function matches that of 251 /// |:set|: for global-local options, both the global and local value are set 252 /// unless otherwise specified with {scope}. 253 /// 254 /// Note the options {win} and {buf} cannot be used together. 255 /// 256 /// @param name Option name 257 /// @param value New option value 258 /// @param opts Optional parameters 259 /// - scope: One of "global" or "local". Analogous to 260 /// |:setglobal| and |:setlocal|, respectively. 261 /// - win: |window-ID|. Used for setting window local option. 262 /// - buf: Buffer number. Used for setting buffer local option. 263 /// @param[out] err Error details, if any 264 void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(option) *opts, 265 Error *err) 266 FUNC_API_SINCE(9) 267 { 268 OptIndex opt_idx = 0; 269 int opt_flags = 0; 270 OptScope scope = kOptScopeGlobal; 271 void *to = NULL; 272 if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &to, NULL, 273 err)) { 274 return; 275 } 276 277 // If: 278 // - window id is provided 279 // - scope is not provided 280 // - option is global or local to window (global-local) 281 // 282 // Then force scope to local since we don't want to change the global option 283 if (scope == kOptScopeWin && opt_flags == 0) { 284 if (option_has_scope(opt_idx, kOptScopeGlobal)) { 285 opt_flags = OPT_LOCAL; 286 } 287 } 288 289 bool error = false; 290 OptVal optval = object_as_optval(value, &error); 291 292 // Handle invalid option value type. 293 // Don't use `name` in the error message here, because `name` can be any String. 294 // No need to check if value type actually matches the types for the option, as set_option_value() 295 // already handles that. 296 VALIDATE_EXP(!error, "value", "valid option type", api_typename(value.type), { 297 return; 298 }); 299 300 WITH_SCRIPT_CONTEXT(channel_id, { 301 set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); 302 }); 303 } 304 305 /// Gets the option information for all options. 306 /// 307 /// The dict has the full option names as keys and option metadata dicts as detailed at 308 /// |nvim_get_option_info2()|. 309 /// 310 /// @see |nvim_get_commands()| 311 /// 312 /// @return dict of all options 313 Dict nvim_get_all_options_info(Arena *arena, Error *err) 314 FUNC_API_SINCE(7) 315 { 316 return get_all_vimoptions(arena); 317 } 318 319 /// Gets the option information for one option from arbitrary buffer or window 320 /// 321 /// Resulting dict has keys: 322 /// - name: Name of the option (like 'filetype') 323 /// - shortname: Shortened name of the option (like 'ft') 324 /// - type: type of option ("string", "number" or "boolean") 325 /// - default: The default value for the option 326 /// - was_set: Whether the option was set. 327 /// 328 /// - last_set_sid: Last set script id (if any) 329 /// - last_set_linenr: line number where option was set 330 /// - last_set_chan: Channel where option was set (0 for local) 331 /// 332 /// - scope: one of "global", "win", or "buf" 333 /// - global_local: whether win or buf option has a global value 334 /// 335 /// - commalist: List of comma separated values 336 /// - flaglist: List of single char flags 337 /// 338 /// When {scope} is not provided, the last set information applies to the local 339 /// value in the current buffer or window if it is available, otherwise the 340 /// global value information is returned. This behavior can be disabled by 341 /// explicitly specifying {scope} in the {opts} table. 342 /// 343 /// @param name Option name 344 /// @param opts Optional parameters 345 /// - scope: One of "global" or "local". Analogous to 346 /// |:setglobal| and |:setlocal|, respectively. 347 /// - win: |window-ID|. Used for getting window local options. 348 /// - buf: Buffer number. Used for getting buffer local options. 349 /// Implies {scope} is "local". 350 /// @param[out] err Error details, if any 351 /// @return Option Information 352 DictAs(get_option_info) nvim_get_option_info2(String name, Dict(option) *opts, Arena *arena, 353 Error *err) 354 FUNC_API_SINCE(11) 355 { 356 OptIndex opt_idx = 0; 357 int opt_flags = 0; 358 OptScope scope = kOptScopeGlobal; 359 void *from = NULL; 360 if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, NULL, 361 err)) { 362 return (Dict)ARRAY_DICT_INIT; 363 } 364 365 buf_T *buf = (scope == kOptScopeBuf) ? (buf_T *)from : curbuf; 366 win_T *win = (scope == kOptScopeWin) ? (win_T *)from : curwin; 367 368 return get_vimoption(name, opt_flags, buf, win, arena, err); 369 }