dte test coverage


Directory: ./
File: src/options.c
Date: 2025-09-07 23:01:39
Exec Total Coverage
Lines: 427 495 86.3%
Functions: 66 71 93.0%
Branches: 180 255 70.6%

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