dte test coverage


Directory: ./
File: src/options.c
Date: 2025-07-13 15:27:15
Exec Total Coverage
Lines: 408 476 85.7%
Functions: 61 66 92.4%
Branches: 184 259 71.0%

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