parse_view_settings: drop unused variables
[tig.git] / src / options.c
blobb6c09d6430cd70de29815cd1acd519d999ed3ec3
1 /* Copyright (c) 2006-2014 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_argv = NULL;
125 const char **opt_rev_argv = NULL;
126 const char **opt_file_argv = NULL;
127 char opt_env_lines[64] = "";
128 char opt_env_columns[64] = "";
129 char *opt_env[] = { opt_env_lines, opt_env_columns, NULL };
132 * Mapping between options and command argument mapping.
135 const char *
136 diff_context_arg()
138 static char opt_diff_context_arg[9] = "";
140 if (opt_diff_context < 0 ||
141 !string_format(opt_diff_context_arg, "-U%u", opt_diff_context))
142 return "";
144 return opt_diff_context_arg;
148 #define ENUM_ARG(enum_name, arg_string) ENUM_MAP_ENTRY(arg_string, enum_name)
150 static const struct enum_map_entry ignore_space_arg_map[] = {
151 ENUM_ARG(IGNORE_SPACE_NO, ""),
152 ENUM_ARG(IGNORE_SPACE_ALL, "--ignore-all-space"),
153 ENUM_ARG(IGNORE_SPACE_SOME, "--ignore-space-change"),
154 ENUM_ARG(IGNORE_SPACE_AT_EOL, "--ignore-space-at-eol"),
157 const char *
158 ignore_space_arg()
160 return ignore_space_arg_map[opt_ignore_space].name;
163 static const struct enum_map_entry commit_order_arg_map[] = {
164 ENUM_ARG(COMMIT_ORDER_DEFAULT, ""),
165 ENUM_ARG(COMMIT_ORDER_TOPO, "--topo-order"),
166 ENUM_ARG(COMMIT_ORDER_DATE, "--date-order"),
167 ENUM_ARG(COMMIT_ORDER_AUTHOR_DATE, "--author-date-order"),
168 ENUM_ARG(COMMIT_ORDER_REVERSE, "--reverse"),
171 const char *
172 commit_order_arg()
174 return commit_order_arg_map[opt_commit_order].name;
177 const char *
178 commit_order_arg_with_graph(enum graph_display graph_display)
180 enum commit_order commit_order = opt_commit_order;
182 if (graph_display == GRAPH_DISPLAY_YES &&
183 commit_order != COMMIT_ORDER_TOPO &&
184 commit_order != COMMIT_ORDER_DATE &&
185 commit_order != COMMIT_ORDER_AUTHOR_DATE)
186 commit_order = COMMIT_ORDER_TOPO;
188 return commit_order_arg_map[commit_order].name;
191 /* Use --show-notes to support Git >= 1.7.6 */
192 #define NOTES_ARG "--show-notes"
193 #define NOTES_EQ_ARG NOTES_ARG "="
195 static char opt_notes_arg[SIZEOF_STR] = NOTES_ARG;
197 const char *
198 show_notes_arg()
200 if (opt_show_notes)
201 return opt_notes_arg;
202 /* Notes are disabled by default when passing --pretty args. */
203 return "";
206 void
207 update_options_from_argv(const char *argv[])
209 int next, flags_pos;
211 for (next = flags_pos = 0; argv[next]; next++) {
212 const char *flag = argv[next];
213 int value = -1;
215 if (map_enum(&value, commit_order_arg_map, flag)) {
216 opt_commit_order = value;
217 mark_option_seen(&opt_commit_order);
218 continue;
221 if (map_enum(&value, ignore_space_arg_map, flag)) {
222 opt_ignore_space = value;
223 mark_option_seen(&opt_ignore_space);
224 continue;
227 if (!strcmp(flag, "--no-notes")) {
228 opt_show_notes = FALSE;
229 mark_option_seen(&opt_show_notes);
230 continue;
233 if (!prefixcmp(flag, "--show-notes") ||
234 !prefixcmp(flag, "--notes")) {
235 opt_show_notes = TRUE;
236 string_ncopy(opt_notes_arg, flag, strlen(flag));
237 mark_option_seen(&opt_show_notes);
238 continue;
241 if (!prefixcmp(flag, "-U")
242 && parse_int(&value, flag + 2, 0, 999999) == SUCCESS) {
243 opt_diff_context = value;
244 mark_option_seen(&opt_diff_context);
245 continue;
248 argv[flags_pos++] = flag;
251 argv[flags_pos] = NULL;
255 * User config file handling.
258 static const struct enum_map_entry color_map[] = {
259 #define COLOR_MAP(name) ENUM_MAP_ENTRY(#name, COLOR_##name)
260 COLOR_MAP(DEFAULT),
261 COLOR_MAP(BLACK),
262 COLOR_MAP(BLUE),
263 COLOR_MAP(CYAN),
264 COLOR_MAP(GREEN),
265 COLOR_MAP(MAGENTA),
266 COLOR_MAP(RED),
267 COLOR_MAP(WHITE),
268 COLOR_MAP(YELLOW),
271 static const struct enum_map_entry attr_map[] = {
272 #define ATTR_MAP(name) ENUM_MAP_ENTRY(#name, A_##name)
273 ATTR_MAP(NORMAL),
274 ATTR_MAP(BLINK),
275 ATTR_MAP(BOLD),
276 ATTR_MAP(DIM),
277 ATTR_MAP(REVERSE),
278 ATTR_MAP(STANDOUT),
279 ATTR_MAP(UNDERLINE),
282 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
284 enum status_code
285 parse_step(double *opt, const char *arg)
287 int value = atoi(arg);
289 if (!value && !isdigit(*arg))
290 return error("Invalid double or percentage");
292 *opt = value;
293 if (!strchr(arg, '%'))
294 return SUCCESS;
296 /* "Shift down" so 100% and 1 does not conflict. */
297 *opt = (*opt - 1) / 100;
298 if (*opt >= 1.0) {
299 *opt = 0.99;
300 return error("Percentage is larger than 100%%");
302 if (*opt < 0.0) {
303 *opt = 1;
304 return error("Percentage is less than 0%%");
306 return SUCCESS;
309 enum status_code
310 parse_int(int *opt, const char *arg, int min, int max)
312 int value = atoi(arg);
314 if (min <= value && value <= max) {
315 *opt = value;
316 return SUCCESS;
319 return error("Value must be between %d and %d", min, max);
322 static bool
323 set_color(int *color, const char *name)
325 if (map_enum(color, color_map, name))
326 return TRUE;
327 /* Git expects a plain int w/o prefix, however, color<int> is
328 * the preferred Tig color notation. */
329 if (!prefixcmp(name, "color"))
330 name += 5;
331 return string_isnumber(name) &&
332 parse_int(color, name, 0, 255) == SUCCESS;
335 #define is_quoted(c) ((c) == '"' || (c) == '\'')
337 static enum status_code
338 parse_color_name(const char *color, struct line_rule *rule, const char **prefix_ptr)
340 const char *prefixend = is_quoted(*color) ? NULL : strchr(color, '.');
342 if (prefixend) {
343 struct keymap *keymap = get_keymap(color, prefixend - color);
345 if (!keymap)
346 return error("Unknown key map: %.*s", (int) (prefixend - color), color);
347 if (prefix_ptr)
348 *prefix_ptr = keymap->name;
349 color = prefixend + 1;
352 memset(rule, 0, sizeof(*rule));
353 if (is_quoted(*color)) {
354 rule->line = color + 1;
355 rule->linelen = strlen(color) - 2;
356 } else {
357 rule->name = color;
358 rule->namelen = strlen(color);
361 return SUCCESS;
364 static int
365 find_remapped(const char *remapped[][2], size_t remapped_size, const char *arg)
367 size_t arglen = strlen(arg);
368 int i;
370 for (i = 0; i < remapped_size; i++) {
371 const char *name = remapped[i][0];
372 size_t namelen = strlen(name);
374 if (arglen == namelen &&
375 !string_enum_compare(arg, name, namelen))
376 return i;
379 return -1;
382 /* Wants: object fgcolor bgcolor [attribute] */
383 static enum status_code
384 option_color_command(int argc, const char *argv[])
386 struct line_rule rule = {};
387 const char *prefix = NULL;
388 struct line_info *info;
389 enum status_code code;
391 if (argc < 3)
392 return error("Invalid color mapping: color area fgcolor bgcolor [attrs]");
394 code = parse_color_name(argv[0], &rule, &prefix);
395 if (code != SUCCESS)
396 return code;
398 info = add_line_rule(prefix, &rule);
399 if (!info) {
400 static const char *obsolete[][2] = {
401 { "acked", "' Acked-by'" },
402 { "diff-copy-from", "'copy from '" },
403 { "diff-copy-to", "'copy to '" },
404 { "diff-deleted-file-mode", "'deleted file mode '" },
405 { "diff-dissimilarity", "'dissimilarity '" },
406 { "diff-rename-from", "'rename from '" },
407 { "diff-rename-to", "'rename to '" },
408 { "diff-tree", "'diff-tree '" },
409 { "filename", "file" },
410 { "help-keymap", "help.section" },
411 { "main-revgraph", "" },
412 { "pp-adate", "'AuthorDate: '" },
413 { "pp-author", "'Author: '" },
414 { "pp-cdate", "'CommitDate: '" },
415 { "pp-commit", "'Commit: '" },
416 { "pp-date", "'Date: '" },
417 { "reviewed", "' Reviewed-by'" },
418 { "signoff", "' Signed-off-by'" },
419 { "stat-head", "status.header" },
420 { "stat-section", "status.section" },
421 { "tested", "' Tested-by'" },
422 { "tree-dir", "tree.directory" },
423 { "tree-file", "tree.file" },
424 { "tree-head", "tree.header" },
426 int index;
428 index = find_remapped(obsolete, ARRAY_SIZE(obsolete), rule.name);
429 if (index != -1) {
430 if (!*obsolete[index][1])
431 return error("%s is obsolete", argv[0]);
432 /* Keep the initial prefix if defined. */
433 code = parse_color_name(obsolete[index][1], &rule, prefix ? NULL : &prefix);
434 if (code != SUCCESS)
435 return code;
436 info = add_line_rule(prefix, &rule);
439 if (!info)
440 return error("Unknown color name: %s", argv[0]);
442 code = error("%s has been replaced by %s",
443 obsolete[index][0], obsolete[index][1]);
446 if (!set_color(&info->fg, argv[1]))
447 return error("Unknown color: %s", argv[1]);
449 if (!set_color(&info->bg, argv[2]))
450 return error("Unknown color: %s", argv[2]);
452 info->attr = 0;
453 while (argc-- > 3) {
454 int attr;
456 if (!set_attribute(&attr, argv[argc]))
457 return error("Unknown color attribute: %s", argv[argc]);
458 info->attr |= attr;
461 return code;
464 static enum status_code
465 parse_bool(bool *opt, const char *arg)
467 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
468 ? TRUE : FALSE;
469 if (*opt || !strcmp(arg, "0") || !strcmp(arg, "false") || !strcmp(arg, "no"))
470 return SUCCESS;
471 return error("Non-boolean value treated as false: %s", arg);
474 static enum status_code
475 parse_enum(const char *name, unsigned int *opt, const char *arg,
476 const struct enum_map *map)
478 bool is_true;
479 enum status_code code;
481 assert(map->size > 1);
483 if (map_enum_do(map->entries, map->size, (int *) opt, arg))
484 return SUCCESS;
486 code = parse_bool(&is_true, arg);
487 *opt = is_true ? map->entries[1].value : map->entries[0].value;
488 if (code == SUCCESS)
489 return code;
490 return error("'%s' is not a valid value for %s; using %s",
491 arg, name, enum_name(map->entries[*opt].name));
494 static enum status_code
495 parse_string(char *opt, const char *arg, size_t optsize)
497 int arglen = strlen(arg);
499 switch (arg[0]) {
500 case '\"':
501 case '\'':
502 if (arglen == 1 || arg[arglen - 1] != arg[0])
503 return ERROR_UNMATCHED_QUOTATION;
504 arg += 1; arglen -= 2;
505 default:
506 string_ncopy_do(opt, optsize, arg, arglen);
507 return SUCCESS;
511 static enum status_code
512 parse_encoding(struct encoding **encoding_ref, const char *arg, bool priority)
514 char buf[SIZEOF_STR];
515 enum status_code code = parse_string(buf, arg, sizeof(buf));
517 if (code == SUCCESS) {
518 struct encoding *encoding = *encoding_ref;
520 if (encoding && !priority)
521 return code;
522 encoding = encoding_open(buf);
523 if (encoding)
524 *encoding_ref = encoding;
527 return code;
530 static enum status_code
531 parse_args(const char ***args, const char *argv[])
533 if (!argv_copy(args, argv))
534 return ERROR_OUT_OF_MEMORY;
535 return SUCCESS;
538 enum status_code
539 parse_option(struct option_info *option, const char *prefix, const char *arg)
541 char name[SIZEOF_STR];
543 if (!enum_name_prefixed(name, sizeof(name), prefix, option->name))
544 return error("Failed to parse option");
546 if (!strcmp("show-notes", name)) {
547 bool *value = option->value;
548 enum status_code res;
550 if (parse_bool(option->value, arg) == SUCCESS)
551 return SUCCESS;
553 *value = TRUE;
554 string_copy(opt_notes_arg, NOTES_EQ_ARG);
555 res = parse_string(opt_notes_arg + STRING_SIZE(NOTES_EQ_ARG), arg,
556 sizeof(opt_notes_arg) - STRING_SIZE(NOTES_EQ_ARG));
557 if (res == SUCCESS && !opt_notes_arg[STRING_SIZE(NOTES_EQ_ARG)])
558 opt_notes_arg[STRING_SIZE(NOTES_ARG)] = 0;
559 return res;
562 if (!strcmp(option->type, "bool"))
563 return parse_bool(option->value, arg);
565 if (!strcmp(option->type, "double"))
566 return parse_step(option->value, arg);
568 if (!strncmp(option->type, "enum", 4)) {
569 const char *type = option->type + STRING_SIZE("enum ");
570 const struct enum_map *map = find_enum_map(type);
572 return parse_enum(name, option->value, arg, map);
575 if (!strcmp(option->type, "int")) {
576 if (strstr(name, "title-overflow")) {
577 bool enabled = FALSE;
578 int *value = option->value;
580 /* We try to parse it as a boolean (and set the
581 * value to 0 if fale), otherwise we parse it as
582 * an integer and use the given value. */
583 if (parse_bool(&enabled, arg) == SUCCESS) {
584 if (!enabled) {
585 *value = 0;
586 return SUCCESS;
588 arg = "50";
592 if (!strcmp(name, "line-number-interval") ||
593 !strcmp(name, "tab-size"))
594 return parse_int(option->value, arg, 1, 1024);
595 else if (!strcmp(name, "id-width"))
596 return parse_int(option->value, arg, 0, SIZEOF_REV - 1);
597 else
598 return parse_int(option->value, arg, 0, 1024);
601 return error("Unhandled option: %s", name);
604 struct view_config {
605 const char *name;
606 size_t namelen;
607 const char ***argv;
610 static enum status_code
611 parse_view_settings(const char *name_, const char *argv[])
613 char buf[SIZEOF_STR];
614 const char *name = enum_name_copy(buf, sizeof(buf), name_) ? buf : name_;
615 const char *prefixed;
617 if ((prefixed = strstr(name, "-view-"))) {
618 const char *column_name = prefixed + STRING_SIZE("-view-");
619 size_t column_namelen = strlen(column_name);
620 enum view_column_type type;
622 for (type = 0; type < view_column_type_map->size; type++) {
623 const struct enum_map_entry *column = &view_column_type_map->entries[type];
625 if (enum_equals(*column, column_name, column_namelen))
626 return parse_view_column_config(name, type, NULL, argv);
628 if (enum_equals_prefix(*column, column_name, column_namelen))
629 return parse_view_column_config(name, type,
630 column_name + column->namelen + 1,
631 argv);
635 return parse_view_config(name, argv);
638 /* Wants: name = value */
639 static enum status_code
640 option_set_command(int argc, const char *argv[])
642 struct option_info *option;
643 enum status_code code;
645 if (argc < 3)
646 return error("Invalid set command: set option = value");
648 if (strcmp(argv[1], "="))
649 return error("No value assigned to %s", argv[0]);
651 if (!strcmp(argv[0], "reference-format"))
652 return parse_ref_formats(argv + 2);
654 option = find_option_info(option_info, ARRAY_SIZE(option_info), "", argv[0]);
655 if (option) {
656 if (option->seen)
657 return SUCCESS;
659 if (!strcmp(option->type, "view_settings"))
660 return parse_view_settings(argv[0], argv + 2);
662 if (!strcmp(option->type, "const char **"))
663 return parse_args(option->value, argv + 2);
665 code = parse_option(option, "", argv[2]);
666 if (code == SUCCESS && argc != 3)
667 return error("Option %s only takes one value", argv[0]);
669 return code;
674 const char *obsolete[][2] = {
675 { "author-width", "author" },
676 { "filename-width", "file-name" },
677 { "line-number-interval", "line-number" },
678 { "show-author", "author" },
679 { "show-date", "date" },
680 { "show-file-size", "file-size" },
681 { "show-filename", "file-name" },
682 { "show-id", "id" },
683 { "show-line-numbers", "line-number" },
684 { "show-refs", "commit-title" },
685 { "show-rev-graph", "commit-title" },
686 { "title-overflow", "commit-title and text" },
688 int index = find_remapped(obsolete, ARRAY_SIZE(obsolete), argv[0]);
690 if (index != -1)
691 return error("%s is obsolete; see tigrc(5) for how to set the %s column option",
692 obsolete[index][0], obsolete[index][1]);
694 if (!strcmp(argv[0], "read-git-colors"))
695 return error("read-git-colors has been obsoleted by the git-colors option");
698 return error("Unknown option name: %s", argv[0]);
701 /* Wants: mode request key */
702 static enum status_code
703 option_bind_command(int argc, const char *argv[])
705 struct key key[1];
706 size_t keys = 0;
707 enum request request;
708 struct keymap *keymap;
709 const char *key_arg;
711 if (argc < 3)
712 return error("Invalid key binding: bind keymap key action");
714 if (!(keymap = get_keymap(argv[0], strlen(argv[0])))) {
715 if (!strcmp(argv[0], "branch"))
716 keymap = get_keymap("refs", strlen("refs"));
717 if (!keymap)
718 return error("Unknown key map: %s", argv[0]);
721 for (keys = 0, key_arg = argv[1]; *key_arg && keys < ARRAY_SIZE(key); keys++) {
722 enum status_code code = get_key_value(&key_arg, &key[keys]);
724 if (code != SUCCESS)
725 return code;
728 if (*key_arg && keys == ARRAY_SIZE(key))
729 return error("Except for <Esc> combos only one key is allowed "
730 "in key combos: %s", argv[1]);
732 request = get_request(argv[2]);
733 if (request == REQ_UNKNOWN) {
734 static const char *obsolete[][2] = {
735 { "view-branch", "view-refs" },
737 static const char *toggles[][2] = {
738 { "diff-context-down", "diff-context" },
739 { "diff-context-up", "diff-context" },
740 { "stage-next", ":/^@@" },
741 { "toggle-author", "author" },
742 { "toggle-changes", "show-changes" },
743 { "toggle-commit-order", "show-commit-order" },
744 { "toggle-date", "date" },
745 { "toggle-files", "file-filter" },
746 { "toggle-file-filter", "file-filter" },
747 { "toggle-file-size", "file-size" },
748 { "toggle-filename", "filename" },
749 { "toggle-graphic", "show-graphic" },
750 { "toggle-id", "id" },
751 { "toggle-ignore-space", "show-ignore-space" },
752 { "toggle-lineno", "line-number" },
753 { "toggle-refs", "commit-title-refs" },
754 { "toggle-rev-graph", "commit-title-graph" },
755 { "toggle-show-changes", "show-changes" },
756 { "toggle-sort-field", "sort-field" },
757 { "toggle-sort-order", "sort-order" },
758 { "toggle-title-overflow", "commit-title-overflow" },
759 { "toggle-untracked-dirs", "status-untracked-dirs" },
760 { "toggle-vertical-split", "show-vertical-split" },
762 int alias;
764 alias = find_remapped(obsolete, ARRAY_SIZE(obsolete), argv[2]);
765 if (alias != -1) {
766 const char *action = obsolete[alias][1];
768 add_keybinding(keymap, get_request(action), key, keys);
769 return error("%s has been renamed to %s",
770 obsolete[alias][0], action);
773 alias = find_remapped(toggles, ARRAY_SIZE(toggles), argv[2]);
774 if (alias != -1) {
775 const char *action = toggles[alias][0];
776 const char *arg = prefixcmp(action, "diff-context-")
777 ? NULL : (strstr(action, "-down") ? "-1" : "+1");
778 const char *mapped = toggles[alias][1];
779 const char *toggle[] = { ":toggle", mapped, arg, NULL};
780 const char *other[] = { mapped, NULL };
781 const char **prompt = *mapped == ':' ? other : toggle;
782 enum status_code code = add_run_request(keymap, key, keys, prompt);
784 if (code == SUCCESS)
785 code = error("%s has been replaced by `%s%s%s%s'",
786 action, prompt == other ? mapped : ":toggle ",
787 prompt == other ? "" : mapped,
788 arg ? " " : "", arg ? arg : "");
789 return code;
793 if (request == REQ_UNKNOWN)
794 return add_run_request(keymap, key, keys, argv + 2);
796 return add_keybinding(keymap, request, key, keys);
800 static enum status_code load_option_file(const char *path);
802 static enum status_code
803 option_source_command(int argc, const char *argv[])
805 enum status_code code;
807 if (argc < 1)
808 return error("Invalid source command: source path");
810 code = load_option_file(argv[0]);
812 return code == ERROR_FILE_DOES_NOT_EXIST
813 ? error("File does not exist: %s", argv[0]) : code;
816 enum status_code
817 set_option(const char *opt, int argc, const char *argv[])
819 if (!strcmp(opt, "color"))
820 return option_color_command(argc, argv);
822 if (!strcmp(opt, "set"))
823 return option_set_command(argc, argv);
825 if (!strcmp(opt, "bind"))
826 return option_bind_command(argc, argv);
828 if (!strcmp(opt, "source"))
829 return option_source_command(argc, argv);
831 return error("Unknown option command: %s", opt);
834 struct config_state {
835 const char *path;
836 size_t lineno;
837 bool errors;
840 static int
841 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
843 struct config_state *config = data;
844 enum status_code status = ERROR_NO_OPTION_VALUE;
846 /* Check for comment markers, since read_properties() will
847 * only ensure opt and value are split at first " \t". */
848 optlen = strcspn(opt, "#");
849 if (optlen == 0)
850 return OK;
852 if (opt[optlen] == 0) {
853 /* Look for comment endings in the value. */
854 size_t len = strcspn(value, "#");
855 const char *argv[SIZEOF_ARG];
856 int argc = 0;
858 if (len < valuelen) {
859 valuelen = len;
860 value[valuelen] = 0;
863 if (!argv_from_string(argv, &argc, value))
864 status = error("Too many option arguments for %s", opt);
865 else
866 status = set_option(opt, argc, argv);
869 if (status != SUCCESS) {
870 warn("%s:%zu: %s", config->path, config->lineno,
871 get_status_message(status));
872 config->errors = TRUE;
875 /* Always keep going if errors are encountered. */
876 return OK;
879 static enum status_code
880 load_option_file(const char *path)
882 struct config_state config = { path, 0, FALSE };
883 struct io io;
884 char buf[SIZEOF_STR];
886 /* Do not read configuration from stdin if set to "" */
887 if (!path || !strlen(path))
888 return SUCCESS;
890 if (!prefixcmp(path, "~/")) {
891 const char *home = getenv("HOME");
893 if (!home || !string_format(buf, "%s/%s", home, path + 2))
894 return error("Failed to expand ~ to user home directory");
895 path = buf;
898 /* It's OK that the file doesn't exist. */
899 if (!io_open(&io, "%s", path)) {
900 /* XXX: Must return ERROR_FILE_DOES_NOT_EXIST so missing
901 * system tigrc is detected properly. */
902 if (io_error(&io) == ENOENT)
903 return ERROR_FILE_DOES_NOT_EXIST;
904 return error("Error loading file %s: %s", path, strerror(io_error(&io)));
907 if (io_load_span(&io, " \t", &config.lineno, read_option, &config) == ERR ||
908 config.errors == TRUE)
909 warn("Errors while loading %s.", path);
910 return SUCCESS;
913 extern const char *builtin_config;
916 load_options(void)
918 const char *tigrc_user = getenv("TIGRC_USER");
919 const char *tigrc_system = getenv("TIGRC_SYSTEM");
920 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
921 const bool diff_opts_from_args = !!opt_diff_options;
922 bool custom_tigrc_system = !!tigrc_system;
924 opt_file_filter = TRUE;
925 if (!find_option_info_by_value(&opt_diff_context)->seen)
926 opt_diff_context = -3;
928 if (!custom_tigrc_system)
929 tigrc_system = SYSCONFDIR "/tigrc";
931 if (!*tigrc_system ||
932 (load_option_file(tigrc_system) == ERROR_FILE_DOES_NOT_EXIST && !custom_tigrc_system)) {
933 struct config_state config = { "<built-in>", 0, FALSE };
934 struct io io;
936 if (!io_from_string(&io, builtin_config))
937 die("Failed to get built-in config");
938 if (io_load_span(&io, " \t", &config.lineno, read_option, &config) == ERR || config.errors == TRUE)
939 die("Error in built-in config");
942 if (!tigrc_user)
943 tigrc_user = "~/.tigrc";
944 load_option_file(tigrc_user);
946 if (!diff_opts_from_args && tig_diff_opts && *tig_diff_opts) {
947 static const char *diff_opts[SIZEOF_ARG] = { NULL };
948 char buf[SIZEOF_STR];
949 int argc = 0;
951 if (!string_format(buf, "%s", tig_diff_opts) ||
952 !argv_from_string(diff_opts, &argc, buf))
953 die("TIG_DIFF_OPTS contains too many arguments");
954 else if (!argv_copy(&opt_diff_options, diff_opts))
955 die("Failed to format TIG_DIFF_OPTS arguments");
958 return OK;
962 * Repository properties
965 static void
966 set_remote_branch(const char *name, const char *value, size_t valuelen)
968 if (!strcmp(name, ".remote")) {
969 string_ncopy(repo.remote, value, valuelen);
971 } else if (*repo.remote && !strcmp(name, ".merge")) {
972 size_t from = strlen(repo.remote);
974 if (!prefixcmp(value, "refs/heads/"))
975 value += STRING_SIZE("refs/heads/");
977 if (!string_format_from(repo.remote, &from, "/%s", value))
978 repo.remote[0] = 0;
982 static void
983 set_repo_config_option(char *name, char *value, enum status_code (*cmd)(int, const char **))
985 const char *argv[SIZEOF_ARG] = { name, "=" };
986 int argc = 1 + (cmd == option_set_command);
987 enum status_code code;
989 if (!argv_from_string(argv, &argc, value))
990 code = error("Too many arguments");
991 else
992 code = cmd(argc, argv);
994 if (code != SUCCESS)
995 warn("Option 'tig.%s': %s", name, get_status_message(code));
998 static void
999 set_work_tree(const char *value)
1001 char cwd[SIZEOF_STR];
1003 if (!getcwd(cwd, sizeof(cwd)))
1004 die("Failed to get cwd path: %s", strerror(errno));
1005 if (chdir(cwd) < 0)
1006 die("Failed to chdir(%s): %s", cwd, strerror(errno));
1007 if (chdir(repo.git_dir) < 0)
1008 die("Failed to chdir(%s): %s", repo.git_dir, strerror(errno));
1009 if (!getcwd(repo.git_dir, sizeof(repo.git_dir)))
1010 die("Failed to get git path: %s", strerror(errno));
1011 if (chdir(value) < 0)
1012 die("Failed to chdir(%s): %s", value, strerror(errno));
1013 if (!getcwd(cwd, sizeof(cwd)))
1014 die("Failed to get cwd path: %s", strerror(errno));
1015 if (setenv("GIT_WORK_TREE", cwd, TRUE))
1016 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
1017 if (setenv("GIT_DIR", repo.git_dir, TRUE))
1018 die("Failed to set GIT_DIR to '%s'", repo.git_dir);
1019 repo.is_inside_work_tree = TRUE;
1022 static struct line_info *
1023 parse_git_color_option(struct line_info *info, char *value)
1025 const char *argv[SIZEOF_ARG];
1026 int argc = 0;
1027 bool first_color = TRUE;
1028 int i;
1030 if (!argv_from_string(argv, &argc, value))
1031 return NULL;
1033 info->fg = COLOR_DEFAULT;
1034 info->bg = COLOR_DEFAULT;
1035 info->attr = 0;
1037 for (i = 0; i < argc; i++) {
1038 int attr = 0;
1040 if (set_attribute(&attr, argv[i])) {
1041 info->attr |= attr;
1043 } else if (set_color(&attr, argv[i])) {
1044 if (first_color)
1045 info->fg = attr;
1046 else
1047 info->bg = attr;
1048 first_color = FALSE;
1051 return info;
1054 static void
1055 set_git_color_option(const char *name, char *value)
1057 struct line_info parsed = {};
1058 struct line_info *color = NULL;
1059 size_t namelen = strlen(name);
1060 int i;
1062 if (!opt_git_colors)
1063 return;
1065 for (i = 0; opt_git_colors[i]; i++) {
1066 struct line_rule rule = {};
1067 const char *prefix = NULL;
1068 struct line_info *info;
1069 const char *alias = opt_git_colors[i];
1070 const char *sep = strchr(alias, '=');
1072 if (!sep || namelen != sep - alias ||
1073 string_enum_compare(name, alias, namelen))
1074 continue;
1076 if (!color) {
1077 color = parse_git_color_option(&parsed, value);
1078 if (!color)
1079 return;
1082 if (parse_color_name(sep + 1, &rule, &prefix) == SUCCESS &&
1083 (info = add_line_rule(prefix, &rule))) {
1084 info->fg = parsed.fg;
1085 info->bg = parsed.bg;
1086 info->attr = parsed.attr;
1091 static void
1092 set_encoding(struct encoding **encoding_ref, const char *arg, bool priority)
1094 if (!strcasecmp(arg, "utf-8") || !strcasecmp(arg, "utf8"))
1095 return;
1096 if (parse_encoding(encoding_ref, arg, priority) == SUCCESS)
1097 encoding_arg[0] = 0;
1100 static int
1101 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
1103 if (!strcmp(name, "i18n.commitencoding"))
1104 set_encoding(&default_encoding, value, FALSE);
1106 else if (!strcmp(name, "gui.encoding"))
1107 set_encoding(&default_encoding, value, TRUE);
1109 else if (!strcmp(name, "core.editor"))
1110 string_ncopy(opt_editor, value, valuelen);
1112 else if (!strcmp(name, "core.worktree"))
1113 set_work_tree(value);
1115 else if (!strcmp(name, "core.abbrev"))
1116 parse_int(&opt_id_width, value, 0, SIZEOF_REV - 1);
1118 else if (!prefixcmp(name, "tig.color."))
1119 set_repo_config_option(name + 10, value, option_color_command);
1121 else if (!prefixcmp(name, "tig.bind."))
1122 set_repo_config_option(name + 9, value, option_bind_command);
1124 else if (!prefixcmp(name, "tig."))
1125 set_repo_config_option(name + 4, value, option_set_command);
1127 else if (!prefixcmp(name, "color."))
1128 set_git_color_option(name + STRING_SIZE("color."), value);
1130 else if (*repo.head && !prefixcmp(name, "branch.") &&
1131 !strncmp(name + 7, repo.head, strlen(repo.head)))
1132 set_remote_branch(name + 7 + strlen(repo.head), value, valuelen);
1134 else if (!strcmp(name, "diff.context")) {
1135 if (!find_option_info_by_value(&opt_diff_context)->seen)
1136 opt_diff_context = -atoi(value);
1138 } else if (!strcmp(name, "format.pretty")) {
1139 if (!prefixcmp(value, "format:") && strstr(value, "%C("))
1140 argv_append(&opt_log_options, "--pretty=medium");
1143 return OK;
1147 load_git_config(void)
1149 const char *config_list_argv[] = { "git", "config", "--list", NULL };
1151 return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
1154 /* vim: set ts=8 sw=8 noexpandtab: */