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