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 |