dte test coverage


Directory: ./
File: src/options.c
Date: 2025-10-16 19:09:21
Exec Total Coverage
Lines: 419 487 86.0%
Functions: 61 66 92.4%
Branches: 182 257 70.8%

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