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