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