Make view columns configurable
[tig.git] / src / options.c
blob9c8d19c2959f71d299a44fd4a712e6d40a6283d7
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 *name)
41 size_t namelen = strlen(name);
42 int i;
44 for (i = 0; i < options; i++)
45 if (enum_equals(option[i], name, namelen))
46 return &option[i];
48 return NULL;
51 static void
52 mark_option_seen(void *value)
54 int i;
56 for (i = 0; i < ARRAY_SIZE(option_info); i++)
57 if (option_info[i].value == value) {
58 option_info[i].seen = TRUE;
59 break;
64 * State variables.
67 iconv_t opt_iconv_out = ICONV_NONE;
68 char opt_editor[SIZEOF_STR] = "";
69 const char **opt_cmdline_argv = NULL;
70 const char **opt_rev_argv = NULL;
71 const char **opt_file_argv = NULL;
72 char opt_env_lines[64] = "";
73 char opt_env_columns[64] = "";
74 char *opt_env[] = { opt_env_lines, opt_env_columns, NULL };
77 * Mapping between options and command argument mapping.
80 const char *
81 diff_context_arg()
83 static char opt_diff_context_arg[9] = "";
85 if (!string_format(opt_diff_context_arg, "-U%u", opt_diff_context))
86 string_ncopy(opt_diff_context_arg, "-U3", 3);
88 return opt_diff_context_arg;
92 #define ENUM_ARG(enum_name, arg_string) ENUM_MAP_ENTRY(arg_string, enum_name)
94 static const struct enum_map_entry ignore_space_arg_map[] = {
95 ENUM_ARG(IGNORE_SPACE_NO, ""),
96 ENUM_ARG(IGNORE_SPACE_ALL, "--ignore-all-space"),
97 ENUM_ARG(IGNORE_SPACE_SOME, "--ignore-space-change"),
98 ENUM_ARG(IGNORE_SPACE_AT_EOL, "--ignore-space-at-eol"),
101 const char *
102 ignore_space_arg()
104 return ignore_space_arg_map[opt_ignore_space].name;
107 static const struct enum_map_entry commit_order_arg_map[] = {
108 ENUM_ARG(COMMIT_ORDER_DEFAULT, ""),
109 ENUM_ARG(COMMIT_ORDER_TOPO, "--topo-order"),
110 ENUM_ARG(COMMIT_ORDER_DATE, "--date-order"),
111 ENUM_ARG(COMMIT_ORDER_REVERSE, "--reverse"),
114 const char *
115 commit_order_arg()
117 return commit_order_arg_map[opt_commit_order].name;
120 static char opt_notes_arg[SIZEOF_STR] = "--show-notes";
122 const char *
123 show_notes_arg()
125 if (opt_show_notes)
126 return opt_notes_arg;
127 /* Notes are disabled by default when passing --pretty args. */
128 return "";
131 void
132 update_options_from_argv(const char *argv[])
134 int next, flags_pos;
136 for (next = flags_pos = 0; argv[next]; next++) {
137 const char *flag = argv[next];
138 int value = -1;
140 if (map_enum(&value, commit_order_arg_map, flag)) {
141 opt_commit_order = value;
142 mark_option_seen(&opt_commit_order);
143 continue;
146 if (map_enum(&value, ignore_space_arg_map, flag)) {
147 opt_ignore_space = value;
148 mark_option_seen(&opt_ignore_space);
149 continue;
152 if (!strcmp(flag, "--no-notes")) {
153 opt_show_notes = FALSE;
154 mark_option_seen(&opt_show_notes);
155 continue;
158 if (!prefixcmp(flag, "--show-notes")) {
159 opt_show_notes = TRUE;
160 string_ncopy(opt_notes_arg, flag, strlen(flag));
161 mark_option_seen(&opt_show_notes);
162 continue;
165 if (!prefixcmp(flag, "-U")
166 && parse_int(&value, flag + 2, 0, 999999) == SUCCESS) {
167 opt_diff_context = value;
168 mark_option_seen(&opt_diff_context);
169 continue;
172 argv[flags_pos++] = flag;
175 argv[flags_pos] = NULL;
179 * User config file handling.
182 static const struct enum_map_entry color_map[] = {
183 #define COLOR_MAP(name) ENUM_MAP_ENTRY(#name, COLOR_##name)
184 COLOR_MAP(DEFAULT),
185 COLOR_MAP(BLACK),
186 COLOR_MAP(BLUE),
187 COLOR_MAP(CYAN),
188 COLOR_MAP(GREEN),
189 COLOR_MAP(MAGENTA),
190 COLOR_MAP(RED),
191 COLOR_MAP(WHITE),
192 COLOR_MAP(YELLOW),
195 static const struct enum_map_entry attr_map[] = {
196 #define ATTR_MAP(name) ENUM_MAP_ENTRY(#name, A_##name)
197 ATTR_MAP(NORMAL),
198 ATTR_MAP(BLINK),
199 ATTR_MAP(BOLD),
200 ATTR_MAP(DIM),
201 ATTR_MAP(REVERSE),
202 ATTR_MAP(STANDOUT),
203 ATTR_MAP(UNDERLINE),
206 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
208 enum status_code
209 parse_step(double *opt, const char *arg)
211 *opt = atoi(arg);
212 if (!strchr(arg, '%'))
213 return SUCCESS;
215 /* "Shift down" so 100% and 1 does not conflict. */
216 *opt = (*opt - 1) / 100;
217 if (*opt >= 1.0) {
218 *opt = 0.99;
219 return error("Percentage is larger than 100%%");
221 if (*opt < 0.0) {
222 *opt = 1;
223 return error("Percentage is less than 0%%");
225 return SUCCESS;
228 enum status_code
229 parse_int(int *opt, const char *arg, int min, int max)
231 int value = atoi(arg);
233 if (min <= value && value <= max) {
234 *opt = value;
235 return SUCCESS;
238 return error("Value must be between %d and %d", min, max);
241 static bool
242 set_color(int *color, const char *name)
244 if (map_enum(color, color_map, name))
245 return TRUE;
246 if (!prefixcmp(name, "color"))
247 return parse_int(color, name + 5, 0, 255) == SUCCESS;
248 /* Used when reading git colors. Git expects a plain int w/o prefix. */
249 return parse_int(color, name, 0, 255) == SUCCESS;
252 #define is_quoted(c) ((c) == '"' || (c) == '\'')
254 static enum status_code
255 parse_color_name(const char *color, struct line_rule *rule, const char **prefix_ptr)
257 const char *prefixend = is_quoted(*color) ? NULL : strchr(color, '.');
259 if (prefixend) {
260 struct keymap *keymap = get_keymap(color, prefixend - color);
262 if (!keymap)
263 return error("Unknown key map: %.*s", (int) (prefixend - color), color);
264 if (prefix_ptr)
265 *prefix_ptr = keymap->name;
266 color = prefixend + 1;
269 memset(rule, 0, sizeof(*rule));
270 if (is_quoted(*color)) {
271 rule->line = color + 1;
272 rule->linelen = strlen(color) - 2;
273 } else {
274 rule->name = color;
275 rule->namelen = strlen(color);
278 return SUCCESS;
281 static int
282 find_remapped(const char *remapped[][2], size_t remapped_size, const char *arg)
284 size_t arglen = strlen(arg);
285 int i;
287 for (i = 0; i < remapped_size; i++) {
288 const char *name = remapped[i][0];
289 size_t namelen = strlen(name);
291 if (arglen == namelen &&
292 !string_enum_compare(arg, name, namelen))
293 return i;
296 return -1;
299 /* Wants: object fgcolor bgcolor [attribute] */
300 static enum status_code
301 option_color_command(int argc, const char *argv[])
303 struct line_rule rule = {};
304 const char *prefix = NULL;
305 struct line_info *info;
306 enum status_code code;
308 if (argc < 3)
309 return error("Invalid color mapping: color area fgcolor bgcolor [attrs]");
311 code = parse_color_name(argv[0], &rule, &prefix);
312 if (code != SUCCESS)
313 return code;
315 info = add_line_rule(prefix, &rule);
316 if (!info) {
317 static const char *obsolete[][2] = {
318 { "acked", "' Acked-by'" },
319 { "diff-copy-from", "'copy from '" },
320 { "diff-copy-to", "'copy to '" },
321 { "diff-deleted-file-mode", "'deleted file mode '" },
322 { "diff-dissimilarity", "'dissimilarity '" },
323 { "diff-rename-from", "'rename from '" },
324 { "diff-rename-to", "'rename to '" },
325 { "diff-tree", "'diff-tree '" },
326 { "filename", "file" },
327 { "pp-adate", "'AuthorDate: '" },
328 { "pp-author", "'Author: '" },
329 { "pp-cdate", "'CommitDate: '" },
330 { "pp-commit", "'Commit: '" },
331 { "pp-date", "'Date: '" },
332 { "reviewed", "' Reviewed-by'" },
333 { "signoff", "' Signed-off-by'" },
334 { "tested", "' Tested-by'" },
335 { "tree-dir", "tree.directory" },
336 { "tree-file", "tree.file" },
338 int index;
340 index = find_remapped(obsolete, ARRAY_SIZE(obsolete), rule.name);
341 if (index != -1) {
342 /* Keep the initial prefix if defined. */
343 code = parse_color_name(obsolete[index][1], &rule, prefix ? NULL : &prefix);
344 if (code != SUCCESS)
345 return code;
346 info = add_line_rule(prefix, &rule);
349 if (!info)
350 return error("Unknown color name: %s", argv[0]);
352 code = error("%s has been replaced by %s",
353 obsolete[index][0], obsolete[index][1]);
356 if (!set_color(&info->fg, argv[1]))
357 return error("Unknown color: %s", argv[1]);
359 if (!set_color(&info->bg, argv[2]))
360 return error("Unknown color: %s", argv[2]);
362 info->attr = 0;
363 while (argc-- > 3) {
364 int attr;
366 if (!set_attribute(&attr, argv[argc]))
367 return error("Unknown color attribute: %s", argv[argc]);
368 info->attr |= attr;
371 return code;
374 static enum status_code
375 parse_bool(bool *opt, const char *arg)
377 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
378 ? TRUE : FALSE;
379 if (*opt || !strcmp(arg, "0") || !strcmp(arg, "false") || !strcmp(arg, "no"))
380 return SUCCESS;
381 return error("Non-boolean value treated as false: %s", arg);
384 static enum status_code
385 parse_enum(unsigned int *opt, const char *arg, const struct enum_map *map)
387 bool is_true;
389 assert(map->size > 1);
391 if (map_enum_do(map->entries, map->size, (int *) opt, arg))
392 return SUCCESS;
394 parse_bool(&is_true, arg);
395 *opt = is_true ? map->entries[1].value : map->entries[0].value;
396 return SUCCESS;
399 static enum status_code
400 parse_string(char *opt, const char *arg, size_t optsize)
402 int arglen = strlen(arg);
404 switch (arg[0]) {
405 case '\"':
406 case '\'':
407 if (arglen == 1 || arg[arglen - 1] != arg[0])
408 return ERROR_UNMATCHED_QUOTATION;
409 arg += 1; arglen -= 2;
410 default:
411 string_ncopy_do(opt, optsize, arg, arglen);
412 return SUCCESS;
416 static enum status_code
417 parse_encoding(struct encoding **encoding_ref, const char *arg, bool priority)
419 char buf[SIZEOF_STR];
420 enum status_code code = parse_string(buf, arg, sizeof(buf));
422 if (code == SUCCESS) {
423 struct encoding *encoding = *encoding_ref;
425 if (encoding && !priority)
426 return code;
427 encoding = encoding_open(buf);
428 if (encoding)
429 *encoding_ref = encoding;
432 return code;
435 static enum status_code
436 parse_args(const char ***args, const char *argv[])
438 if (!argv_copy(args, argv))
439 return ERROR_OUT_OF_MEMORY;
440 return SUCCESS;
443 enum status_code
444 parse_option(struct option_info *option, const char *arg)
446 const char *name = enum_name_static(option->name, strlen(option->name));
448 if (!strcmp("show-notes", name)) {
449 bool *value = option->value;
450 enum status_code res;
452 if (parse_bool(option->value, arg) == SUCCESS)
453 return SUCCESS;
455 *value = TRUE;
456 strcpy(opt_notes_arg, "--show-notes=");
457 res = parse_string(opt_notes_arg + 8, arg,
458 sizeof(opt_notes_arg) - 8);
459 if (res == SUCCESS && opt_notes_arg[8] == '\0')
460 opt_notes_arg[7] = '\0';
461 return res;
464 if (!strcmp(option->type, "bool"))
465 return parse_bool(option->value, arg);
467 if (!strcmp(option->type, "double"))
468 return parse_step(option->value, arg);
470 if (!strncmp(option->type, "enum", 4)) {
471 const char *type = option->type + STRING_SIZE("enum ");
472 const struct enum_map *map = find_enum_map(type);
474 return parse_enum(option->value, arg, map);
477 if (!strcmp(option->type, "int")) {
478 if (strstr(name, "title-overflow")) {
479 bool enabled = FALSE;
480 int *value = option->value;
482 /* We try to parse it as a boolean (and set the
483 * value to 0 if fale), otherwise we parse it as
484 * an integer and use the given value. */
485 if (parse_bool(&enabled, arg) == SUCCESS) {
486 if (!enabled) {
487 *value = 0;
488 return SUCCESS;
490 arg = "50";
494 if (!strcmp(name, "line-number-interval") ||
495 !strcmp(name, "tab-size"))
496 return parse_int(option->value, arg, 1, 1024);
497 else if (!strcmp(name, "id-width"))
498 return parse_int(option->value, arg, 0, SIZEOF_REV - 1);
499 else
500 return parse_int(option->value, arg, 0, 1024);
503 return error("Unhandled option: %s", name);
506 struct view_config {
507 const char *name;
508 const char ***argv;
511 static struct view_config view_configs[] = {
512 { "blame-view", &opt_blame_view },
513 { "blob-view", &opt_blob_view },
514 { "diff-view", &opt_diff_view },
515 { "grep-view", &opt_grep_view },
516 { "log-view", &opt_log_view },
517 { "main-view", &opt_main_view },
518 { "pager-view", &opt_pager_view },
519 { "refs-view", &opt_refs_view },
520 { "stash-view", &opt_stash_view },
521 { "stage-view", &opt_stage_view },
522 { "tree-view", &opt_tree_view },
525 static enum status_code
526 check_view_config(struct option_info *option, const char *argv[])
528 const char *name = enum_name_static(option->name, strlen(option->name));
529 int i;
531 for (i = 0; i < ARRAY_SIZE(view_configs); i++)
532 if (!strcmp(name, view_configs[i].name))
533 return parse_view_config(name, argv);
535 return SUCCESS;
538 /* Wants: name = value */
539 static enum status_code
540 option_set_command(int argc, const char *argv[])
542 struct option_info *option;
544 if (argc < 3)
545 return error("Invalid set command: set option = value");
547 if (strcmp(argv[1], "="))
548 return error("No value assigned to %s", argv[0]);
550 if (!strcmp(argv[0], "reference-format"))
551 return parse_ref_formats(argv + 2);
553 option = find_option_info(option_info, ARRAY_SIZE(option_info), argv[0]);
554 if (option) {
555 enum status_code code;
557 if (option->seen)
558 return SUCCESS;
560 if (!strcmp(option->type, "const char **")) {
561 code = check_view_config(option, argv + 2);
562 if (code != SUCCESS)
563 return code;
564 return parse_args(option->value, argv + 2);
567 code = parse_option(option, argv[2]);
568 if (code == SUCCESS && argc != 3)
569 return error("Option %s only takes one value", argv[0]);
571 return code;
576 const char *obsolete[][2] = {
577 { "author-width", "author" },
578 { "filename-width", "file-name" },
579 { "line-number-interval", "line-number" },
580 { "show-author", "author" },
581 { "show-date", "date" },
582 { "show-file-size", "file-size" },
583 { "show-filename", "file-name" },
584 { "show-id", "id" },
585 { "show-line-numbers", "line-number" },
586 { "show-refs", "commit-title" },
587 { "show-rev-graph", "commit-title" },
588 { "title-overflow", "commit-title and text" },
590 int index = find_remapped(obsolete, ARRAY_SIZE(obsolete), argv[0]);
592 if (index != -1)
593 return error("%s is obsolete; use the %s view column options instead",
594 obsolete[index][0], obsolete[index][1]);
597 return error("Unknown option name: %s", argv[0]);
600 /* Wants: mode request key */
601 static enum status_code
602 option_bind_command(int argc, const char *argv[])
604 struct key key[1];
605 size_t keys = 0;
606 enum request request;
607 struct keymap *keymap;
608 const char *key_arg;
610 if (argc < 3)
611 return error("Invalid key binding: bind keymap key action");
613 if (!(keymap = get_keymap(argv[0], strlen(argv[0])))) {
614 if (!strcmp(argv[0], "branch"))
615 keymap = get_keymap("refs", strlen("refs"));
616 if (!keymap)
617 return error("Unknown key map: %s", argv[0]);
620 for (keys = 0, key_arg = argv[1]; *key_arg && keys < ARRAY_SIZE(key); keys++) {
621 if (get_key_value(&key_arg, &key[keys]) == ERR)
622 return error("Unknown key combo: %s", argv[1]);
625 if (*key_arg && keys == ARRAY_SIZE(key))
626 return error("Max %zu keys are allowed in key combos: %s", ARRAY_SIZE(key), argv[1]);
628 request = get_request(argv[2]);
629 if (request == REQ_UNKNOWN) {
630 static const char *obsolete[][2] = {
631 { "view-branch", "view-refs" },
633 static const char *toggles[][2] = {
634 { "diff-context-down", "diff-context" },
635 { "diff-context-up", "diff-context" },
636 { "toggle-author", "show-author" },
637 { "toggle-changes", "show-changes" },
638 { "toggle-commit-order", "show-commit-order" },
639 { "toggle-date", "show-date" },
640 { "toggle-file-filter", "file-filter" },
641 { "toggle-file-size", "show-file-size" },
642 { "toggle-filename", "show-filename" },
643 { "toggle-graphic", "show-graphic" },
644 { "toggle-id", "show-id" },
645 { "toggle-ignore-space", "show-ignore-space" },
646 { "toggle-lineno", "show-line-numbers" },
647 { "toggle-refs", "show-refs" },
648 { "toggle-rev-graph", "show-rev-graph" },
649 { "toggle-sort-field", "sort-field" },
650 { "toggle-sort-order", "sort-order" },
651 { "toggle-title-overflow", "show-title-overflow" },
652 { "toggle-untracked-dirs", "status-untracked-dirs" },
653 { "toggle-vertical-split", "show-vertical-split" },
655 int alias;
657 alias = find_remapped(obsolete, ARRAY_SIZE(obsolete), argv[2]);
658 if (alias != -1) {
659 const char *action = obsolete[alias][1];
661 add_keybinding(keymap, get_request(action), key, keys);
662 return error("%s has been renamed to %s",
663 obsolete[alias][0], action);
666 alias = find_remapped(toggles, ARRAY_SIZE(toggles), argv[2]);
667 if (alias != -1) {
668 const char *action = toggles[alias][0];
669 const char *arg = prefixcmp(action, "diff-context-")
670 ? NULL : (strstr(action, "-down") ? "-1" : "+1");
671 const char *toggle[] = { ":toggle", toggles[alias][1], arg, NULL};
672 enum status_code code = add_run_request(keymap, key, keys, toggle);
674 if (code == SUCCESS)
675 code = error("%s has been replaced by `:toggle %s%s%s'",
676 action, toggles[alias][1],
677 arg ? " " : "", arg ? arg : "");
678 return code;
682 if (request == REQ_UNKNOWN)
683 return add_run_request(keymap, key, keys, argv + 2);
685 return add_keybinding(keymap, request, key, keys);
689 static enum status_code load_option_file(const char *path);
691 static enum status_code
692 option_source_command(int argc, const char *argv[])
694 enum status_code code;
696 if (argc < 1)
697 return error("Invalid source command: source path");
699 code = load_option_file(argv[0]);
701 return code == ERROR_FILE_DOES_NOT_EXIST
702 ? error("File does not exist: %s", argv[0]) : code;
705 enum status_code
706 set_option(const char *opt, int argc, const char *argv[])
708 if (!strcmp(opt, "color"))
709 return option_color_command(argc, argv);
711 if (!strcmp(opt, "set"))
712 return option_set_command(argc, argv);
714 if (!strcmp(opt, "bind"))
715 return option_bind_command(argc, argv);
717 if (!strcmp(opt, "source"))
718 return option_source_command(argc, argv);
720 return error("Unknown option command: %s", opt);
723 struct config_state {
724 const char *path;
725 int lineno;
726 bool errors;
729 static int
730 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
732 struct config_state *config = data;
733 enum status_code status = ERROR_NO_OPTION_VALUE;
735 config->lineno++;
737 /* Check for comment markers, since read_properties() will
738 * only ensure opt and value are split at first " \t". */
739 optlen = strcspn(opt, "#");
740 if (optlen == 0)
741 return OK;
743 if (opt[optlen] == 0) {
744 /* Look for comment endings in the value. */
745 size_t len = strcspn(value, "#");
746 const char *argv[SIZEOF_ARG];
747 int argc = 0;
749 if (len < valuelen) {
750 valuelen = len;
751 value[valuelen] = 0;
754 if (!argv_from_string(argv, &argc, value))
755 status = error("Too many option arguments for %s", opt);
756 else
757 status = set_option(opt, argc, argv);
760 if (status != SUCCESS) {
761 warn("%s:%d: %s", config->path, config->lineno,
762 get_status_message(status));
763 config->errors = TRUE;
766 /* Always keep going if errors are encountered. */
767 return OK;
770 static enum status_code
771 load_option_file(const char *path)
773 struct config_state config = { path, 0, FALSE };
774 struct io io;
775 char buf[SIZEOF_STR];
777 /* Do not read configuration from stdin if set to "" */
778 if (!path || !strlen(path))
779 return SUCCESS;
781 if (!prefixcmp(path, "~/")) {
782 const char *home = getenv("HOME");
784 if (!home || !string_format(buf, "%s/%s", home, path + 2))
785 return error("Failed to expand ~ to user home directory");
786 path = buf;
789 /* It's OK that the file doesn't exist. */
790 if (!io_open(&io, "%s", path)) {
791 /* XXX: Must return ERROR_FILE_DOES_NOT_EXIST so missing
792 * system tigrc is detected properly. */
793 if (io_error(&io) == ENOENT)
794 return ERROR_FILE_DOES_NOT_EXIST;
795 return error("Error loading file %s: %s", path, strerror(io_error(&io)));
798 if (io_load(&io, " \t", read_option, &config) == ERR ||
799 config.errors == TRUE)
800 warn("Errors while loading %s.", path);
801 return SUCCESS;
804 extern const char *builtin_config;
807 load_options(void)
809 const char *tigrc_user = getenv("TIGRC_USER");
810 const char *tigrc_system = getenv("TIGRC_SYSTEM");
811 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
812 const bool diff_opts_from_args = !!opt_diff_options;
813 bool custom_tigrc_system = !!tigrc_system;
815 opt_file_filter = TRUE;
817 if (!custom_tigrc_system)
818 tigrc_system = SYSCONFDIR "/tigrc";
820 if (load_option_file(tigrc_system) == ERROR_FILE_DOES_NOT_EXIST && !custom_tigrc_system) {
821 struct config_state config = { "<built-in>", 0, FALSE };
822 struct io io;
824 if (!io_from_string(&io, builtin_config) ||
825 !io_load(&io, " \t", read_option, &config) == ERR ||
826 config.errors == TRUE)
827 die("Error in built-in config");
830 if (!tigrc_user)
831 tigrc_user = "~/.tigrc";
832 load_option_file(tigrc_user);
834 if (!diff_opts_from_args && tig_diff_opts && *tig_diff_opts) {
835 static const char *diff_opts[SIZEOF_ARG] = { NULL };
836 char buf[SIZEOF_STR];
837 int argc = 0;
839 if (!string_format(buf, "%s", tig_diff_opts) ||
840 !argv_from_string(diff_opts, &argc, buf))
841 die("TIG_DIFF_OPTS contains too many arguments");
842 else if (!argv_copy(&opt_diff_options, diff_opts))
843 die("Failed to format TIG_DIFF_OPTS arguments");
846 return OK;
850 * Repository properties
853 static void
854 set_remote_branch(const char *name, const char *value, size_t valuelen)
856 if (!strcmp(name, ".remote")) {
857 string_ncopy(repo.remote, value, valuelen);
859 } else if (*repo.remote && !strcmp(name, ".merge")) {
860 size_t from = strlen(repo.remote);
862 if (!prefixcmp(value, "refs/heads/"))
863 value += STRING_SIZE("refs/heads/");
865 if (!string_format_from(repo.remote, &from, "/%s", value))
866 repo.remote[0] = 0;
870 static void
871 set_repo_config_option(char *name, char *value, enum status_code (*cmd)(int, const char **))
873 const char *argv[SIZEOF_ARG] = { name, "=" };
874 int argc = 1 + (cmd == option_set_command);
875 enum status_code code;
877 if (!argv_from_string(argv, &argc, value))
878 code = error("Too many arguments");
879 else
880 code = cmd(argc, argv);
882 if (code != SUCCESS)
883 warn("Option 'tig.%s': %s", name, get_status_message(code));
886 static void
887 set_work_tree(const char *value)
889 char cwd[SIZEOF_STR];
891 if (!getcwd(cwd, sizeof(cwd)))
892 die("Failed to get cwd path: %s", strerror(errno));
893 if (chdir(cwd) < 0)
894 die("Failed to chdir(%s): %s", cwd, strerror(errno));
895 if (chdir(repo.git_dir) < 0)
896 die("Failed to chdir(%s): %s", repo.git_dir, strerror(errno));
897 if (!getcwd(repo.git_dir, sizeof(repo.git_dir)))
898 die("Failed to get git path: %s", strerror(errno));
899 if (chdir(value) < 0)
900 die("Failed to chdir(%s): %s", value, strerror(errno));
901 if (!getcwd(cwd, sizeof(cwd)))
902 die("Failed to get cwd path: %s", strerror(errno));
903 if (setenv("GIT_WORK_TREE", cwd, TRUE))
904 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
905 if (setenv("GIT_DIR", repo.git_dir, TRUE))
906 die("Failed to set GIT_DIR to '%s'", repo.git_dir);
907 repo.is_inside_work_tree = TRUE;
910 static void
911 parse_git_color_option(enum line_type type, char *value)
913 struct line_info *info = get_line_info(NULL, type);
914 const char *argv[SIZEOF_ARG];
915 int argc = 0;
916 bool first_color = TRUE;
917 int i;
919 if (!argv_from_string(argv, &argc, value))
920 return;
922 info->fg = COLOR_DEFAULT;
923 info->bg = COLOR_DEFAULT;
924 info->attr = 0;
926 for (i = 0; i < argc; i++) {
927 int attr = 0;
929 if (set_attribute(&attr, argv[i])) {
930 info->attr |= attr;
932 } else if (set_color(&attr, argv[i])) {
933 if (first_color)
934 info->fg = attr;
935 else
936 info->bg = attr;
937 first_color = FALSE;
942 static void
943 set_git_color_option(const char *name, char *value)
945 static const struct enum_map_entry color_option_map[] = {
946 ENUM_MAP_ENTRY("branch.current", LINE_MAIN_HEAD),
947 ENUM_MAP_ENTRY("branch.local", LINE_MAIN_REF),
948 ENUM_MAP_ENTRY("branch.plain", LINE_MAIN_REF),
949 ENUM_MAP_ENTRY("branch.remote", LINE_MAIN_REMOTE),
951 ENUM_MAP_ENTRY("diff.meta", LINE_DIFF_HEADER),
952 ENUM_MAP_ENTRY("diff.meta", LINE_DIFF_INDEX),
953 ENUM_MAP_ENTRY("diff.meta", LINE_DIFF_OLDMODE),
954 ENUM_MAP_ENTRY("diff.meta", LINE_DIFF_NEWMODE),
955 ENUM_MAP_ENTRY("diff.frag", LINE_DIFF_CHUNK),
956 ENUM_MAP_ENTRY("diff.old", LINE_DIFF_DEL),
957 ENUM_MAP_ENTRY("diff.new", LINE_DIFF_ADD),
959 //ENUM_MAP_ENTRY("diff.commit", LINE_DIFF_ADD),
961 ENUM_MAP_ENTRY("status.branch", LINE_STAT_HEAD),
962 //ENUM_MAP_ENTRY("status.nobranch", LINE_STAT_HEAD),
963 ENUM_MAP_ENTRY("status.added", LINE_STAT_STAGED),
964 ENUM_MAP_ENTRY("status.updated", LINE_STAT_STAGED),
965 ENUM_MAP_ENTRY("status.changed", LINE_STAT_UNSTAGED),
966 ENUM_MAP_ENTRY("status.untracked", LINE_STAT_UNTRACKED),
968 int type = LINE_NONE;
970 if (opt_read_git_colors && map_enum(&type, color_option_map, name)) {
971 parse_git_color_option(type, value);
975 static void
976 set_encoding(struct encoding **encoding_ref, const char *arg, bool priority)
978 if (parse_encoding(encoding_ref, arg, priority) == SUCCESS)
979 encoding_arg[0] = 0;
982 static int
983 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
985 if (!strcmp(name, "i18n.commitencoding"))
986 set_encoding(&default_encoding, value, FALSE);
988 else if (!strcmp(name, "gui.encoding"))
989 set_encoding(&default_encoding, value, TRUE);
991 else if (!strcmp(name, "core.editor"))
992 string_ncopy(opt_editor, value, valuelen);
994 else if (!strcmp(name, "core.worktree"))
995 set_work_tree(value);
997 else if (!strcmp(name, "core.abbrev"))
998 parse_int(&opt_id_width, value, 0, SIZEOF_REV - 1);
1000 else if (!prefixcmp(name, "tig.color."))
1001 set_repo_config_option(name + 10, value, option_color_command);
1003 else if (!prefixcmp(name, "tig.bind."))
1004 set_repo_config_option(name + 9, value, option_bind_command);
1006 else if (!prefixcmp(name, "tig."))
1007 set_repo_config_option(name + 4, value, option_set_command);
1009 else if (!prefixcmp(name, "color."))
1010 set_git_color_option(name + STRING_SIZE("color."), value);
1012 else if (*repo.head && !prefixcmp(name, "branch.") &&
1013 !strncmp(name + 7, repo.head, strlen(repo.head)))
1014 set_remote_branch(name + 7 + strlen(repo.head), value, valuelen);
1016 return OK;
1020 load_git_config(void)
1022 const char *config_list_argv[] = { "git", "config", "--list", NULL };
1024 return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
1027 /* vim: set ts=8 sw=8 noexpandtab: */