dte test coverage


Directory: ./
File: src/options.c
Date: 2025-02-14 16:55:22
Exec Total Coverage
Lines: 407 491 82.9%
Functions: 60 66 90.9%
Branches: 186 273 68.1%

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