Respect the XDG standard for configuration files
[tig.git] / src / options.c
blob8b02a8599ba55720b967eca05d243f7faa30038e
1 /* Copyright (c) 2006-2015 Jonas Fonseca <jonas.fonseca@gmail.com>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #include "tig/tig.h"
15 #include "tig/types.h"
16 #include "tig/argv.h"
17 #include "tig/io.h"
18 #include "tig/repo.h"
19 #include "tig/refdb.h"
20 #include "tig/options.h"
21 #include "tig/request.h"
22 #include "tig/line.h"
23 #include "tig/keys.h"
24 #include "tig/view.h"
27 * Option variables.
30 #define DEFINE_OPTION_VARIABLES(name, type, flags) type opt_##name;
31 OPTION_INFO(DEFINE_OPTION_VARIABLES)
33 static struct option_info option_info[] = {
34 #define DEFINE_OPTION_INFO(name, type, flags) { #name, STRING_SIZE(#name), #type, &opt_##name },
35 OPTION_INFO(DEFINE_OPTION_INFO)
38 struct option_info *
39 find_option_info(struct option_info *option, size_t options, const char *prefix, const char *name)
41 size_t namelen = strlen(name);
42 char prefixed[SIZEOF_STR];
43 int i;
45 if (*prefix && namelen == strlen(prefix) &&
46 !string_enum_compare(prefix, name, namelen)) {
47 name = "display";
48 namelen = strlen(name);
51 for (i = 0; i < options; i++) {
52 if (!strcmp(option[i].type, "view_settings") &&
53 enum_equals_prefix(option[i], name, namelen))
54 return &option[i];
56 if (enum_equals(option[i], name, namelen))
57 return &option[i];
59 if (enum_name_prefixed(prefixed, sizeof(prefixed), prefix, option[i].name) &&
60 namelen == strlen(prefixed) &&
61 !string_enum_compare(prefixed, name, namelen))
62 return &option[i];
65 return NULL;
68 static struct option_info *
69 find_option_info_by_value(void *value)
71 int i;
73 for (i = 0; i < ARRAY_SIZE(option_info); i++)
74 if (option_info[i].value == value)
75 return &option_info[i];
77 return NULL;
80 static void
81 mark_option_seen(void *value)
83 struct option_info *option = find_option_info_by_value(value);
85 if (option)
86 option->seen = true;
89 struct option_info *
90 find_column_option_info(enum view_column_type type, union view_column_options *opts,
91 const char *option, struct option_info *column_info,
92 const char **column_name)
94 #define DEFINE_COLUMN_OPTION_INFO(name, type, flags) \
95 { #name, STRING_SIZE(#name), #type, &opt->name, flags },
97 #define DEFINE_COLUMN_OPTION_INFO_CHECK(name, id, options) \
98 if (type == VIEW_COLUMN_##id) { \
99 struct name##_options *opt = &opts->name; \
100 struct option_info info[] = { \
101 options(DEFINE_COLUMN_OPTION_INFO) \
102 }; \
103 struct option_info *match; \
104 match = find_option_info(info, ARRAY_SIZE(info), #name, option); \
105 if (match) { \
106 *column_info = *match; \
107 *column_name = #name; \
108 return column_info; \
112 COLUMN_OPTIONS(DEFINE_COLUMN_OPTION_INFO_CHECK);
114 *column_name = NULL;
115 return NULL;
119 * State variables.
122 iconv_t opt_iconv_out = ICONV_NONE;
123 char opt_editor[SIZEOF_STR] = "";
124 const char **opt_cmdline_args = NULL;
127 * Mapping between options and command argument mapping.
130 const char *
131 diff_context_arg()
133 static char opt_diff_context_arg[9] = "";
135 if (opt_diff_context < 0 ||
136 !string_format(opt_diff_context_arg, "-U%u", opt_diff_context))
137 return "";
139 return opt_diff_context_arg;
142 const char *
143 use_mailmap_arg()
145 return opt_mailmap ? "--use-mailmap" : "";
148 const char *
149 log_custom_pretty_arg(void)
151 return opt_mailmap
152 ? "--pretty=format:commit %m %H %P%x00%aN <%aE> %ad%x00%s"
153 : "--pretty=format:commit %m %H %P%x00%an <%ae> %ad%x00%s";
156 #define ENUM_ARG(enum_name, arg_string) ENUM_MAP_ENTRY(arg_string, enum_name)
158 static const struct enum_map_entry ignore_space_arg_map[] = {
159 ENUM_ARG(IGNORE_SPACE_NO, ""),
160 ENUM_ARG(IGNORE_SPACE_ALL, "--ignore-all-space"),
161 ENUM_ARG(IGNORE_SPACE_SOME, "--ignore-space-change"),
162 ENUM_ARG(IGNORE_SPACE_AT_EOL, "--ignore-space-at-eol"),
165 const char *
166 ignore_space_arg()
168 return ignore_space_arg_map[opt_ignore_space].name;
171 static const struct enum_map_entry commit_order_arg_map[] = {
172 ENUM_ARG(COMMIT_ORDER_AUTO, ""),
173 ENUM_ARG(COMMIT_ORDER_DEFAULT, ""),
174 ENUM_ARG(COMMIT_ORDER_TOPO, "--topo-order"),
175 ENUM_ARG(COMMIT_ORDER_DATE, "--date-order"),
176 ENUM_ARG(COMMIT_ORDER_AUTHOR_DATE, "--author-date-order"),
177 ENUM_ARG(COMMIT_ORDER_REVERSE, "--reverse"),
180 const char *
181 commit_order_arg()
183 return commit_order_arg_map[opt_commit_order].name;
186 const char *
187 commit_order_arg_with_graph(enum graph_display graph_display)
189 enum commit_order commit_order = opt_commit_order;
191 if (commit_order == COMMIT_ORDER_AUTO &&
192 graph_display != GRAPH_DISPLAY_NO)
193 commit_order = COMMIT_ORDER_TOPO;
195 return commit_order_arg_map[commit_order].name;
198 /* Use --show-notes to support Git >= 1.7.6 */
199 #define NOTES_ARG "--show-notes"
200 #define NOTES_EQ_ARG NOTES_ARG "="
202 static char opt_notes_arg[SIZEOF_STR] = NOTES_ARG;
204 const char *
205 show_notes_arg()
207 if (opt_show_notes)
208 return opt_notes_arg;
209 /* Notes are disabled by default when passing --pretty args. */
210 return "";
213 void
214 update_options_from_argv(const char *argv[])
216 int next, flags_pos;
218 for (next = flags_pos = 0; argv[next]; next++) {
219 const char *flag = argv[next];
220 int value = -1;
222 if (map_enum(&value, commit_order_arg_map, flag)) {
223 opt_commit_order = value;
224 mark_option_seen(&opt_commit_order);
225 continue;
228 if (map_enum(&value, ignore_space_arg_map, flag)) {
229 opt_ignore_space = value;
230 mark_option_seen(&opt_ignore_space);
231 continue;
234 if (!strcmp(flag, "--no-notes")) {
235 opt_show_notes = false;
236 mark_option_seen(&opt_show_notes);
237 continue;
240 if (!prefixcmp(flag, "--show-notes") ||
241 !prefixcmp(flag, "--notes")) {
242 opt_show_notes = true;
243 string_ncopy(opt_notes_arg, flag, strlen(flag));
244 mark_option_seen(&opt_show_notes);
245 continue;
248 if (!prefixcmp(flag, "-U")
249 && parse_int(&value, flag + 2, 0, 999999) == SUCCESS) {
250 opt_diff_context = value;
251 mark_option_seen(&opt_diff_context);
252 continue;
255 argv[flags_pos++] = flag;
258 argv[flags_pos] = NULL;
262 * User config file handling.
265 static const struct enum_map_entry color_map[] = {
266 #define COLOR_MAP(name) ENUM_MAP_ENTRY(#name, COLOR_##name)
267 COLOR_MAP(DEFAULT),
268 COLOR_MAP(BLACK),
269 COLOR_MAP(BLUE),
270 COLOR_MAP(CYAN),
271 COLOR_MAP(GREEN),
272 COLOR_MAP(MAGENTA),
273 COLOR_MAP(RED),
274 COLOR_MAP(WHITE),
275 COLOR_MAP(YELLOW),
278 static const struct enum_map_entry attr_map[] = {
279 #define ATTR_MAP(name) ENUM_MAP_ENTRY(#name, A_##name)
280 ATTR_MAP(NORMAL),
281 ATTR_MAP(BLINK),
282 ATTR_MAP(BOLD),
283 ATTR_MAP(DIM),
284 ATTR_MAP(REVERSE),
285 ATTR_MAP(STANDOUT),
286 ATTR_MAP(UNDERLINE),
289 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
291 enum status_code
292 parse_step(double *opt, const char *arg)
294 int value = atoi(arg);
296 if (!value && !isdigit(*arg))
297 return error("Invalid double or percentage");
299 *opt = value;
300 if (!strchr(arg, '%'))
301 return SUCCESS;
303 /* "Shift down" so 100% and 1 does not conflict. */
304 *opt /= 100;
305 if (*opt >= 1.0) {
306 *opt = 0.99;
307 return error("Percentage is larger than 100%%");
309 if (*opt < 0.0) {
310 *opt = 1;
311 return error("Percentage is less than 0%%");
313 return SUCCESS;
316 enum status_code
317 parse_int(int *opt, const char *arg, int min, int max)
319 int value = atoi(arg);
321 if (min <= value && value <= max) {
322 *opt = value;
323 return SUCCESS;
326 return error("Value must be between %d and %d", min, max);
329 static bool
330 set_color(int *color, const char *name)
332 if (map_enum(color, color_map, name))
333 return true;
334 /* Git expects a plain int w/o prefix, however, color<int> is
335 * the preferred Tig color notation. */
336 if (!prefixcmp(name, "color"))
337 name += 5;
338 return string_isnumber(name) &&
339 parse_int(color, name, 0, 255) == SUCCESS;
342 #define is_quoted(c) ((c) == '"' || (c) == '\'')
344 static enum status_code
345 parse_color_name(const char *color, struct line_rule *rule, const char **prefix_ptr)
347 const char *prefixend = is_quoted(*color) ? NULL : strchr(color, '.');
349 if (prefixend) {
350 struct keymap *keymap = get_keymap(color, prefixend - color);
352 if (!keymap)
353 return error("Unknown key map: %.*s", (int) (prefixend - color), color);
354 if (prefix_ptr)
355 *prefix_ptr = keymap->name;
356 color = prefixend + 1;
359 memset(rule, 0, sizeof(*rule));
360 if (is_quoted(*color)) {
361 rule->line = color + 1;
362 rule->linelen = strlen(color) - 2;
363 } else {
364 rule->name = color;
365 rule->namelen = strlen(color);
368 return SUCCESS;
371 static int
372 find_remapped(const char *remapped[][2], size_t remapped_size, const char *arg)
374 size_t arglen = strlen(arg);
375 int i;
377 for (i = 0; i < remapped_size; i++) {
378 const char *name = remapped[i][0];
379 size_t namelen = strlen(name);
381 if (arglen == namelen &&
382 !string_enum_compare(arg, name, namelen))
383 return i;
386 return -1;
389 /* Wants: object fgcolor bgcolor [attribute] */
390 static enum status_code
391 option_color_command(int argc, const char *argv[])
393 struct line_rule rule = {0};
394 const char *prefix = NULL;
395 struct line_info *info;
396 enum status_code code;
398 if (argc < 3)
399 return error("Invalid color mapping: color area fgcolor bgcolor [attrs]");
401 code = parse_color_name(argv[0], &rule, &prefix);
402 if (code != SUCCESS)
403 return code;
405 info = add_line_rule(prefix, &rule);
406 if (!info) {
407 static const char *obsolete[][2] = {
408 { "acked", "' Acked-by'" },
409 { "diff-copy-from", "'copy from '" },
410 { "diff-copy-to", "'copy to '" },
411 { "diff-deleted-file-mode", "'deleted file mode '" },
412 { "diff-dissimilarity", "'dissimilarity '" },
413 { "diff-rename-from", "'rename from '" },
414 { "diff-rename-to", "'rename to '" },
415 { "diff-tree", "'diff-tree '" },
416 { "filename", "file" },
417 { "help-keymap", "help.section" },
418 { "main-revgraph", "" },
419 { "pp-adate", "'AuthorDate: '" },
420 { "pp-author", "'Author: '" },
421 { "pp-cdate", "'CommitDate: '" },
422 { "pp-commit", "'Commit: '" },
423 { "pp-date", "'Date: '" },
424 { "reviewed", "' Reviewed-by'" },
425 { "signoff", "' Signed-off-by'" },
426 { "stat-head", "status.header" },
427 { "stat-section", "status.section" },
428 { "tested", "' Tested-by'" },
429 { "tree-dir", "tree.directory" },
430 { "tree-file", "tree.file" },
431 { "tree-head", "tree.header" },
433 int index;
435 index = find_remapped(obsolete, ARRAY_SIZE(obsolete), rule.name);
436 if (index != -1) {
437 if (!*obsolete[index][1])
438 return error("%s is obsolete", argv[0]);
439 /* Keep the initial prefix if defined. */
440 code = parse_color_name(obsolete[index][1], &rule, prefix ? NULL : &prefix);
441 if (code != SUCCESS)
442 return code;
443 info = add_line_rule(prefix, &rule);
446 if (!info)
447 return error("Unknown color name: %s", argv[0]);
449 code = error("%s has been replaced by %s",
450 obsolete[index][0], obsolete[index][1]);
453 if (!set_color(&info->fg, argv[1]))
454 return error("Unknown color: %s", argv[1]);
456 if (!set_color(&info->bg, argv[2]))
457 return error("Unknown color: %s", argv[2]);
459 info->attr = 0;
460 while (argc-- > 3) {
461 int attr;
463 if (!set_attribute(&attr, argv[argc]))
464 return error("Unknown color attribute: %s", argv[argc]);
465 info->attr |= attr;
468 return code;
471 static enum status_code
472 parse_bool(bool *opt, const char *arg)
474 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
475 ? true : false;
476 if (*opt || !strcmp(arg, "0") || !strcmp(arg, "false") || !strcmp(arg, "no"))
477 return SUCCESS;
478 return error("Non-boolean value treated as false: %s", arg);
481 static enum status_code
482 parse_enum(const char *name, unsigned int *opt, const char *arg,
483 const struct enum_map *map)
485 bool is_true;
486 enum status_code code;
488 assert(map->size > 1);
490 if (map_enum_do(map->entries, map->size, (int *) opt, arg))
491 return SUCCESS;
493 code = parse_bool(&is_true, arg);
494 *opt = is_true ? map->entries[1].value : map->entries[0].value;
495 if (code == SUCCESS)
496 return code;
498 if (!strcmp(name, "date-display")) {
499 const char *msg = "";
501 if (!strcasecmp(arg, "local"))
502 msg = ", use the 'date-local' column option";
503 else if (!strcasecmp(arg, "short"))
504 msg = ", use the 'custom' display mode and set 'date-format'";
506 *opt = map->entries[1].value;
507 return error("'%s' is no longer supported for %s%s", arg, name, msg);
510 return error("'%s' is not a valid value for %s; using %s",
511 arg, name, enum_name(map->entries[*opt].name));
514 static enum status_code
515 parse_string(char *opt, const char *arg, size_t optsize)
517 int arglen = strlen(arg);
519 switch (arg[0]) {
520 case '\"':
521 case '\'':
522 if (arglen == 1 || arg[arglen - 1] != arg[0])
523 return ERROR_UNMATCHED_QUOTATION;
524 arg += 1; arglen -= 2;
525 default:
526 string_ncopy_do(opt, optsize, arg, arglen);
527 return SUCCESS;
531 static enum status_code
532 parse_encoding(struct encoding **encoding_ref, const char *arg, bool priority)
534 char buf[SIZEOF_STR];
535 enum status_code code = parse_string(buf, arg, sizeof(buf));
537 if (code == SUCCESS) {
538 struct encoding *encoding = *encoding_ref;
540 if (encoding && !priority)
541 return code;
542 encoding = encoding_open(buf);
543 if (encoding)
544 *encoding_ref = encoding;
547 return code;
550 static enum status_code
551 parse_args(const char ***args, const char *argv[])
553 if (!argv_copy(args, argv))
554 return ERROR_OUT_OF_MEMORY;
555 return SUCCESS;
558 enum status_code
559 parse_option(struct option_info *option, const char *prefix, const char *arg)
561 char name[SIZEOF_STR];
563 if (!enum_name_prefixed(name, sizeof(name), prefix, option->name))
564 return error("Failed to parse option");
566 if (!strcmp("show-notes", name)) {
567 bool *value = option->value;
568 enum status_code res;
570 if (parse_bool(option->value, arg) == SUCCESS)
571 return SUCCESS;
573 *value = true;
574 string_copy(opt_notes_arg, NOTES_EQ_ARG);
575 res = parse_string(opt_notes_arg + STRING_SIZE(NOTES_EQ_ARG), arg,
576 sizeof(opt_notes_arg) - STRING_SIZE(NOTES_EQ_ARG));
577 if (res == SUCCESS && !opt_notes_arg[STRING_SIZE(NOTES_EQ_ARG)])
578 opt_notes_arg[STRING_SIZE(NOTES_ARG)] = 0;
579 return res;
582 if (!strcmp(option->type, "bool"))
583 return parse_bool(option->value, arg);
585 if (!strcmp(option->type, "double"))
586 return parse_step(option->value, arg);
588 if (!strncmp(option->type, "enum", 4)) {
589 const char *type = option->type + STRING_SIZE("enum ");
590 const struct enum_map *map = find_enum_map(type);
592 return parse_enum(name, option->value, arg, map);
595 if (!strcmp(option->type, "int")) {
596 if (strstr(name, "title-overflow")) {
597 bool enabled = false;
598 int *value = option->value;
600 /* We try to parse it as a boolean (and set the
601 * value to 0 if fale), otherwise we parse it as
602 * an integer and use the given value. */
603 if (parse_bool(&enabled, arg) == SUCCESS) {
604 if (!enabled) {
605 *value = 0;
606 return SUCCESS;
608 arg = "50";
612 if (!strcmp(name, "line-number-interval") ||
613 !strcmp(name, "tab-size"))
614 return parse_int(option->value, arg, 1, 1024);
615 else if (!strcmp(name, "id-width"))
616 return parse_int(option->value, arg, 0, SIZEOF_REV - 1);
617 else
618 return parse_int(option->value, arg, 0, 1024);
621 if (!strcmp(option->type, "const char *")) {
622 const char *alloc = NULL;
623 const char **value = option->value;
625 if (strlen(arg)) {
626 if (arg[0] == '"' && arg[strlen(arg) - 1] == '"')
627 alloc = strndup(arg + 1, strlen(arg + 1) - 1);
628 else
629 alloc = strdup(arg);
630 if (!alloc)
631 return ERROR_OUT_OF_MEMORY;
634 free((void *) *value);
635 *value = alloc;
636 return SUCCESS;
639 return error("Unhandled option: %s", name);
642 static enum status_code
643 parse_view_settings(struct view_column **view_column, const char *name_, const char *argv[])
645 char buf[SIZEOF_STR];
646 const char *name = enum_name_copy(buf, sizeof(buf), name_) ? buf : name_;
647 const char *prefixed;
649 if ((prefixed = strstr(name, "-view-"))) {
650 const char *column_name = prefixed + STRING_SIZE("-view-");
651 size_t column_namelen = strlen(column_name);
652 enum view_column_type type;
654 for (type = 0; type < view_column_type_map->size; type++) {
655 const struct enum_map_entry *column = &view_column_type_map->entries[type];
657 if (enum_equals(*column, column_name, column_namelen))
658 return parse_view_column_config(name, type, NULL, argv);
660 if (enum_equals_prefix(*column, column_name, column_namelen))
661 return parse_view_column_config(name, type,
662 column_name + column->namelen + 1,
663 argv);
667 return parse_view_config(view_column, name, argv);
670 /* Wants: name = value */
671 static enum status_code
672 option_set_command(int argc, const char *argv[])
674 struct option_info *option;
675 enum status_code code;
677 if (argc < 2)
678 return error("Invalid set command: set option = value");
680 if (strcmp(argv[1], "="))
681 return error("No value assigned to %s", argv[0]);
683 option = find_option_info(option_info, ARRAY_SIZE(option_info), "", argv[0]);
684 if (option) {
685 if (option->seen)
686 return SUCCESS;
688 if (!strcmp(option->type, "const char **"))
689 return parse_args(option->value, argv + 2);
691 if (argc < 3)
692 return error("Invalid set command: set option = value");
694 if (!strcmp(option->type, "view_settings"))
695 return parse_view_settings(option->value, argv[0], argv + 2);
697 if (!strcmp(option->type, "struct ref_format **"))
698 return parse_ref_formats(option->value, argv + 2);
700 code = parse_option(option, "", argv[2]);
701 if (code == SUCCESS && argc != 3)
702 return error("Option %s only takes one value", argv[0]);
704 return code;
709 const char *obsolete[][2] = {
710 { "author-width", "author" },
711 { "filename-width", "file-name" },
712 { "line-number-interval", "line-number" },
713 { "show-author", "author" },
714 { "show-date", "date" },
715 { "show-file-size", "file-size" },
716 { "show-filename", "file-name" },
717 { "show-id", "id" },
718 { "show-line-numbers", "line-number" },
719 { "show-refs", "commit-title" },
720 { "show-rev-graph", "commit-title" },
721 { "title-overflow", "commit-title and text" },
723 int index = find_remapped(obsolete, ARRAY_SIZE(obsolete), argv[0]);
725 if (index != -1)
726 return error("%s is obsolete; see tigrc(5) for how to set the %s column option",
727 obsolete[index][0], obsolete[index][1]);
729 if (!strcmp(argv[0], "read-git-colors"))
730 return error("read-git-colors has been obsoleted by the git-colors option");
732 if (!strcmp(argv[0], "cmdline-args"))
733 return error("cmdline-args is obsolete; use view-specific options instead, e.g. main-options");
736 return error("Unknown option name: %s", argv[0]);
739 /* Wants: mode request key */
740 static enum status_code
741 option_bind_command(int argc, const char *argv[])
743 struct key key[16];
744 size_t keys = 0;
745 enum request request;
746 struct keymap *keymap;
747 const char *key_arg;
749 if (argc < 3)
750 return error("Invalid key binding: bind keymap key action");
752 if (!(keymap = get_keymap(argv[0], strlen(argv[0])))) {
753 if (!strcmp(argv[0], "branch"))
754 keymap = get_keymap("refs", strlen("refs"));
755 if (!keymap)
756 return error("Unknown key map: %s", argv[0]);
759 for (keys = 0, key_arg = argv[1]; *key_arg && keys < ARRAY_SIZE(key); keys++) {
760 enum status_code code = get_key_value(&key_arg, &key[keys]);
762 if (code != SUCCESS)
763 return code;
766 if (*key_arg && keys == ARRAY_SIZE(key))
767 return error("Except for <Esc> combos only one key is allowed "
768 "in key combos: %s", argv[1]);
770 request = get_request(argv[2]);
771 if (request == REQ_UNKNOWN) {
772 static const char *obsolete[][2] = {
773 { "view-branch", "view-refs" },
775 static const char *toggles[][2] = {
776 { "diff-context-down", "diff-context" },
777 { "diff-context-up", "diff-context" },
778 { "stage-next", ":/^@@" },
779 { "toggle-author", "author" },
780 { "toggle-changes", "show-changes" },
781 { "toggle-commit-order", "show-commit-order" },
782 { "toggle-date", "date" },
783 { "toggle-files", "file-filter" },
784 { "toggle-file-filter", "file-filter" },
785 { "toggle-file-size", "file-size" },
786 { "toggle-filename", "filename" },
787 { "toggle-graphic", "show-graphic" },
788 { "toggle-id", "id" },
789 { "toggle-ignore-space", "show-ignore-space" },
790 { "toggle-lineno", "line-number" },
791 { "toggle-refs", "commit-title-refs" },
792 { "toggle-rev-graph", "commit-title-graph" },
793 { "toggle-show-changes", "show-changes" },
794 { "toggle-sort-field", "sort-field" },
795 { "toggle-sort-order", "sort-order" },
796 { "toggle-title-overflow", "commit-title-overflow" },
797 { "toggle-untracked-dirs", "status-untracked-dirs" },
798 { "toggle-vertical-split", "show-vertical-split" },
800 int alias;
802 alias = find_remapped(obsolete, ARRAY_SIZE(obsolete), argv[2]);
803 if (alias != -1) {
804 const char *action = obsolete[alias][1];
806 add_keybinding(keymap, get_request(action), key, keys);
807 return error("%s has been renamed to %s",
808 obsolete[alias][0], action);
811 alias = find_remapped(toggles, ARRAY_SIZE(toggles), argv[2]);
812 if (alias != -1) {
813 const char *action = toggles[alias][0];
814 const char *arg = prefixcmp(action, "diff-context-")
815 ? NULL : (strstr(action, "-down") ? "-1" : "+1");
816 const char *mapped = toggles[alias][1];
817 const char *toggle[] = { ":toggle", mapped, arg, NULL};
818 const char *other[] = { mapped, NULL };
819 const char **prompt = *mapped == ':' ? other : toggle;
820 enum status_code code = add_run_request(keymap, key, keys, prompt);
822 if (code == SUCCESS)
823 code = error("%s has been replaced by `%s%s%s%s'",
824 action, prompt == other ? mapped : ":toggle ",
825 prompt == other ? "" : mapped,
826 arg ? " " : "", arg ? arg : "");
827 return code;
831 if (request == REQ_UNKNOWN)
832 return add_run_request(keymap, key, keys, argv + 2);
834 return add_keybinding(keymap, request, key, keys);
838 static enum status_code load_option_file(const char *path);
840 static enum status_code
841 option_source_command(int argc, const char *argv[])
843 enum status_code code;
845 if (argc < 1)
846 return error("Invalid source command: source path");
848 code = load_option_file(argv[0]);
850 return code == ERROR_FILE_DOES_NOT_EXIST
851 ? error("File does not exist: %s", argv[0]) : code;
854 enum status_code
855 set_option(const char *opt, int argc, const char *argv[])
857 if (!strcmp(opt, "color"))
858 return option_color_command(argc, argv);
860 if (!strcmp(opt, "set"))
861 return option_set_command(argc, argv);
863 if (!strcmp(opt, "bind"))
864 return option_bind_command(argc, argv);
866 if (!strcmp(opt, "source"))
867 return option_source_command(argc, argv);
869 return error("Unknown option command: %s", opt);
872 struct config_state {
873 const char *path;
874 size_t lineno;
875 bool errors;
878 static enum status_code
879 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
881 struct config_state *config = data;
882 enum status_code status = ERROR_NO_OPTION_VALUE;
884 /* Check for comment markers, since read_properties() will
885 * only ensure opt and value are split at first " \t". */
886 optlen = strcspn(opt, "#");
887 if (optlen == 0)
888 return SUCCESS;
890 if (opt[optlen] == 0) {
891 /* Look for comment endings in the value. */
892 size_t len = strcspn(value, "#");
893 const char *argv[SIZEOF_ARG];
894 int argc = 0;
896 if (len < valuelen) {
897 valuelen = len;
898 value[valuelen] = 0;
901 if (!argv_from_string(argv, &argc, value))
902 status = error("Too many option arguments for %s", opt);
903 else
904 status = set_option(opt, argc, argv);
907 if (status != SUCCESS) {
908 warn("%s:%zu: %s", config->path, config->lineno,
909 get_status_message(status));
910 config->errors = true;
913 /* Always keep going if errors are encountered. */
914 return SUCCESS;
917 static enum status_code
918 load_option_file(const char *path)
920 struct config_state config = { path, 0, false };
921 struct io io;
922 char buf[SIZEOF_STR];
924 /* Do not read configuration from stdin if set to "" */
925 if (!path || !strlen(path))
926 return SUCCESS;
928 if (!prefixcmp(path, "~/")) {
929 const char *home = getenv("HOME");
931 if (!home || !string_format(buf, "%s/%s", home, path + 2))
932 return error("Failed to expand ~ to user home directory");
933 path = buf;
936 /* It's OK that the file doesn't exist. */
937 if (!io_open(&io, "%s", path)) {
938 /* XXX: Must return ERROR_FILE_DOES_NOT_EXIST so missing
939 * system tigrc is detected properly. */
940 if (io_error(&io) == ENOENT)
941 return ERROR_FILE_DOES_NOT_EXIST;
942 return error("Error loading file %s: %s", path, io_strerror(&io));
945 if (io_load_span(&io, " \t", &config.lineno, read_option, &config) != SUCCESS ||
946 config.errors == true)
947 warn("Errors while loading %s.", path);
948 return SUCCESS;
951 extern const char *builtin_config;
953 enum status_code
954 load_options(void)
956 const char *tigrc_user = getenv("TIGRC_USER");
957 const char *tigrc_system = getenv("TIGRC_SYSTEM");
958 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
959 const bool diff_opts_from_args = !!opt_diff_options;
960 bool custom_tigrc_system = !!tigrc_system;
961 char buf[SIZEOF_STR];
963 opt_file_filter = true;
964 if (!find_option_info_by_value(&opt_diff_context)->seen)
965 opt_diff_context = -3;
967 if (!custom_tigrc_system)
968 tigrc_system = SYSCONFDIR "/tigrc";
970 if (!*tigrc_system ||
971 (load_option_file(tigrc_system) == ERROR_FILE_DOES_NOT_EXIST && !custom_tigrc_system)) {
972 struct config_state config = { "<built-in>", 0, false };
973 struct io io;
975 if (!io_from_string(&io, builtin_config))
976 return error("Failed to get built-in config");
977 if (io_load_span(&io, " \t", &config.lineno, read_option, &config) != SUCCESS || config.errors == true)
978 return error("Error in built-in config");
981 if (!tigrc_user) {
982 const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
984 if (!xdg_config_home)
985 tigrc_user = TIG_USER_CONFIG;
986 else if (!string_format(buf, "%s/tig/config", xdg_config_home))
987 return error("Failed to expand $XDG_CONFIG_HOME");
988 else
989 tigrc_user = buf;
992 load_option_file(tigrc_user);
994 if (!diff_opts_from_args && tig_diff_opts && *tig_diff_opts) {
995 static const char *diff_opts[SIZEOF_ARG] = { NULL };
996 char buf[SIZEOF_STR];
997 int argc = 0;
999 if (!string_format(buf, "%s", tig_diff_opts) ||
1000 !argv_from_string(diff_opts, &argc, buf))
1001 return error("TIG_DIFF_OPTS contains too many arguments");
1002 else if (!argv_copy(&opt_diff_options, diff_opts))
1003 return error("Failed to format TIG_DIFF_OPTS arguments");
1006 return SUCCESS;
1009 const char *
1010 format_option_value(const struct option_info *option, char buf[], size_t bufsize)
1012 buf[0] = 0;
1014 if (!strcmp(option->type, "bool")) {
1015 bool *opt = option->value;
1017 if (string_nformat(buf, bufsize, NULL, "%s", *opt ? "yes" : "no"))
1018 return buf;
1020 } else if (!strncmp(option->type, "enum", 4)) {
1021 const char *type = option->type + STRING_SIZE("enum ");
1022 enum author *opt = option->value;
1023 const struct enum_map *map = find_enum_map(type);
1025 if (enum_name_copy(buf, bufsize, map->entries[*opt].name))
1026 return buf;
1028 } else if (!strcmp(option->type, "int")) {
1029 int *opt = option->value;
1031 if (opt == &opt_diff_context && *opt < 0)
1032 *opt = -*opt;
1034 if (string_nformat(buf, bufsize, NULL, "%d", *opt))
1035 return buf;
1037 } else if (!strcmp(option->type, "double")) {
1038 double *opt = option->value;
1040 if (*opt >= 1) {
1041 if (string_nformat(buf, bufsize, NULL, "%d", (int) *opt))
1042 return buf;
1044 } else if (string_nformat(buf, bufsize, NULL, "%.0f%%", (*opt) * 100)) {
1045 return buf;
1048 } else if (!strcmp(option->type, "const char *")) {
1049 const char **opt = option->value;
1050 size_t bufpos = 0;
1052 if (!*opt)
1053 return "\"\"";
1054 if (!string_nformat(buf, bufsize, &bufpos, "\"%s\"", *opt))
1055 return NULL;
1056 return buf;
1058 } else if (!strcmp(option->type, "const char **")) {
1059 const char *sep = "";
1060 const char ***opt = option->value;
1061 size_t bufpos = 0;
1062 int i;
1064 for (i = 0; (*opt) && (*opt)[i]; i++) {
1065 const char *arg = (*opt)[i];
1067 if (!string_nformat(buf, bufsize, &bufpos, "%s%s", sep, arg))
1068 return NULL;
1070 sep = " ";
1073 return buf;
1075 } else if (!strcmp(option->type, "struct ref_format **")) {
1076 struct ref_format ***opt = option->value;
1078 if (format_ref_formats(*opt, buf, bufsize) == SUCCESS)
1079 return buf;
1081 } else if (!strcmp(option->type, "view_settings")) {
1082 struct view_column **opt = option->value;
1084 if (format_view_config(*opt, buf, bufsize) == SUCCESS)
1085 return buf;
1087 } else {
1088 if (string_nformat(buf, bufsize, NULL, "<%s>", option->type))
1089 return buf;
1092 return NULL;
1095 static bool
1096 save_option_settings(FILE *file)
1098 char buf[SIZEOF_STR];
1099 int i;
1101 if (!io_fprintf(file, "%s", "\n## Settings\n"))
1102 return false;
1104 for (i = 0; i < ARRAY_SIZE(option_info); i++) {
1105 struct option_info *option = &option_info[i];
1106 const char *name = enum_name(option->name);
1107 const char *value = format_option_value(option, buf, sizeof(buf));
1109 if (!value)
1110 return false;
1112 if (!suffixcmp(name, strlen(name), "-args"))
1113 continue;
1115 if (!io_fprintf(file, "\nset %-25s = %s", name, value))
1116 return false;
1119 return true;
1122 static bool
1123 save_option_keybinding(void *data, const char *group, struct keymap *keymap,
1124 enum request request, const char *key,
1125 const struct request_info *req_info,
1126 const struct run_request *run_req)
1128 FILE *file = data;
1130 if (group && !io_fprintf(file, "\n# %s", group))
1131 return false;
1133 if (!io_fprintf(file, "\nbind %-10s %-15s ", enum_name(keymap->name), key))
1134 return false;
1136 if (req_info) {
1137 return io_fprintf(file, "%s", enum_name(req_info->name));
1139 } else {
1140 const char *sep = format_run_request_flags(run_req);
1141 int i;
1143 for (i = 0; run_req->argv[i]; i++) {
1144 if (!io_fprintf(file, "%s%s", sep, run_req->argv[i]))
1145 return false;
1146 sep = " ";
1149 return true;
1153 static bool
1154 save_option_keybindings(FILE *file)
1156 if (!io_fprintf(file, "%s", "\n\n## Keybindings\n"))
1157 return false;
1159 return foreach_key(save_option_keybinding, file, false);
1162 static bool
1163 save_option_color_name(FILE *file, int color)
1165 int i;
1167 for (i = 0; i < ARRAY_SIZE(color_map); i++)
1168 if (color_map[i].value == color)
1169 return io_fprintf(file, " %-8s", enum_name(color_map[i].name));
1171 return io_fprintf(file, " color%d", color);
1174 static bool
1175 save_option_color_attr(FILE *file, int attr)
1177 int i;
1179 for (i = 0; i < ARRAY_SIZE(attr_map); i++)
1180 if ((attr & attr_map[i].value) &&
1181 !io_fprintf(file, " %s", enum_name(attr_map[i].name)))
1182 return false;
1184 return true;
1187 static bool
1188 save_option_color(void *data, const struct line_rule *rule)
1190 FILE *file = data;
1191 const struct line_info *info;
1193 for (info = &rule->info; info; info = info->next) {
1194 const char *prefix = info->prefix ? info->prefix : "";
1195 const char *prefix_sep = info->prefix ? "." : "";
1196 const char *quote = *rule->line ? "\"" : "";
1197 const char *name = *rule->line ? rule->line : enum_name(rule->name);
1198 int name_width = strlen(prefix) + strlen(prefix_sep) + 2 * strlen(quote) + strlen(name);
1199 int padding = name_width > 30 ? 0 : 30 - name_width;
1201 if (!io_fprintf(file, "\ncolor %s%s%s%s%s%-*s",
1202 prefix, prefix_sep, quote, name, quote, padding, "")
1203 || !save_option_color_name(file, info->fg)
1204 || !save_option_color_name(file, info->bg)
1205 || !save_option_color_attr(file, info->attr))
1206 return false;
1209 return true;
1212 static bool
1213 save_option_colors(FILE *file)
1215 if (!io_fprintf(file, "%s", "\n\n## Colors\n"))
1216 return false;
1218 return foreach_line_rule(save_option_color, file);
1221 enum status_code
1222 save_options(const char *path)
1224 int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0666);
1225 FILE *file = fd != -1 ? fdopen(fd, "w") : NULL;
1226 enum status_code code = SUCCESS;
1228 if (!file)
1229 return error("%s", strerror(errno));
1231 if (!io_fprintf(file, "%s", "# Saved by Tig\n")
1232 || !save_option_settings(file)
1233 || !save_option_keybindings(file)
1234 || !save_option_colors(file))
1235 code = error("Write returned an error");
1237 fclose(file);
1238 return code;
1242 * Repository properties
1245 static void
1246 set_remote_branch(const char *name, const char *value, size_t valuelen)
1248 if (!strcmp(name, ".remote")) {
1249 string_ncopy(repo.remote, value, valuelen);
1251 } else if (*repo.remote && !strcmp(name, ".merge")) {
1252 size_t from = strlen(repo.remote);
1254 if (!prefixcmp(value, "refs/heads/"))
1255 value += STRING_SIZE("refs/heads/");
1257 if (!string_format_from(repo.remote, &from, "/%s", value))
1258 repo.remote[0] = 0;
1262 static void
1263 set_repo_config_option(char *name, char *value, enum status_code (*cmd)(int, const char **))
1265 const char *argv[SIZEOF_ARG] = { name, "=" };
1266 int argc = 1 + (cmd == option_set_command);
1267 enum status_code code;
1269 if (!argv_from_string(argv, &argc, value))
1270 code = error("Too many arguments");
1271 else
1272 code = cmd(argc, argv);
1274 if (code != SUCCESS)
1275 warn("Option 'tig.%s': %s", name, get_status_message(code));
1278 static void
1279 set_work_tree(const char *value)
1281 char cwd[SIZEOF_STR];
1283 if (!getcwd(cwd, sizeof(cwd)))
1284 die("Failed to get cwd path: %s", strerror(errno));
1285 if (chdir(cwd) < 0)
1286 die("Failed to chdir(%s): %s", cwd, strerror(errno));
1287 if (chdir(repo.git_dir) < 0)
1288 die("Failed to chdir(%s): %s", repo.git_dir, strerror(errno));
1289 if (!getcwd(repo.git_dir, sizeof(repo.git_dir)))
1290 die("Failed to get git path: %s", strerror(errno));
1291 if (chdir(value) < 0)
1292 die("Failed to chdir(%s): %s", value, strerror(errno));
1293 if (!getcwd(cwd, sizeof(cwd)))
1294 die("Failed to get cwd path: %s", strerror(errno));
1295 if (setenv("GIT_WORK_TREE", cwd, true))
1296 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
1297 if (setenv("GIT_DIR", repo.git_dir, true))
1298 die("Failed to set GIT_DIR to '%s'", repo.git_dir);
1299 repo.is_inside_work_tree = true;
1302 static struct line_info *
1303 parse_git_color_option(struct line_info *info, char *value)
1305 const char *argv[SIZEOF_ARG];
1306 int argc = 0;
1307 bool first_color = true;
1308 int i;
1310 if (!argv_from_string(argv, &argc, value))
1311 return NULL;
1313 info->fg = COLOR_DEFAULT;
1314 info->bg = COLOR_DEFAULT;
1315 info->attr = 0;
1317 for (i = 0; i < argc; i++) {
1318 int attr = 0;
1320 if (set_attribute(&attr, argv[i])) {
1321 info->attr |= attr;
1323 } else if (set_color(&attr, argv[i])) {
1324 if (first_color)
1325 info->fg = attr;
1326 else
1327 info->bg = attr;
1328 first_color = false;
1331 return info;
1334 static void
1335 set_git_color_option(const char *name, char *value)
1337 struct line_info parsed = {0};
1338 struct line_info *color = NULL;
1339 size_t namelen = strlen(name);
1340 int i;
1342 if (!opt_git_colors)
1343 return;
1345 for (i = 0; opt_git_colors[i]; i++) {
1346 struct line_rule rule = {0};
1347 const char *prefix = NULL;
1348 struct line_info *info;
1349 const char *alias = opt_git_colors[i];
1350 const char *sep = strchr(alias, '=');
1352 if (!sep || namelen != sep - alias ||
1353 string_enum_compare(name, alias, namelen))
1354 continue;
1356 if (!color) {
1357 color = parse_git_color_option(&parsed, value);
1358 if (!color)
1359 return;
1362 if (parse_color_name(sep + 1, &rule, &prefix) == SUCCESS &&
1363 (info = add_line_rule(prefix, &rule))) {
1364 info->fg = parsed.fg;
1365 info->bg = parsed.bg;
1366 info->attr = parsed.attr;
1371 static void
1372 set_encoding(struct encoding **encoding_ref, const char *arg, bool priority)
1374 if (!strcasecmp(arg, "utf-8") || !strcasecmp(arg, "utf8"))
1375 return;
1376 if (parse_encoding(encoding_ref, arg, priority) == SUCCESS)
1377 encoding_arg[0] = 0;
1380 static enum status_code
1381 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
1383 if (!strcmp(name, "i18n.commitencoding"))
1384 set_encoding(&default_encoding, value, false);
1386 else if (!strcmp(name, "gui.encoding"))
1387 set_encoding(&default_encoding, value, true);
1389 else if (!strcmp(name, "core.editor"))
1390 string_ncopy(opt_editor, value, valuelen);
1392 else if (!strcmp(name, "core.worktree"))
1393 set_work_tree(value);
1395 else if (!strcmp(name, "core.abbrev"))
1396 parse_int(&opt_id_width, value, 0, SIZEOF_REV - 1);
1398 else if (!prefixcmp(name, "tig.color."))
1399 set_repo_config_option(name + 10, value, option_color_command);
1401 else if (!prefixcmp(name, "tig.bind."))
1402 set_repo_config_option(name + 9, value, option_bind_command);
1404 else if (!prefixcmp(name, "tig."))
1405 set_repo_config_option(name + 4, value, option_set_command);
1407 else if (!prefixcmp(name, "color."))
1408 set_git_color_option(name + STRING_SIZE("color."), value);
1410 else if (*repo.head && !prefixcmp(name, "branch.") &&
1411 !strncmp(name + 7, repo.head, strlen(repo.head)))
1412 set_remote_branch(name + 7 + strlen(repo.head), value, valuelen);
1414 else if (!strcmp(name, "diff.context")) {
1415 if (!find_option_info_by_value(&opt_diff_context)->seen)
1416 opt_diff_context = -atoi(value);
1418 } else if (!strcmp(name, "format.pretty")) {
1419 if (!prefixcmp(value, "format:") && strstr(value, "%C("))
1420 argv_append(&opt_log_options, "--pretty=medium");
1423 return SUCCESS;
1426 enum status_code
1427 load_git_config(void)
1429 const char *config_list_argv[] = { "git", "config", "--list", NULL };
1431 return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
1434 /* vim: set ts=8 sw=8 noexpandtab: */