dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 85.1% 416 / 5 / 494
Functions: 91.3% 63 / 0 / 69
Branches: 72.4% 199 / 78 / 353

src/options.c
Line Branch Exec Source
1 #include <errno.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include "options.h"
5 #include "buffer.h"
6 #include "command/serialize.h"
7 #include "editor.h"
8 #include "file-option.h"
9 #include "filetype.h"
10 #include "status.h"
11 #include "util/align.h"
12 #include "util/arith.h"
13 #include "util/bsearch.h"
14 #include "util/intern.h"
15 #include "util/numtostr.h"
16 #include "util/str-array.h"
17 #include "util/str-util.h"
18 #include "util/strtonum.h"
19 #include "util/xmalloc.h"
20 #include "util/xstring.h"
21
22 typedef enum {
23 OPT_STR,
24 OPT_UINT,
25 OPT_UINT8,
26 OPT_ENUM,
27 OPT_BOOL,
28 OPT_FLAG,
29 OPT_REGEX,
30 OPT_FILESIZE,
31 } OptionType;
32
33 typedef union {
34 const char *str_val; // OPT_STR, OPT_REGEX
35 unsigned int uint_val; // OPT_UINT, OPT_UINT8, OPT_ENUM, OPT_FLAG
36 uint_least64_t u64_val; // OPT_FILESIZE
37 bool bool_val; // OPT_BOOL
38 } OptionValue;
39
40 typedef union {
41 struct {bool (*validate)(ErrorBuffer *ebuf, const char *value);} str_opt; // OPT_STR (optional)
42 struct {unsigned int min, max;} uint_opt; // OPT_UINT, OPT_UINT8
43 struct {const char *const *values;} enum_opt; // OPT_ENUM, OPT_FLAG, OPT_BOOL
44 } OptionConstraint;
45
46 typedef struct {
47 const char name[22];
48 bool local;
49 bool global;
50 unsigned int offset;
51 OptionType type;
52 OptionConstraint u;
53 void (*on_change)(EditorState *e, bool global); // Optional
54 } OptionDesc;
55
56 #define STR_OPT(_name, OLG, _validate, _on_change) { \
57 OLG \
58 .name = _name, \
59 .type = OPT_STR, \
60 .u = {.str_opt = {.validate = _validate}}, \
61 .on_change = _on_change, \
62 }
63
64 #define UINT_OPT(_name, OLG, _min, _max, _on_change) { \
65 OLG \
66 .name = _name, \
67 .type = OPT_UINT, \
68 .u = {.uint_opt = {.min = _min, .max = _max}}, \
69 .on_change = _on_change, \
70 }
71
72 #define UINT8_OPT(_name, OLG, _min, _max, _on_change) { \
73 OLG \
74 .name = _name, \
75 .type = OPT_UINT8, \
76 .u = {.uint_opt = {.min = _min, .max = _max}}, \
77 .on_change = _on_change, \
78 }
79
80 #define ENUM_OPT(_name, OLG, _values, _on_change) { \
81 OLG \
82 .name = _name, \
83 .type = OPT_ENUM, \
84 .u = {.enum_opt = {.values = _values}}, \
85 .on_change = _on_change, \
86 }
87
88 #define FLAG_OPT(_name, OLG, _values, _on_change) { \
89 OLG \
90 .name = _name, \
91 .type = OPT_FLAG, \
92 .u = {.enum_opt = {.values = _values}}, \
93 .on_change = _on_change, \
94 }
95
96 #define BOOL_OPT(_name, OLG, _on_change) { \
97 OLG \
98 .name = _name, \
99 .type = OPT_BOOL, \
100 .u = {.enum_opt = {.values = bool_enum}}, \
101 .on_change = _on_change, \
102 }
103
104 #define REGEX_OPT(_name, OLG, _on_change) { \
105 OLG \
106 .name = _name, \
107 .type = OPT_REGEX, \
108 .on_change = _on_change, \
109 }
110
111 #define FSIZE_OPT(_name, OLG, _on_change) { \
112 OLG \
113 .name = _name, \
114 .type = OPT_FILESIZE, \
115 .on_change = _on_change, \
116 }
117
118 #define OLG(_offset, _local, _global) \
119 .offset = _offset, \
120 .local = _local, \
121 .global = _global, \
122
123 #define L(member) OLG(offsetof(LocalOptions, member), true, false)
124 #define G(member) OLG(offsetof(GlobalOptions, member), false, true)
125 #define C(member) OLG(offsetof(CommonOptions, member), true, true)
126
127 static void filetype_changed(EditorState *e, bool global)
128 {
129 BUG_ON(!e->buffer);
130 BUG_ON(global);
131 set_file_options(e, e->buffer);
132 buffer_update_syntax(e, e->buffer);
133 }
134
135 3 static void set_window_title_changed(EditorState *e, bool global)
136 {
137 3 BUG_ON(!global);
138 3 e->screen_update |= UPDATE_TERM_TITLE;
139 3 }
140
141 1 static void syntax_changed(EditorState *e, bool global)
142 {
143
2/4
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 5 not taken.
1 if (e->buffer && !global) {
144 1 buffer_update_syntax(e, e->buffer);
145 }
146 1 }
147
148 static void overwrite_changed(EditorState *e, bool global)
149 {
150 if (!global) {
151 e->screen_update |= UPDATE_CURSOR_STYLE;
152 }
153 }
154
155 static void window_separator_changed(EditorState *e, bool global)
156 {
157 BUG_ON(!global);
158 if (e->root_frame && !e->root_frame->window) {
159 e->screen_update |= UPDATE_WINDOW_SEPARATORS;
160 }
161 }
162
163 5 static void redraw_buffer(EditorState *e, bool global)
164 {
165
2/4
✓ Branch 2 → 3 taken 5 times.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 5 not taken.
5 if (e->buffer && !global) {
166 5 mark_all_lines_changed(e->buffer);
167 }
168 5 }
169
170 3 static void redraw_screen(EditorState *e, bool global)
171 {
172 3 BUG_ON(!global);
173 3 e->screen_update |= UPDATE_ALL_WINDOWS;
174 3 }
175
176 28 static bool validate_statusline_format(ErrorBuffer *ebuf, const char *value)
177 {
178 28 size_t errpos = statusline_format_find_error(value);
179
2/2
✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 7 taken 24 times.
28 if (likely(errpos == 0)) {
180 return true;
181 }
182
183 4 char ch = value[errpos];
184
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 2 times.
4 if (ch == '\0') {
185 2 return error_msg(ebuf, "Format character expected after '%%'");
186 }
187 2 return error_msg(ebuf, "Invalid format character '%c'", ch);
188 }
189
190 64 static bool validate_filetype(ErrorBuffer *ebuf, const char *value)
191 {
192
2/2
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 62 times.
64 if (!is_valid_filetype_name(value)) {
193 2 return error_msg(ebuf, "Invalid filetype name '%s'", value);
194 }
195 return true;
196 }
197
198 90 static OptionValue str_get(void *ptr)
199 {
200 90 const char *const *strp = ptr;
201 90 return (OptionValue){.str_val = *strp};
202 }
203
204 1 static void str_set(void *ptr, OptionValue v)
205 {
206 1 const char **strp = ptr;
207 1 *strp = str_intern(v.str_val);
208 1 }
209
210 9 static bool str_parse(const OptionDesc *d, ErrorBuffer *ebuf, const char *str, OptionValue *v)
211 {
212
3/4
✓ Branch 2 → 3 taken 9 times.
✗ Branch 2 → 6 not taken.
✓ Branch 4 → 5 taken 6 times.
✓ Branch 4 → 6 taken 3 times.
9 bool valid = !d->u.str_opt.validate || d->u.str_opt.validate(ebuf, str);
213 9 v->str_val = valid ? str : NULL;
214 9 return valid;
215 }
216
217 8 static const char *str_string(const OptionDesc* UNUSED_ARG(d), OptionValue v)
218 {
219 8 const char *s = v.str_val;
220
2/2
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 6 times.
8 return s ? s : "";
221 }
222
223 65 static OptionValue re_get(void *ptr)
224 {
225 65 const InternedRegexp *const *irp = ptr;
226
2/2
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 63 times.
65 return (OptionValue){.str_val = *irp ? (*irp)->str : NULL};
227 }
228
229 2 static void re_set(void *ptr, OptionValue v)
230 {
231 // Note that this function is only ever called if re_parse() has already
232 // validated the pattern
233 2 const InternedRegexp **irp = ptr;
234
1/2
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 4 not taken.
2 *irp = v.str_val ? regexp_intern(NULL, v.str_val) : NULL;
235 2 }
236
237 56 static bool re_parse(const OptionDesc* UNUSED_ARG(d), ErrorBuffer *ebuf, const char *str, OptionValue *v)
238 {
239
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 56 times.
56 if (str[0] == '\0') {
240 v->str_val = NULL;
241 return true;
242 }
243
244
3/4
✓ Branch 5 → 6 taken 55 times.
✓ Branch 5 → 9 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 55 times.
56 bool valid = regexp_is_interned(str) || regexp_is_valid(ebuf, str, REG_NEWLINE);
245 56 v->str_val = valid ? str : NULL;
246 56 return valid;
247 }
248
249 252 static OptionValue uint_get(void *ptr)
250 {
251 252 const unsigned int *valp = ptr;
252 252 return (OptionValue){.uint_val = *valp};
253 }
254
255 3 static void uint_set(void *ptr, OptionValue v)
256 {
257 3 unsigned int *valp = ptr;
258 3 *valp = v.uint_val;
259 3 }
260
261 28 static bool uint_parse(const OptionDesc *d, ErrorBuffer *ebuf, const char *str, OptionValue *v)
262 {
263 28 unsigned int val;
264
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 26 times.
28 if (!str_to_uint(str, &val)) {
265 2 return error_msg(ebuf, "Integer value for %s expected", d->name);
266 }
267
268 26 const unsigned int min = d->u.uint_opt.min;
269 26 const unsigned int max = d->u.uint_opt.max;
270
4/4
✓ Branch 5 → 6 taken 25 times.
✓ Branch 5 → 7 taken 1 time.
✓ Branch 6 → 7 taken 3 times.
✓ Branch 6 → 8 taken 22 times.
26 if (val < min || val > max) {
271 4 return error_msg(ebuf, "Value for %s must be in %u-%u range", d->name, min, max);
272 }
273
274 22 v->uint_val = val;
275 22 return true;
276 }
277
278 11 static const char *uint_string(const OptionDesc* UNUSED_ARG(desc), OptionValue value)
279 {
280 11 return uint_to_str(value.uint_val);
281 }
282
283 316 static OptionValue uint8_get(void *ptr)
284 {
285 316 const uint8_t *valp = ptr;
286 316 return (OptionValue){.uint_val = (unsigned int)(*valp)};
287 }
288
289 4 static void uint8_set(void *ptr, OptionValue value)
290 {
291 4 uint8_t *valp = ptr;
292 4 *valp = (uint8_t)value.uint_val;
293 4 }
294
295 850 static OptionValue bool_get(void *ptr)
296 {
297 850 const bool *valp = ptr;
298 850 return (OptionValue){.bool_val = *valp};
299 }
300
301 24 static void bool_set(void *ptr, OptionValue v)
302 {
303 24 bool *valp = ptr;
304 24 *valp = v.bool_val;
305 24 }
306
307 115 static bool bool_parse(const OptionDesc *d, ErrorBuffer *ebuf, const char *str, OptionValue *v)
308 {
309 115 bool t = streq(str, "true");
310
4/4
✓ Branch 2 → 3 taken 41 times.
✓ Branch 2 → 4 taken 74 times.
✓ Branch 3 → 4 taken 38 times.
✓ Branch 3 → 5 taken 3 times.
115 if (t || streq(str, "false")) {
311 112 v->bool_val = t;
312 112 return true;
313 }
314 3 return error_msg(ebuf, "Invalid value for %s; expected: true, false", d->name);
315 }
316
317 37 static const char *bool_string(const OptionDesc* UNUSED_ARG(d), OptionValue v)
318 {
319
2/2
✓ Branch 2 → 3 taken 22 times.
✓ Branch 2 → 4 taken 15 times.
37 return v.bool_val ? "true" : "false";
320 }
321
322 12 static bool enum_parse(const OptionDesc *d, ErrorBuffer *ebuf, const char *str, OptionValue *v)
323 {
324 12 const char *const *values = d->u.enum_opt.values;
325 12 unsigned int n;
326
2/2
✓ Branch 6 → 3 taken 32 times.
✓ Branch 6 → 7 taken 12 times.
44 for (n = 0; values[n]; n++) {
327
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 32 times.
32 if (streq(values[n], str)) {
328 v->uint_val = n;
329 return true;
330 }
331 }
332
333 12 char expected[64];
334 12 string_array_concat(expected, sizeof(expected), values, n, strview(", "));
335 12 return error_msg(ebuf, "Invalid value for %s; expected: %s", d->name, expected);
336 }
337
338 12 static const char *enum_string(const OptionDesc *desc, OptionValue value)
339 {
340 12 return desc->u.enum_opt.values[value.uint_val];
341 }
342
343 83 static bool flag_parse(const OptionDesc *d, ErrorBuffer *ebuf, const char *str, OptionValue *v)
344 {
345 // "0" is allowed for compatibility and is the same as ""
346
2/2
✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 4 taken 74 times.
83 if (streq(str, "0")) {
347 9 v->uint_val = 0;
348 9 return true;
349 }
350
351 74 const char *const *values = d->u.enum_opt.values;
352 74 unsigned int flags = 0;
353
354
2/2
✓ Branch 17 → 5 taken 464 times.
✓ Branch 17 → 18 taken 70 times.
534 for (size_t pos = 0, len = strlen(str); pos < len; ) {
355 464 const StringView flag = get_delim(str, &pos, len, ',');
356 464 size_t n;
357
2/2
✓ Branch 11 → 7 taken 2114 times.
✓ Branch 11 → 12 taken 4 times.
2582 for (n = 0; values[n]; n++) {
358
2/2
✓ Branch 8 → 9 taken 460 times.
✓ Branch 8 → 10 taken 1654 times.
2114 if (strview_equal_cstring(flag, values[n])) {
359 460 flags |= 1u << n;
360 460 break;
361 }
362 }
363
2/2
✓ Branch 12 → 13 taken 4 times.
✓ Branch 12 → 16 taken 460 times.
464 if (unlikely(!values[n])) {
364 4 char expected[128];
365 4 string_array_concat(expected, sizeof(expected), values, n, strview(", "));
366 4 return error_msg (
367 ebuf,
368 "Invalid flag '%.*s' for %s; expected: %s",
369 4 (int)flag.length, flag.data, d->name, expected
370 );
371 }
372 }
373
374 70 v->uint_val = flags;
375 70 return true;
376 }
377
378 52 static const char *flag_string(const OptionDesc *desc, OptionValue value)
379 {
380 52 const char *const *values = desc->u.enum_opt.values;
381 52 const char *tmp[16];
382 52 const unsigned int flags = value.uint_val;
383 52 size_t nstrs = 0;
384
385 // Collect any `values` strings that correspond to set `flags` into
386 // a temporary array
387
2/2
✓ Branch 8 → 3 taken 416 times.
✓ Branch 8 → 9 taken 52 times.
468 for (size_t i = 0; values[i]; i++) {
388 416 BUG_ON(i >= ARRAYLEN(tmp));
389
2/2
✓ Branch 5 → 6 taken 386 times.
✓ Branch 5 → 7 taken 30 times.
416 if (flags & (1u << i)) {
390 386 tmp[nstrs++] = values[i];
391 }
392 }
393
394 // ...so that string_array_concat() can be used to concatenate them
395 // into a comma-separated list
396 52 static char buf[128];
397 52 string_array_concat(buf, sizeof(buf), tmp, nstrs, strview(","));
398 52 return buf;
399 }
400
401 39 static OptionValue fsize_get(void *ptr)
402 {
403 39 const uint_least64_t *valp = ptr;
404 39 return (OptionValue){.u64_val = *valp};
405 }
406
407 static void fsize_set(void *ptr, OptionValue v)
408 {
409 uint_least64_t *valp = ptr;
410 *valp = v.u64_val;
411 }
412
413 6 static bool fsize_parse(const OptionDesc* UNUSED_ARG(d), ErrorBuffer *ebuf, const char *str, OptionValue *value)
414 {
415 6 intmax_t bytes = parse_filesize(str);
416
1/2
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 8 not taken.
6 if (bytes < 0 || (uintmax_t)bytes > (uintmax_t)UINT_LEAST64_MAX) {
417 6 int errnum = (bytes < 0) ? -bytes : EOVERFLOW;
418
1/2
✓ Branch 4 → 5 taken 6 times.
✗ Branch 4 → 6 not taken.
6 if (errnum == EINVAL) {
419 // No need to append `strerror(EINVAL)` ("invalid argument") here
420 6 return error_msg(ebuf, "invalid filesize '%s'", str);
421 }
422 const char *err = strerror(errnum);
423 return error_msg(ebuf, "invalid filesize '%s': %s", str, err);
424 }
425
426 value->u64_val = bytes;
427 return true;
428 }
429
430 6 static const char *fsize_string(const OptionDesc* UNUSED_ARG(d), OptionValue value)
431 {
432 6 static char buf[PRECISE_FILESIZE_STR_MAX];
433 6 return filesize_to_str_precise(value.u64_val, buf);
434 }
435
436 static const struct {
437 OptionValue (*get)(void *ptr);
438 void (*set)(void *ptr, OptionValue value);
439 bool (*parse)(const OptionDesc *desc, ErrorBuffer *ebuf, const char *str, OptionValue *value);
440 const char *(*string)(const OptionDesc *desc, OptionValue value);
441 } option_ops[] = {
442 [OPT_STR] = {str_get, str_set, str_parse, str_string},
443 [OPT_UINT] = {uint_get, uint_set, uint_parse, uint_string},
444 [OPT_UINT8] = {uint8_get, uint8_set, uint_parse, uint_string},
445 [OPT_ENUM] = {uint8_get, uint8_set, enum_parse, enum_string},
446 [OPT_BOOL] = {bool_get, bool_set, bool_parse, bool_string},
447 [OPT_FLAG] = {uint_get, uint_set, flag_parse, flag_string},
448 [OPT_REGEX] = {re_get, re_set, re_parse, str_string},
449 [OPT_FILESIZE] = {fsize_get, fsize_set, fsize_parse, fsize_string},
450 };
451
452 static const char *const bool_enum[] = {"false", "true", NULL};
453 static const char *const msg_enum[] = {"A", "B", "C", NULL};
454 static const char *const newline_enum[] = {"unix", "dos", NULL};
455 static const char *const tristate_enum[] = {"false", "true", "auto", NULL};
456 static const char *const save_unmodified_enum[] = {"none", "touch", "full", NULL};
457 static const char *const window_separator_enum[] = {"blank", "bar", NULL};
458
459 static const char *const detect_indent_values[] = {
460 "1", "2", "3", "4", "5", "6", "7", "8",
461 NULL
462 };
463
464 // Note: this must be kept in sync with WhitespaceErrorFlags
465 static const char *const ws_error_values[] = {
466 "space-indent",
467 "space-align",
468 "tab-indent",
469 "tab-after-indent",
470 "special",
471 "auto-indent",
472 "trailing",
473 "all-trailing",
474 NULL
475 };
476
477 static const OptionDesc option_desc[] = {
478 BOOL_OPT("auto-indent", C(auto_indent), NULL),
479 BOOL_OPT("brace-indent", L(brace_indent), NULL),
480 ENUM_OPT("case-sensitive-search", G(case_sensitive_search), tristate_enum, NULL),
481 BOOL_OPT("center-at-eof", G(center_at_eof), NULL),
482 FLAG_OPT("detect-indent", C(detect_indent), detect_indent_values, NULL),
483 BOOL_OPT("display-special", G(display_special), redraw_screen),
484 BOOL_OPT("editorconfig", C(editorconfig), NULL),
485 BOOL_OPT("emulate-tab", C(emulate_tab), NULL),
486 UINT_OPT("esc-timeout", G(esc_timeout), 0, 2000, NULL),
487 BOOL_OPT("expand-tab", C(expand_tab), redraw_buffer),
488 BOOL_OPT("file-history", C(file_history), NULL),
489 FSIZE_OPT("filesize-limit", G(filesize_limit), NULL),
490 STR_OPT("filetype", L(filetype), validate_filetype, filetype_changed),
491 BOOL_OPT("fsync", C(fsync), NULL),
492 REGEX_OPT("indent-regex", L(indent_regex), NULL),
493 UINT8_OPT("indent-width", C(indent_width), 1, INDENT_WIDTH_MAX, NULL),
494 BOOL_OPT("lock-files", G(lock_files), NULL),
495 ENUM_OPT("msg-compile", G(msg_compile), msg_enum, NULL),
496 ENUM_OPT("msg-tag", G(msg_tag), msg_enum, NULL),
497 ENUM_OPT("newline", G(crlf_newlines), newline_enum, NULL),
498 BOOL_OPT("optimize-true-color", G(optimize_true_color), redraw_screen),
499 BOOL_OPT("overwrite", C(overwrite), overwrite_changed),
500 ENUM_OPT("save-unmodified", C(save_unmodified), save_unmodified_enum, NULL),
501 UINT8_OPT("scroll-margin", G(scroll_margin), 0, 100, redraw_screen),
502 BOOL_OPT("select-cursor-char", G(select_cursor_char), redraw_screen),
503 BOOL_OPT("set-window-title", G(set_window_title), set_window_title_changed),
504 BOOL_OPT("show-line-numbers", G(show_line_numbers), redraw_screen),
505 STR_OPT("statusline-left", G(statusline_left), validate_statusline_format, NULL),
506 STR_OPT("statusline-right", G(statusline_right), validate_statusline_format, NULL),
507 BOOL_OPT("syntax", C(syntax), syntax_changed),
508 FSIZE_OPT("syntax-line-limit", G(syntax_line_limit), NULL),
509 FSIZE_OPT("syntax-size-limit", G(syntax_size_limit), NULL),
510 BOOL_OPT("tab-bar", G(tab_bar), redraw_screen),
511 UINT8_OPT("tab-width", C(tab_width), 1, TAB_WIDTH_MAX, redraw_buffer),
512 UINT_OPT("text-width", C(text_width), 1, TEXT_WIDTH_MAX, NULL),
513 BOOL_OPT("utf8-bom", G(utf8_bom), NULL),
514 ENUM_OPT("window-separator", G(window_separator), window_separator_enum, window_separator_changed),
515 FLAG_OPT("ws-error", C(ws_error), ws_error_values, redraw_buffer),
516 };
517
518 473 static char *local_ptr(const OptionDesc *desc, LocalOptions *opt)
519 {
520 473 BUG_ON(!desc->local);
521 473 return (char*)opt + desc->offset;
522 }
523
524 922 static char *global_ptr(const OptionDesc *desc, GlobalOptions *opt)
525 {
526 922 BUG_ON(!desc->global);
527 922 return (char*)opt + desc->offset;
528 }
529
530 18 static char *get_option_ptr(EditorState *e, const OptionDesc *d, bool global)
531 {
532
2/2
✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 4 taken 9 times.
18 return global ? global_ptr(d, &e->options) : local_ptr(d, &e->buffer->options);
533 }
534
535 463 static size_t count_enum_values(const OptionDesc *desc)
536 {
537 463 OptionType type = desc->type;
538 463 BUG_ON(type != OPT_ENUM && type != OPT_FLAG && type != OPT_BOOL);
539 463 const char *const *values = desc->u.enum_opt.values;
540 463 BUG_ON(!values);
541 463 return string_array_length((char**)values);
542 }
543
544 24 UNITTEST {
545 24 static const struct {
546 size_t alignment;
547 size_t size;
548 } map[] = {
549 [OPT_STR] = {ALIGNOF(const char*), sizeof(const char*)},
550 [OPT_UINT] = {ALIGNOF(unsigned int), sizeof(unsigned int)},
551 [OPT_UINT8] = {ALIGNOF(uint8_t), sizeof(uint8_t)},
552 [OPT_ENUM] = {ALIGNOF(uint8_t), sizeof(uint8_t)},
553 [OPT_BOOL] = {ALIGNOF(bool), sizeof(bool)},
554 [OPT_FLAG] = {ALIGNOF(unsigned int), sizeof(unsigned int)},
555 [OPT_REGEX] = {ALIGNOF(const InternedRegexp*), sizeof(const InternedRegexp*)},
556 [OPT_FILESIZE] = {ALIGNOF(uint_least64_t), sizeof(uint_least64_t)},
557 };
558
559 24 static_assert(ARRAYLEN(map) == ARRAYLEN(option_ops));
560 24 static_assert(offsetof(CommonOptions, syntax) == offsetof(GlobalOptions, syntax));
561 24 static_assert(offsetof(CommonOptions, syntax) == offsetof(LocalOptions, syntax));
562 24 GlobalOptions gopts = {.tab_bar = true};
563 24 LocalOptions lopts = {.filetype = NULL};
564
565
2/2
✓ Branch 48 → 3 taken 912 times.
✓ Branch 48 → 49 taken 24 times.
936 for (size_t i = 0; i < ARRAYLEN(option_desc); i++) {
566 912 const OptionDesc *desc = &option_desc[i];
567 912 const OptionType type = desc->type;
568 912 BUG_ON(type >= ARRAYLEN(map));
569 912 size_t alignment = map[type].alignment;
570 912 size_t end = desc->offset + map[type].size;
571
572
2/2
✓ Branch 5 → 6 taken 840 times.
✓ Branch 5 → 11 taken 72 times.
912 if (desc->global) {
573 840 const char *ptr = global_ptr(desc, &gopts);
574 840 uintptr_t ptr_val = (uintptr_t)ptr;
575 840 BUG_ON(ptr_val % alignment != 0);
576 840 BUG_ON(end > sizeof(GlobalOptions));
577 }
578
579
2/2
✓ Branch 11 → 12 taken 408 times.
✓ Branch 11 → 17 taken 504 times.
912 if (desc->local) {
580 408 const char *ptr = local_ptr(desc, &lopts);
581 408 uintptr_t ptr_val = (uintptr_t)ptr;
582 408 BUG_ON(ptr_val % alignment != 0);
583 408 BUG_ON(end > sizeof(LocalOptions));
584 }
585
586
4/4
✓ Branch 17 → 18 taken 840 times.
✓ Branch 17 → 21 taken 72 times.
✓ Branch 18 → 19 taken 336 times.
✓ Branch 18 → 21 taken 504 times.
912 if (desc->global && desc->local) {
587 336 BUG_ON(end > sizeof(CommonOptions));
588 }
589
590
6/6
✓ Branch 21 → 22 taken 72 times.
✓ Branch 21 → 24 taken 48 times.
✓ Branch 21 → 26 taken 432 times.
✓ Branch 21 → 28 taken 144 times.
✓ Branch 21 → 31 taken 48 times.
✓ Branch 21 → 47 taken 168 times.
912 switch (type) {
591 72 case OPT_UINT8:
592 72 BUG_ON(desc->u.uint_opt.max > UINT8_MAX);
593 // Fallthrough
594 case OPT_UINT:
595 120 BUG_ON(desc->u.uint_opt.min >= desc->u.uint_opt.max);
596 break;
597 432 case OPT_BOOL:
598 432 BUG_ON(desc->u.enum_opt.values != bool_enum);
599 break;
600 144 case OPT_ENUM:
601 144 BUG_ON(count_enum_values(desc) < 2); // NOLINT(*-assert-side-effect)
602 break;
603 48 case OPT_FLAG: {
604 48 size_t nvals = count_enum_values(desc);
605 48 OptionValue val = {.uint_val = -1};
606 48 BUG_ON(nvals < 2);
607 48 BUG_ON(nvals >= BITSIZE(val.uint_val));
608 48 const char *str = flag_string(desc, val);
609 48 BUG_ON(!str);
610 48 BUG_ON(str[0] == '\0');
611
1/2
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 44 taken 48 times.
48 if (!flag_parse(desc, NULL, str, &val)) {
612 BUG("flag_parse() failed for string: %s", str);
613 }
614 48 unsigned int mask = (1u << nvals) - 1;
615
1/2
✗ Branch 44 → 45 not taken.
✓ Branch 44 → 46 taken 48 times.
48 if (val.uint_val != mask) {
616 BUG("values not equal: %u, %u", val.uint_val, mask);
617 }
618 48 } break;
619 case OPT_REGEX:
620 case OPT_STR:
621 case OPT_FILESIZE:
622 break;
623 }
624 }
625
626 // Ensure option_desc[] is properly sorted
627 24 CHECK_BSEARCH_ARRAY(option_desc, name);
628 24 }
629
630 1612 static OptionValue desc_get(const OptionDesc *desc, void *ptr)
631 {
632 1612 return option_ops[desc->type].get(ptr);
633 }
634
635 68 static bool desc_equals(const OptionDesc *desc, void *ptr, OptionValue ref)
636 {
637 68 OptionValue current = desc_get(desc, ptr);
638
3/5
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 46 times.
✓ Branch 3 → 6 taken 19 times.
✗ Branch 3 → 7 not taken.
✗ Branch 3 → 8 not taken.
68 switch (desc->type) {
639 3 case OPT_STR:
640 case OPT_REGEX:
641 3 return xstreq(current.str_val, ref.str_val);
642 46 case OPT_BOOL:
643 46 return current.bool_val == ref.bool_val;
644 19 case OPT_UINT:
645 case OPT_UINT8:
646 case OPT_ENUM:
647 case OPT_FLAG:
648 19 return current.uint_val == ref.uint_val;
649 case OPT_FILESIZE:
650 return current.u64_val == ref.u64_val;
651 }
652
653 BUG("unhandled option type");
654 }
655
656 40 static void desc_set(EditorState *e, const OptionDesc *desc, void *ptr, bool global, OptionValue value)
657 {
658
2/2
✓ Branch 3 → 4 taken 34 times.
✓ Branch 3 → 7 taken 6 times.
40 if (desc_equals(desc, ptr, value)) {
659 return;
660 }
661
662 34 option_ops[desc->type].set(ptr, value);
663
2/2
✓ Branch 5 → 6 taken 12 times.
✓ Branch 5 → 7 taken 22 times.
34 if (desc->on_change) {
664 12 desc->on_change(e, global);
665 }
666 }
667
668 261 static bool desc_parse(const OptionDesc *desc, ErrorBuffer *ebuf, const char *str, OptionValue *value)
669 {
670 261 return option_ops[desc->type].parse(desc, ebuf, str, value);
671 }
672
673 78 static const char *desc_string(const OptionDesc *desc, OptionValue value)
674 {
675 78 return option_ops[desc->type].string(desc, value);
676 }
677
678 17785 static int option_cmp(const void *key, const void *elem)
679 {
680 17785 const char *name = key;
681 17785 const OptionDesc *desc = elem;
682 17785 return strcmp(name, desc->name);
683 }
684
685 3488 static const OptionDesc *find_option(const char *name)
686 {
687 3488 return BSEARCH(name, option_desc, option_cmp);
688 }
689
690 3192 bool is_option(const char *name)
691 {
692 3192 return !!find_option(name);
693 }
694
695 287 static const OptionDesc *must_find_option(ErrorBuffer *ebuf, const char *name)
696 {
697 287 const OptionDesc *desc = find_option(name);
698
2/2
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 284 times.
287 if (!desc) {
699 3 error_msg(ebuf, "No such option %s", name);
700 }
701 287 return desc;
702 }
703
704 static const OptionDesc *must_find_global_option(ErrorBuffer *ebuf, const char *name)
705 {
706 const OptionDesc *desc = must_find_option(ebuf, name);
707 if (desc && !desc->global) {
708 error_msg(ebuf, "Option %s is not global", name);
709 return NULL;
710 }
711 return desc;
712 }
713
714 61 static bool do_set_option (
715 EditorState *e,
716 const OptionDesc *desc,
717 const char *value,
718 bool local,
719 bool global,
720 bool quiet
721 ) {
722
2/2
✓ Branch 2 → 3 taken 45 times.
✓ Branch 2 → 4 taken 16 times.
61 ErrorBuffer *ebuf = quiet ? NULL : &e->err;
723
6/6
✓ Branch 4 → 5 taken 24 times.
✓ Branch 4 → 8 taken 37 times.
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 8 taken 21 times.
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 8 taken 1 time.
61 if (local && !desc->local && !quiet) {
724 2 return error_msg(ebuf, "Option %s is not local", desc->name);
725 }
726
5/6
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 12 taken 57 times.
✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 12 not taken.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 1 time.
59 if (global && !desc->global && !quiet) {
727 1 return error_msg(ebuf, "Option %s is not global", desc->name);
728 }
729
730 58 OptionValue val;
731
2/2
✓ Branch 13 → 14 taken 26 times.
✓ Branch 13 → 27 taken 32 times.
58 if (!desc_parse(desc, ebuf, value, &val)) {
732 return quiet;
733 }
734
735 26 BUG_ON(!desc->local && !desc->global);
736
737
2/2
✓ Branch 17 → 18 taken 3 times.
✓ Branch 17 → 19 taken 23 times.
26 if (!local && !global) {
738 // Set both by default
739 3 local = desc->local;
740 3 global = desc->global;
741 }
742
743
4/4
✓ Branch 19 → 20 taken 23 times.
✓ Branch 19 → 23 taken 3 times.
✓ Branch 20 → 21 taken 22 times.
✓ Branch 20 → 23 taken 1 time.
26 if (local && desc->local) {
744 22 desc_set(e, desc, local_ptr(desc, &e->buffer->options), false, val);
745 }
746
4/4
✓ Branch 23 → 24 taken 4 times.
✓ Branch 23 → 27 taken 22 times.
✓ Branch 24 → 25 taken 3 times.
✓ Branch 24 → 27 taken 1 time.
26 if (global && desc->global) {
747 3 desc_set(e, desc, global_ptr(desc, &e->options), true, val);
748 }
749
750 return true;
751 }
752
753 63 bool set_option(EditorState *e, const char *name, const char *value, bool local, bool global, bool quiet)
754 {
755
2/2
✓ Branch 2 → 3 taken 46 times.
✓ Branch 2 → 4 taken 17 times.
63 ErrorBuffer *ebuf = quiet ? NULL : &e->err;
756 63 const OptionDesc *desc = must_find_option(ebuf, name);
757
2/2
✓ Branch 5 → 6 taken 61 times.
✓ Branch 5 → 7 taken 2 times.
63 if (!desc) {
758 return quiet;
759 }
760
761 61 return do_set_option(e, desc, value, local, global, quiet);
762 }
763
764 2 bool set_bool_option(EditorState *e, const char *name, bool local, bool global, bool quiet)
765 {
766
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 1 time.
2 ErrorBuffer *ebuf = quiet ? NULL : &e->err;
767 2 const OptionDesc *desc = must_find_option(ebuf, name);
768
1/2
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 13 not taken.
2 if (!desc) {
769 return quiet;
770 }
771
772
1/2
✓ Branch 6 → 7 taken 2 times.
✗ Branch 6 → 12 not taken.
2 if (desc->type != OPT_BOOL) {
773
3/4
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 11 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 11 not taken.
3 return quiet || error_msg(ebuf, "Option %s is not boolean", desc->name);
774 }
775
776 return do_set_option(e, desc, "true", local, global, quiet);
777 }
778
779 16 static const OptionDesc *find_toggle_option(ErrorBuffer *ebuf, const char *name, bool *global)
780 {
781
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 16 times.
16 if (*global) {
782 return must_find_global_option(ebuf, name);
783 }
784
785 // Toggle local value by default if option has both values
786 16 const OptionDesc *desc = must_find_option(ebuf, name);
787
3/4
✓ Branch 5 → 6 taken 16 times.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 7 times.
✓ Branch 6 → 8 taken 9 times.
16 if (desc && !desc->local) {
788 7 *global = true;
789 }
790 return desc;
791 }
792
793 16 bool toggle_option(EditorState *e, const char *name, bool global, bool verbose)
794 {
795 16 const OptionDesc *desc = find_toggle_option(&e->err, name, &global);
796
1/2
✓ Branch 3 → 4 taken 16 times.
✗ Branch 3 → 20 not taken.
16 if (!desc) {
797 return false;
798 }
799
800 16 char *ptr = get_option_ptr(e, desc, global);
801 16 OptionValue value = desc_get(desc, ptr);
802 16 OptionType type = desc->type;
803
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 10 taken 16 times.
16 if (type == OPT_ENUM) {
804 if (desc->u.enum_opt.values[value.uint_val + 1]) {
805 value.uint_val++;
806 } else {
807 value.uint_val = 0;
808 }
809
2/2
✓ Branch 10 → 11 taken 15 times.
✓ Branch 10 → 12 taken 1 time.
16 } else if (type == OPT_BOOL) {
810 15 value.bool_val = !value.bool_val;
811 } else {
812 1 return error_msg(&e->err, "Toggling %s requires arguments", name);
813 }
814
815 15 desc_set(e, desc, ptr, global, value);
816
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 20 taken 15 times.
15 if (!verbose) {
817 return true;
818 }
819
820 const char *prefix = (global && desc->local) ? "[global] " : "";
821 const char *str = desc_string(desc, value);
822 return info_msg(&e->err, "%s%s = %s", prefix, desc->name, str);
823 }
824
825 bool toggle_option_values (
826 EditorState *e,
827 const char *name,
828 bool global,
829 bool verbose,
830 char **values,
831 size_t count
832 ) {
833 const OptionDesc *desc = find_toggle_option(&e->err, name, &global);
834 if (!desc) {
835 return false;
836 }
837
838 BUG_ON(count == 0);
839 size_t current = 0;
840 bool error = false;
841 char *ptr = get_option_ptr(e, desc, global);
842 OptionValue *parsed_values = xmallocarray(count, sizeof(*parsed_values));
843
844 for (size_t i = 0; i < count; i++) {
845 if (desc_parse(desc, &e->err, values[i], &parsed_values[i])) {
846 if (desc_equals(desc, ptr, parsed_values[i])) {
847 current = i;
848 }
849 } else {
850 error = true;
851 }
852 }
853
854 if (!error) {
855 size_t i = wrapping_increment(current, count);
856 desc_set(e, desc, ptr, global, parsed_values[i]);
857 if (verbose) {
858 const char *prefix = (global && desc->local) ? "[global] " : "";
859 const char *str = desc_string(desc, parsed_values[i]);
860 info_msg(&e->err, "%s%s = %s", prefix, desc->name, str);
861 }
862 }
863
864 free(parsed_values);
865 return !error;
866 }
867
868 143 bool validate_local_options(ErrorBuffer *ebuf, char **strs)
869 {
870 143 size_t invalid = 0;
871
2/2
✓ Branch 17 → 3 taken 206 times.
✓ Branch 17 → 18 taken 143 times.
349 for (size_t i = 0; strs[i]; i += 2) {
872 206 const char *name = strs[i];
873 206 const char *value = strs[i + 1];
874 206 const OptionDesc *desc = must_find_option(ebuf, name);
875
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 205 times.
206 if (unlikely(!desc)) {
876 1 invalid++;
877
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 204 times.
205 } else if (unlikely(!desc->local)) {
878 1 error_msg(ebuf, "%s is not local", name);
879 1 invalid++;
880
2/2
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 12 taken 203 times.
204 } else if (unlikely(desc->on_change == filetype_changed)) {
881 1 error_msg(ebuf, "filetype cannot be set via option command");
882 1 invalid++;
883 } else {
884 203 OptionValue val;
885
2/2
✓ Branch 13 → 14 taken 5 times.
✓ Branch 13 → 15 taken 198 times.
203 if (unlikely(!desc_parse(desc, ebuf, value, &val))) {
886 5 invalid++;
887 }
888 }
889 }
890 143 return !invalid;
891 }
892
893 #if DEBUG_ASSERTIONS_ENABLED
894 1422 static void sanity_check_option_value(const OptionDesc *desc, OptionValue val)
895 {
896 // NOLINTBEGIN(bugprone-assert-side-effect)
897
6/7
✓ Branch 2 → 3 taken 83 times.
✓ Branch 2 → 12 taken 238 times.
✓ Branch 2 → 16 taken 127 times.
✓ Branch 2 → 19 taken 144 times.
✓ Branch 2 → 24 taken 61 times.
✗ Branch 2 → 30 not taken.
✓ Branch 2 → 31 taken 769 times.
1422 switch (desc->type) {
898 83 case OPT_STR:
899 83 BUG_ON(!val.str_val);
900 83 BUG_ON(val.str_val != str_intern(val.str_val));
901
1/2
✓ Branch 8 → 9 taken 83 times.
✗ Branch 8 → 31 not taken.
83 if (desc->u.str_opt.validate) {
902 83 BUG_ON(!desc->u.str_opt.validate(NULL, val.str_val));
903 }
904 return;
905 238 case OPT_UINT:
906 case OPT_UINT8:
907 238 BUG_ON(val.uint_val < desc->u.uint_opt.min);
908 238 BUG_ON(val.uint_val > desc->u.uint_opt.max);
909 return;
910 127 case OPT_ENUM:
911 127 BUG_ON(val.uint_val >= count_enum_values(desc));
912 return;
913 144 case OPT_FLAG: {
914 144 size_t nvals = count_enum_values(desc);
915 144 BUG_ON(nvals >= 32);
916 144 unsigned int mask = (1u << nvals) - 1;
917 144 unsigned int uint_val = val.uint_val;
918 144 BUG_ON((uint_val & mask) != uint_val);
919 }
920 return;
921 61 case OPT_REGEX:
922
2/2
✓ Branch 24 → 25 taken 2 times.
✓ Branch 24 → 31 taken 59 times.
61 if (val.str_val) {
923 2 const InternedRegexp *ir = regexp_intern(NULL, val.str_val);
924 2 BUG_ON(!ir);
925 2 BUG_ON(ir->str != val.str_val);
926 }
927 return;
928 case OPT_BOOL:
929 case OPT_FILESIZE:
930 return;
931 }
932 // NOLINTEND(bugprone-assert-side-effect)
933
934 BUG("unhandled option type");
935 }
936
937 72 static void sanity_check_options(const void *opts, bool global)
938 {
939
2/2
✓ Branch 12 → 3 taken 2736 times.
✓ Branch 12 → 13 taken 72 times.
2808 for (size_t i = 0; i < ARRAYLEN(option_desc); i++) {
940 2736 const OptionDesc *desc = &option_desc[i];
941 2736 BUG_ON(desc->type >= ARRAYLEN(option_ops));
942
6/6
✓ Branch 5 → 6 taken 2520 times.
✓ Branch 5 → 7 taken 216 times.
✓ Branch 6 → 7 taken 1512 times.
✓ Branch 6 → 8 taken 1008 times.
✓ Branch 7 → 8 taken 414 times.
✓ Branch 7 → 11 taken 1314 times.
2736 if ((desc->global && desc->local) || global == desc->global) {
943 1422 OptionValue val = desc_get(desc, (char*)opts + desc->offset);
944 1422 sanity_check_option_value(desc, val);
945 }
946 }
947 72 }
948
949 11 void sanity_check_global_options(const GlobalOptions *gopts)
950 {
951 11 sanity_check_options(gopts, true);
952 11 }
953
954 61 void sanity_check_local_options(const LocalOptions *lopts)
955 {
956 61 sanity_check_options(lopts, false);
957 61 }
958 #endif
959
960 2 void collect_options(PointerArray *a, StringView prefix, bool local, bool global)
961 {
962
2/2
✓ Branch 12 → 3 taken 76 times.
✓ Branch 12 → 13 taken 2 times.
78 for (size_t i = 0; i < ARRAYLEN(option_desc); i++) {
963 76 const OptionDesc *desc = &option_desc[i];
964
2/8
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 76 times.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 7 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 8 taken 76 times.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
76 if ((local && !desc->local) || (global && !desc->global)) {
965 continue;
966 }
967
2/2
✓ Branch 8 → 9 taken 39 times.
✓ Branch 8 → 11 taken 37 times.
76 if (str_has_sv_prefix(desc->name, prefix)) {
968 39 ptr_array_append(a, xstrdup(desc->name));
969 }
970 }
971 2 }
972
973 // Collect options that can be set via the "option" command
974 1 void collect_auto_options(PointerArray *a, StringView prefix)
975 {
976
2/2
✓ Branch 10 → 3 taken 38 times.
✓ Branch 10 → 11 taken 1 time.
39 for (size_t i = 0; i < ARRAYLEN(option_desc); i++) {
977 38 const OptionDesc *desc = &option_desc[i];
978
4/4
✓ Branch 3 → 4 taken 17 times.
✓ Branch 3 → 5 taken 21 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 16 times.
38 if (!desc->local || desc->on_change == filetype_changed) {
979 22 continue;
980 }
981
1/2
✓ Branch 6 → 7 taken 16 times.
✗ Branch 6 → 9 not taken.
16 if (str_has_sv_prefix(desc->name, prefix)) {
982 16 ptr_array_append(a, xstrdup(desc->name));
983 }
984 }
985 1 }
986
987 2 void collect_toggleable_options(PointerArray *a, StringView prefix, bool global)
988 {
989
2/2
✓ Branch 11 → 3 taken 76 times.
✓ Branch 11 → 12 taken 2 times.
78 for (size_t i = 0; i < ARRAYLEN(option_desc); i++) {
990 76 const OptionDesc *desc = &option_desc[i];
991
1/4
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 76 times.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
76 if (global && !desc->global) {
992 continue;
993 }
994 76 OptionType type = desc->type;
995 76 bool toggleable = (type == OPT_ENUM || type == OPT_BOOL);
996
4/4
✓ Branch 6 → 7 taken 48 times.
✓ Branch 6 → 10 taken 28 times.
✓ Branch 7 → 8 taken 25 times.
✓ Branch 7 → 10 taken 23 times.
76 if (toggleable && str_has_sv_prefix(desc->name, prefix)) {
997 25 ptr_array_append(a, xstrdup(desc->name));
998 }
999 }
1000 2 }
1001
1002 4 static void collect_enum_option_values (
1003 const char *const *values,
1004 PointerArray *a,
1005 StringView prefix
1006 ) {
1007
2/2
✓ Branch 7 → 3 taken 10 times.
✓ Branch 7 → 8 taken 4 times.
14 for (size_t i = 0; values[i]; i++) {
1008
2/2
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 6 taken 3 times.
10 if (str_has_sv_prefix(values[i], prefix)) {
1009 7 ptr_array_append(a, xstrdup(values[i]));
1010 }
1011 }
1012 4 }
1013
1014 2 static void collect_flag_option_values (
1015 const char *const *values,
1016 PointerArray *a,
1017 StringView prefix
1018 ) {
1019 2 ssize_t comma_idx = strview_memrchr_idx(prefix, ',');
1020 2 size_t tail_idx = (comma_idx < 0) ? 0 : comma_idx + 1;
1021 2 StringView tail = strview_from_slice(prefix.data, tail_idx, prefix.length);
1022
1023
2/2
✓ Branch 9 → 4 taken 16 times.
✓ Branch 9 → 10 taken 2 times.
20 for (size_t i = 0; values[i]; i++) {
1024 16 StringView val = strview(values[i]);
1025
2/2
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 8 taken 13 times.
16 if (strview_has_sv_prefix(val, tail)) {
1026 3 ptr_array_append(a, xmemjoin(prefix.data, tail_idx, val.data, val.length + 1));
1027 }
1028 }
1029 2 }
1030
1031 6 void collect_option_values (
1032 EditorState *e,
1033 PointerArray *a,
1034 const char *option,
1035 StringView prefix
1036 ) {
1037 6 const OptionDesc *desc = find_option(option);
1038
1/2
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 12 not taken.
6 if (!desc) {
1039 return;
1040 }
1041
1042
2/5
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 4 times.
✓ Branch 4 → 8 taken 2 times.
✗ Branch 4 → 10 not taken.
✗ Branch 4 → 12 not taken.
6 switch (desc->type) {
1043 case OPT_UINT:
1044 case OPT_UINT8:
1045 case OPT_REGEX:
1046 case OPT_FILESIZE:
1047 // Not enumerated; nothing to collect
1048 return;
1049 case OPT_STR:
1050 if (desc->on_change == filetype_changed) {
1051 collect_ft(&e->filetypes, a, prefix);
1052 }
1053 return;
1054 4 case OPT_ENUM:
1055 case OPT_BOOL:
1056 4 collect_enum_option_values(desc->u.enum_opt.values, a, prefix);
1057 4 return;
1058 2 case OPT_FLAG:
1059 2 collect_flag_option_values(desc->u.enum_opt.values, a, prefix);
1060 2 return;
1061 }
1062
1063 BUG("unhandled OptionType value");
1064 }
1065
1066 76 static void append_option(String *s, const OptionDesc *desc, void *ptr)
1067 {
1068 76 const OptionValue value = desc_get(desc, ptr);
1069 76 const char *value_str = desc_string(desc, value);
1070
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 76 times.
76 if (unlikely(value_str[0] == '-')) {
1071 string_append_literal(s, "-- ");
1072 }
1073 76 string_append_cstring(s, desc->name);
1074 76 string_append_byte(s, ' ');
1075 76 string_append_escaped_arg(s, value_str, true);
1076 76 string_append_byte(s, '\n');
1077 76 }
1078
1079 2 String dump_options(GlobalOptions *gopts, LocalOptions *lopts)
1080 {
1081 2 String buf = string_new(4096);
1082
2/2
✓ Branch 24 → 4 taken 76 times.
✓ Branch 24 → 25 taken 2 times.
80 for (size_t i = 0; i < ARRAYLEN(option_desc); i++) {
1083 76 const OptionDesc *desc = &option_desc[i];
1084
2/2
✓ Branch 4 → 5 taken 34 times.
✓ Branch 4 → 6 taken 42 times.
76 void *local = desc->local ? local_ptr(desc, lopts) : NULL;
1085
2/2
✓ Branch 6 → 7 taken 70 times.
✓ Branch 6 → 8 taken 6 times.
76 void *global = desc->global ? global_ptr(desc, gopts) : NULL;
1086
2/2
✓ Branch 8 → 9 taken 28 times.
✓ Branch 8 → 19 taken 48 times.
76 if (local && global) {
1087 28 const OptionValue global_value = desc_get(desc, global);
1088
1/2
✓ Branch 11 → 12 taken 28 times.
✗ Branch 11 → 14 not taken.
28 if (desc_equals(desc, local, global_value)) {
1089 28 string_append_literal(&buf, "set ");
1090 28 append_option(&buf, desc, local);
1091 } else {
1092 string_append_literal(&buf, "set -g ");
1093 append_option(&buf, desc, global);
1094 string_append_literal(&buf, "set -l ");
1095 append_option(&buf, desc, local);
1096 }
1097 } else {
1098 48 string_append_literal(&buf, "set ");
1099
2/2
✓ Branch 20 → 21 taken 42 times.
✓ Branch 20 → 22 taken 6 times.
90 append_option(&buf, desc, local ? local : global);
1100 }
1101 }
1102 2 return buf;
1103 }
1104
1105 3 const char *get_option_value_string(EditorState *e, const char *name)
1106 {
1107 3 const OptionDesc *desc = find_option(name);
1108
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 7 taken 1 time.
3 if (!desc) {
1109 return NULL;
1110 }
1111 2 char *ptr = get_option_ptr(e, desc, !desc->local);
1112 2 return desc_string(desc, desc_get(desc, ptr));
1113 }
1114