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