dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 84.8% 420 / 5 / 500
Functions: 91.2% 62 / 0 / 68
Branches: 71.8% 199 / 82 / 359

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