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