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