dte test coverage


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