1 /* Copyright (c) 2006-2013 Jonas Fonseca <fonseca@diku.dk>
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.
20 static void TIG_NORETURN die(const char *err, ...) PRINTF_LIKE(1, 2);
21 static void warn(const char *msg, ...) PRINTF_LIKE(1, 2);
22 static void report(const char *msg, ...) PRINTF_LIKE(1, 2);
23 #define report_clear() report("%s", "")
25 static bool set_environment_variable(const char *name, const char *value);
26 static bool set_int_environment_variable(const char *name, int value);
36 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
38 static char *prompt_input(const char *prompt, input_handler handler, void *data);
39 static bool prompt_yesno(const char *prompt);
40 static char *read_prompt(const char *prompt);
48 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
50 #define GRAPHIC_ENUM(_) \
52 _(GRAPHIC, DEFAULT), \
55 DEFINE_ENUM(graphic, GRAPHIC_ENUM);
57 #define DATE_ENUM(_) \
64 DEFINE_ENUM(date, DATE_ENUM);
71 static inline int timecmp(const struct time *t1, const struct time *t2)
73 return t1->sec - t2->sec;
77 mkdate(const struct time *time, enum date date)
79 static char buf[DATE_WIDTH + 1];
80 static const struct enum_map reldate[] = {
81 { "second", 1, 60 * 2 },
82 { "minute", 60, 60 * 60 * 2 },
83 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
84 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
85 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
86 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 365 },
87 { "year", 60 * 60 * 24 * 365, 0 },
91 if (!date || !time || !time->sec)
94 if (date == DATE_RELATIVE) {
96 time_t date = time->sec + time->tz;
100 gettimeofday(&now, NULL);
101 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
102 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
103 if (seconds >= reldate[i].value && reldate[i].value)
106 seconds /= reldate[i].namelen;
107 if (!string_format(buf, "%ld %s%s %s",
108 seconds, reldate[i].name,
109 seconds > 1 ? "s" : "",
110 now.tv_sec >= date ? "ago" : "ahead"))
116 if (date == DATE_LOCAL) {
117 time_t date = time->sec + time->tz;
118 localtime_r(&date, &tm);
121 gmtime_r(&time->sec, &tm);
123 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
127 #define AUTHOR_ENUM(_) \
130 _(AUTHOR, ABBREVIATED), \
132 _(AUTHOR, EMAIL_USER)
134 DEFINE_ENUM(author, AUTHOR_ENUM);
141 static const struct ident unknown_ident = { "Unknown", "unknown@localhost" };
144 ident_compare(const struct ident *i1, const struct ident *i2)
147 return (!!i1) - (!!i2);
148 if (!i1->name || !i2->name)
149 return (!!i1->name) - (!!i2->name);
150 return strcmp(i1->name, i2->name);
154 get_author_initials(const char *author)
156 static char initials[AUTHOR_WIDTH * 6 + 1];
158 const char *end = strchr(author, '\0');
160 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
162 memset(initials, 0, sizeof(initials));
163 while (author < end) {
167 while (author < end && is_initial_sep(*author))
170 bytes = utf8_char_length(author, end);
171 if (bytes >= sizeof(initials) - 1 - pos)
174 initials[pos++] = *author++;
178 while (author < end && !is_initial_sep(*author)) {
179 bytes = utf8_char_length(author, end);
180 if (bytes >= sizeof(initials) - 1 - i) {
181 while (author < end && !is_initial_sep(*author))
186 initials[i++] = *author++;
197 get_email_user(const char *email)
199 static char user[AUTHOR_WIDTH * 6 + 1];
200 const char *end = strchr(email, '@');
201 int length = end ? end - email : strlen(email);
203 string_format(user, "%.*s%c", length, email, 0);
207 #define author_trim(cols) (cols == 0 || cols > 10)
210 mkauthor(const struct ident *ident, int cols, enum author author)
212 bool trim = author_trim(cols);
213 bool abbreviate = author == AUTHOR_ABBREVIATED || !trim;
215 if (author == AUTHOR_NO || !ident)
217 if (author == AUTHOR_EMAIL && ident->email)
219 if (author == AUTHOR_EMAIL_USER && ident->email)
220 return get_email_user(ident->email);
221 if (abbreviate && ident->name)
222 return get_author_initials(ident->name);
231 else if (S_ISLNK(mode))
233 else if (S_ISGITLINK(mode))
235 else if (S_ISREG(mode) && mode & S_IXUSR)
237 else if (S_ISREG(mode))
252 tmp = getenv("TMPDIR");
254 tmp = getenv("TEMP");
266 #define FILENAME_ENUM(_) \
268 _(FILENAME, ALWAYS), \
271 DEFINE_ENUM(filename, FILENAME_ENUM);
273 #define IGNORE_SPACE_ENUM(_) \
274 _(IGNORE_SPACE, NO), \
275 _(IGNORE_SPACE, ALL), \
276 _(IGNORE_SPACE, SOME), \
277 _(IGNORE_SPACE, AT_EOL)
279 DEFINE_ENUM(ignore_space, IGNORE_SPACE_ENUM);
281 #define COMMIT_ORDER_ENUM(_) \
282 _(COMMIT_ORDER, DEFAULT), \
283 _(COMMIT_ORDER, TOPO), \
284 _(COMMIT_ORDER, DATE), \
285 _(COMMIT_ORDER, REVERSE)
287 DEFINE_ENUM(commit_order, COMMIT_ORDER_ENUM);
289 #define VIEW_INFO(_) \
290 _(MAIN, main, ref_head), \
291 _(DIFF, diff, ref_commit), \
292 _(LOG, log, ref_head), \
293 _(TREE, tree, ref_commit), \
294 _(BLOB, blob, ref_blob), \
295 _(BLAME, blame, ref_commit), \
296 _(BRANCH, branch, ref_head), \
298 _(PAGER, pager, ""), \
299 _(STATUS, status, "status"), \
300 _(STAGE, stage, ref_status)
302 static struct encoding *
303 get_path_encoding(const char *path, struct encoding *default_encoding)
305 const char *check_attr_argv[] = {
306 "git", "check-attr", "encoding", "--", path, NULL
308 char buf[SIZEOF_STR];
311 /* <path>: encoding: <encoding> */
313 if (!*path || !io_run_buf(check_attr_argv, buf, sizeof(buf))
314 || !(encoding = strstr(buf, ENCODING_SEP)))
315 return default_encoding;
317 encoding += STRING_SIZE(ENCODING_SEP);
318 if (!strcmp(encoding, ENCODING_UTF8)
319 || !strcmp(encoding, "unspecified")
320 || !strcmp(encoding, "set"))
321 return default_encoding;
323 return encoding_open(encoding);
330 #define VIEW_REQ(id, name, ref) REQ_(VIEW_##id, "Show " #name " view")
333 REQ_GROUP("View switching") \
334 VIEW_INFO(VIEW_REQ), \
336 REQ_GROUP("View manipulation") \
337 REQ_(ENTER, "Enter current line and scroll"), \
338 REQ_(NEXT, "Move to next"), \
339 REQ_(PREVIOUS, "Move to previous"), \
340 REQ_(PARENT, "Move to parent"), \
341 REQ_(VIEW_NEXT, "Move focus to next view"), \
342 REQ_(REFRESH, "Reload and refresh"), \
343 REQ_(MAXIMIZE, "Maximize the current view"), \
344 REQ_(VIEW_CLOSE, "Close the current view"), \
345 REQ_(QUIT, "Close all views and quit"), \
347 REQ_GROUP("View specific requests") \
348 REQ_(STATUS_UPDATE, "Update file status"), \
349 REQ_(STATUS_REVERT, "Revert file changes"), \
350 REQ_(STATUS_MERGE, "Merge file using external tool"), \
351 REQ_(STAGE_UPDATE_LINE, "Update single line"), \
352 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
353 REQ_(DIFF_CONTEXT_DOWN, "Decrease the diff context"), \
354 REQ_(DIFF_CONTEXT_UP, "Increase the diff context"), \
356 REQ_GROUP("Cursor navigation") \
357 REQ_(MOVE_UP, "Move cursor one line up"), \
358 REQ_(MOVE_DOWN, "Move cursor one line down"), \
359 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
360 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
361 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
362 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
364 REQ_GROUP("Scrolling") \
365 REQ_(SCROLL_FIRST_COL, "Scroll to the first line columns"), \
366 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
367 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
368 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
369 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
370 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
371 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
373 REQ_GROUP("Searching") \
374 REQ_(SEARCH, "Search the view"), \
375 REQ_(SEARCH_BACK, "Search backwards in the view"), \
376 REQ_(FIND_NEXT, "Find next search match"), \
377 REQ_(FIND_PREV, "Find previous search match"), \
379 REQ_GROUP("Option manipulation") \
380 REQ_(OPTIONS, "Open option menu"), \
381 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
382 REQ_(TOGGLE_DATE, "Toggle date display"), \
383 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
384 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
385 REQ_(TOGGLE_GRAPHIC, "Toggle (line) graphics mode"), \
386 REQ_(TOGGLE_FILENAME, "Toggle file name display"), \
387 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
388 REQ_(TOGGLE_CHANGES, "Toggle local changes display in the main view"), \
389 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
390 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
391 REQ_(TOGGLE_IGNORE_SPACE, "Toggle ignoring whitespace in diffs"), \
392 REQ_(TOGGLE_COMMIT_ORDER, "Toggle commit ordering"), \
393 REQ_(TOGGLE_ID, "Toggle commit ID display"), \
394 REQ_(TOGGLE_FILES, "Toggle file filtering"), \
395 REQ_(TOGGLE_TITLE_OVERFLOW, "Toggle highlighting of commit title overflow"), \
398 REQ_(PROMPT, "Bring up the prompt"), \
399 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
400 REQ_(SHOW_VERSION, "Show version information"), \
401 REQ_(STOP_LOADING, "Stop all loading views"), \
402 REQ_(EDIT, "Open in editor"), \
403 REQ_(NONE, "Do nothing")
406 /* User action requests. */
408 #define REQ_GROUP(help)
409 #define REQ_(req, help) REQ_##req
411 /* Offset all requests to avoid conflicts with ncurses getch values. */
412 REQ_UNKNOWN = KEY_MAX + 1,
416 /* Internal requests. */
423 struct request_info {
424 enum request request;
430 static const struct request_info req_info[] = {
431 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
432 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
439 get_request(const char *name)
441 int namelen = strlen(name);
444 for (i = 0; i < ARRAY_SIZE(req_info); i++)
445 if (enum_equals(req_info[i], name, namelen))
446 return req_info[i].request;
456 /* Option and state variables. */
457 static enum graphic opt_line_graphics = GRAPHIC_DEFAULT;
458 static enum date opt_date = DATE_DEFAULT;
459 static enum author opt_author = AUTHOR_FULL;
460 static enum filename opt_filename = FILENAME_AUTO;
461 static bool opt_rev_graph = TRUE;
462 static bool opt_line_number = FALSE;
463 static bool opt_show_refs = TRUE;
464 static bool opt_show_changes = TRUE;
465 static bool opt_untracked_dirs_content = TRUE;
466 static bool opt_read_git_colors = TRUE;
467 static bool opt_wrap_lines = FALSE;
468 static bool opt_ignore_case = FALSE;
469 static bool opt_stdin = FALSE;
470 static bool opt_focus_child = TRUE;
471 static int opt_diff_context = 3;
472 static char opt_diff_context_arg[9] = "";
473 static enum ignore_space opt_ignore_space = IGNORE_SPACE_NO;
474 static char opt_ignore_space_arg[22] = "";
475 static enum commit_order opt_commit_order = COMMIT_ORDER_DEFAULT;
476 static char opt_commit_order_arg[22] = "";
477 static bool opt_notes = TRUE;
478 static char opt_notes_arg[SIZEOF_STR] = "--show-notes";
479 static int opt_num_interval = 5;
480 static double opt_hscroll = 0.50;
481 static double opt_scale_split_view = 2.0 / 3.0;
482 static double opt_scale_vsplit_view = 0.5;
483 static bool opt_vsplit = FALSE;
484 static int opt_tab_size = 8;
485 static int opt_author_width = AUTHOR_WIDTH;
486 static int opt_filename_width = FILENAME_WIDTH;
487 static char opt_path[SIZEOF_STR] = "";
488 static char opt_file[SIZEOF_STR] = "";
489 static char opt_ref[SIZEOF_REF] = "";
490 static unsigned long opt_goto_line = 0;
491 static char opt_head[SIZEOF_REF] = "";
492 static char opt_remote[SIZEOF_REF] = "";
493 static struct encoding *opt_encoding = NULL;
494 static char opt_encoding_arg[SIZEOF_STR] = ENCODING_ARG;
495 static iconv_t opt_iconv_out = ICONV_NONE;
496 static char opt_search[SIZEOF_STR] = "";
497 static char opt_cdup[SIZEOF_STR] = "";
498 static char opt_prefix[SIZEOF_STR] = "";
499 static char opt_git_dir[SIZEOF_STR] = "";
500 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
501 static char opt_editor[SIZEOF_STR] = "";
502 static bool opt_editor_lineno = TRUE;
503 static FILE *opt_tty = NULL;
504 static const char **opt_diff_argv = NULL;
505 static const char **opt_rev_argv = NULL;
506 static const char **opt_file_argv = NULL;
507 static const char **opt_blame_argv = NULL;
508 static int opt_lineno = 0;
509 static bool opt_show_id = FALSE;
510 static int opt_id_cols = ID_WIDTH;
511 static bool opt_file_filter = TRUE;
512 static bool opt_show_title_overflow = FALSE;
513 static int opt_title_overflow = 50;
515 #define is_initial_commit() (!get_ref_head())
516 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strncmp(rev, get_ref_head()->id, SIZEOF_REV - 1)))
517 #define load_refs() reload_refs(opt_git_dir, opt_remote, opt_head, sizeof(opt_head))
520 update_diff_context_arg(int diff_context)
522 if (!string_format(opt_diff_context_arg, "-U%u", diff_context))
523 string_ncopy(opt_diff_context_arg, "-U3", 3);
527 update_ignore_space_arg()
529 if (opt_ignore_space == IGNORE_SPACE_ALL) {
530 string_copy(opt_ignore_space_arg, "--ignore-all-space");
531 } else if (opt_ignore_space == IGNORE_SPACE_SOME) {
532 string_copy(opt_ignore_space_arg, "--ignore-space-change");
533 } else if (opt_ignore_space == IGNORE_SPACE_AT_EOL) {
534 string_copy(opt_ignore_space_arg, "--ignore-space-at-eol");
536 string_copy(opt_ignore_space_arg, "");
541 update_commit_order_arg()
543 if (opt_commit_order == COMMIT_ORDER_TOPO) {
544 string_copy(opt_commit_order_arg, "--topo-order");
545 } else if (opt_commit_order == COMMIT_ORDER_DATE) {
546 string_copy(opt_commit_order_arg, "--date-order");
547 } else if (opt_commit_order == COMMIT_ORDER_REVERSE) {
548 string_copy(opt_commit_order_arg, "--reverse");
550 string_copy(opt_commit_order_arg, "");
558 string_copy(opt_notes_arg, "--show-notes");
560 /* Notes are disabled by default when passing --pretty args. */
561 string_copy(opt_notes_arg, "");
566 * Line-oriented content detection.
570 LINE(DIFF_HEADER, "diff --", COLOR_YELLOW, COLOR_DEFAULT, 0), \
571 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
572 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
573 LINE(DIFF_ADD2, " +", COLOR_GREEN, COLOR_DEFAULT, 0), \
574 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
575 LINE(DIFF_DEL2, " -", COLOR_RED, COLOR_DEFAULT, 0), \
576 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
577 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
578 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
579 LINE(DIFF_DELETED_FILE_MODE, \
580 "deleted file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
581 LINE(DIFF_COPY_FROM, "copy from ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
582 LINE(DIFF_COPY_TO, "copy to ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
583 LINE(DIFF_RENAME_FROM, "rename from ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
584 LINE(DIFF_RENAME_TO, "rename to ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
585 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
586 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
587 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
588 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
589 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
590 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
591 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
592 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
593 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
594 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
595 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
596 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
597 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
598 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
599 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
600 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
601 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
602 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
603 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
604 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
605 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
606 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
607 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
608 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
609 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
610 LINE(ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
611 LINE(OVERFLOW, "", COLOR_RED, COLOR_DEFAULT, 0), \
612 LINE(FILENAME, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
613 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
614 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
615 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
616 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
617 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
618 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
619 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
620 LINE(MAIN_REPLACE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
621 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
622 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
623 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
624 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
625 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
626 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
627 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
628 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
629 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
630 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
631 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
632 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
633 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
634 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
635 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
636 LINE(DIFF_STAT, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
637 LINE(PALETTE_0, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
638 LINE(PALETTE_1, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
639 LINE(PALETTE_2, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
640 LINE(PALETTE_3, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
641 LINE(PALETTE_4, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
642 LINE(PALETTE_5, "", COLOR_WHITE, COLOR_DEFAULT, 0), \
643 LINE(PALETTE_6, "", COLOR_RED, COLOR_DEFAULT, 0), \
644 LINE(GRAPH_COMMIT, "", COLOR_BLUE, COLOR_DEFAULT, 0)
647 #define LINE(type, line, fg, bg, attr) \
655 const char *name; /* Option name. */
656 int namelen; /* Size of option name. */
657 const char *line; /* The start of line to match. */
658 int linelen; /* Size of string to match. */
659 int fg, bg, attr; /* Color and text attributes for the lines. */
663 static struct line_info line_info[] = {
664 #define LINE(type, line, fg, bg, attr) \
665 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
670 static struct line_info **color_pair;
671 static size_t color_pairs;
673 static struct line_info *custom_color;
674 static size_t custom_colors;
676 DEFINE_ALLOCATOR(realloc_custom_color, struct line_info, 8)
677 DEFINE_ALLOCATOR(realloc_color_pair, struct line_info *, 8)
679 #define TO_CUSTOM_COLOR_TYPE(type) (LINE_NONE + 1 + (type))
680 #define TO_CUSTOM_COLOR_OFFSET(type) ((type) - LINE_NONE - 1)
682 /* Color IDs must be 1 or higher. [GH #15] */
683 #define COLOR_ID(line_type) ((line_type) + 1)
685 static enum line_type
686 get_line_type(const char *line)
688 int linelen = strlen(line);
691 for (type = 0; type < custom_colors; type++)
692 /* Case insensitive search matches Signed-off-by lines better. */
693 if (linelen >= custom_color[type].linelen &&
694 !strncasecmp(custom_color[type].line, line, custom_color[type].linelen))
695 return TO_CUSTOM_COLOR_TYPE(type);
697 for (type = 0; type < ARRAY_SIZE(line_info); type++)
698 /* Case insensitive search matches Signed-off-by lines better. */
699 if (linelen >= line_info[type].linelen &&
700 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
706 static enum line_type
707 get_line_type_from_ref(const struct ref *ref)
710 return LINE_MAIN_HEAD;
712 return LINE_MAIN_LOCAL_TAG;
714 return LINE_MAIN_TAG;
715 else if (ref->tracked)
716 return LINE_MAIN_TRACKED;
717 else if (ref->remote)
718 return LINE_MAIN_REMOTE;
719 else if (ref->replace)
720 return LINE_MAIN_REPLACE;
722 return LINE_MAIN_REF;
725 static inline struct line_info *
726 get_line(enum line_type type)
728 if (type > LINE_NONE) {
729 assert(TO_CUSTOM_COLOR_OFFSET(type) < custom_colors);
730 return &custom_color[TO_CUSTOM_COLOR_OFFSET(type)];
732 assert(type < ARRAY_SIZE(line_info));
733 return &line_info[type];
738 get_line_color(enum line_type type)
740 return COLOR_ID(get_line(type)->color_pair);
744 get_line_attr(enum line_type type)
746 struct line_info *info = get_line(type);
748 return COLOR_PAIR(COLOR_ID(info->color_pair)) | info->attr;
751 static struct line_info *
752 get_line_info(const char *name)
754 size_t namelen = strlen(name);
757 for (type = 0; type < ARRAY_SIZE(line_info); type++)
758 if (enum_equals(line_info[type], name, namelen))
759 return &line_info[type];
764 static struct line_info *
765 add_custom_color(const char *quoted_line)
767 struct line_info *info;
771 if (!realloc_custom_color(&custom_color, custom_colors, 1))
772 die("Failed to alloc custom line info");
774 linelen = strlen(quoted_line) - 1;
775 line = malloc(linelen);
779 strncpy(line, quoted_line + 1, linelen);
780 line[linelen - 1] = 0;
782 info = &custom_color[custom_colors++];
783 info->name = info->line = line;
784 info->namelen = info->linelen = strlen(line);
790 init_line_info_color_pair(struct line_info *info, enum line_type type,
791 int default_bg, int default_fg)
793 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
794 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
797 for (i = 0; i < color_pairs; i++) {
798 if (color_pair[i]->fg == info->fg && color_pair[i]->bg == info->bg) {
799 info->color_pair = i;
804 if (!realloc_color_pair(&color_pair, color_pairs, 1))
805 die("Failed to alloc color pair");
807 color_pair[color_pairs] = info;
808 info->color_pair = color_pairs++;
809 init_pair(COLOR_ID(info->color_pair), fg, bg);
815 int default_bg = line_info[LINE_DEFAULT].bg;
816 int default_fg = line_info[LINE_DEFAULT].fg;
821 if (assume_default_colors(default_fg, default_bg) == ERR) {
822 default_bg = COLOR_BLACK;
823 default_fg = COLOR_WHITE;
826 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
827 struct line_info *info = &line_info[type];
829 init_line_info_color_pair(info, type, default_bg, default_fg);
832 for (type = 0; type < custom_colors; type++) {
833 struct line_info *info = &custom_color[type];
835 init_line_info_color_pair(info, TO_CUSTOM_COLOR_TYPE(type),
836 default_bg, default_fg);
842 unsigned int lineno:24;
845 unsigned int selected:1;
846 unsigned int dirty:1;
847 unsigned int cleareol:1;
848 unsigned int dont_free:1;
849 unsigned int wrapped:1;
851 unsigned int user_flags:6;
852 void *data; /* User data */
862 enum request request;
865 static struct keybinding default_keybindings[] = {
867 { 'm', REQ_VIEW_MAIN },
868 { 'd', REQ_VIEW_DIFF },
869 { 'l', REQ_VIEW_LOG },
870 { 't', REQ_VIEW_TREE },
871 { 'f', REQ_VIEW_BLOB },
872 { 'B', REQ_VIEW_BLAME },
873 { 'H', REQ_VIEW_BRANCH },
874 { 'p', REQ_VIEW_PAGER },
875 { 'h', REQ_VIEW_HELP },
876 { 'S', REQ_VIEW_STATUS },
877 { 'c', REQ_VIEW_STAGE },
879 /* View manipulation */
880 { 'q', REQ_VIEW_CLOSE },
881 { KEY_TAB, REQ_VIEW_NEXT },
882 { KEY_RETURN, REQ_ENTER },
883 { KEY_UP, REQ_PREVIOUS },
884 { KEY_CTL('P'), REQ_PREVIOUS },
885 { KEY_DOWN, REQ_NEXT },
886 { KEY_CTL('N'), REQ_NEXT },
887 { 'R', REQ_REFRESH },
888 { KEY_F(5), REQ_REFRESH },
889 { 'O', REQ_MAXIMIZE },
893 { 'u', REQ_STATUS_UPDATE },
894 { '!', REQ_STATUS_REVERT },
895 { 'M', REQ_STATUS_MERGE },
896 { '1', REQ_STAGE_UPDATE_LINE },
897 { '@', REQ_STAGE_NEXT },
898 { '[', REQ_DIFF_CONTEXT_DOWN },
899 { ']', REQ_DIFF_CONTEXT_UP },
901 /* Cursor navigation */
902 { 'k', REQ_MOVE_UP },
903 { 'j', REQ_MOVE_DOWN },
904 { KEY_HOME, REQ_MOVE_FIRST_LINE },
905 { KEY_END, REQ_MOVE_LAST_LINE },
906 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
907 { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
908 { ' ', REQ_MOVE_PAGE_DOWN },
909 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
910 { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
911 { 'b', REQ_MOVE_PAGE_UP },
912 { '-', REQ_MOVE_PAGE_UP },
915 { '|', REQ_SCROLL_FIRST_COL },
916 { KEY_LEFT, REQ_SCROLL_LEFT },
917 { KEY_RIGHT, REQ_SCROLL_RIGHT },
918 { KEY_IC, REQ_SCROLL_LINE_UP },
919 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
920 { KEY_DC, REQ_SCROLL_LINE_DOWN },
921 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
922 { 'w', REQ_SCROLL_PAGE_UP },
923 { 's', REQ_SCROLL_PAGE_DOWN },
927 { '?', REQ_SEARCH_BACK },
928 { 'n', REQ_FIND_NEXT },
929 { 'N', REQ_FIND_PREV },
933 { 'z', REQ_STOP_LOADING },
934 { 'v', REQ_SHOW_VERSION },
935 { 'r', REQ_SCREEN_REDRAW },
936 { KEY_CTL('L'), REQ_SCREEN_REDRAW },
937 { 'o', REQ_OPTIONS },
938 { '.', REQ_TOGGLE_LINENO },
939 { 'D', REQ_TOGGLE_DATE },
940 { 'A', REQ_TOGGLE_AUTHOR },
941 { 'g', REQ_TOGGLE_REV_GRAPH },
942 { '~', REQ_TOGGLE_GRAPHIC },
943 { '#', REQ_TOGGLE_FILENAME },
944 { 'F', REQ_TOGGLE_REFS },
945 { 'I', REQ_TOGGLE_SORT_ORDER },
946 { 'i', REQ_TOGGLE_SORT_FIELD },
947 { 'W', REQ_TOGGLE_IGNORE_SPACE },
948 { 'X', REQ_TOGGLE_ID },
949 { '%', REQ_TOGGLE_FILES },
950 { '$', REQ_TOGGLE_TITLE_OVERFLOW },
958 struct keybinding *data;
963 static struct keymap generic_keymap = { "generic" };
964 #define is_generic_keymap(keymap) ((keymap) == &generic_keymap)
966 static struct keymap *keymaps = &generic_keymap;
969 add_keymap(struct keymap *keymap)
971 keymap->next = keymaps;
975 static struct keymap *
976 get_keymap(const char *name)
978 struct keymap *keymap = keymaps;
981 if (!strcasecmp(keymap->name, name))
983 keymap = keymap->next;
991 add_keybinding(struct keymap *table, enum request request, int key)
995 for (i = 0; i < table->size; i++) {
996 if (table->data[i].alias == key) {
997 table->data[i].request = request;
1002 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1004 die("Failed to allocate keybinding");
1005 table->data[table->size].alias = key;
1006 table->data[table->size++].request = request;
1008 if (request == REQ_NONE && is_generic_keymap(table)) {
1011 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1012 if (default_keybindings[i].alias == key)
1013 default_keybindings[i].request = REQ_NONE;
1017 /* Looks for a key binding first in the given map, then in the generic map, and
1018 * lastly in the default keybindings. */
1020 get_keybinding(struct keymap *keymap, int key)
1024 for (i = 0; i < keymap->size; i++)
1025 if (keymap->data[i].alias == key)
1026 return keymap->data[i].request;
1028 for (i = 0; i < generic_keymap.size; i++)
1029 if (generic_keymap.data[i].alias == key)
1030 return generic_keymap.data[i].request;
1032 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1033 if (default_keybindings[i].alias == key)
1034 return default_keybindings[i].request;
1036 return (enum request) key;
1045 static const struct key key_table[] = {
1046 { "Enter", KEY_RETURN },
1048 { "Backspace", KEY_BACKSPACE },
1050 { "Escape", KEY_ESC },
1051 { "Left", KEY_LEFT },
1052 { "Right", KEY_RIGHT },
1054 { "Down", KEY_DOWN },
1055 { "Insert", KEY_IC },
1056 { "Delete", KEY_DC },
1058 { "Home", KEY_HOME },
1060 { "PageUp", KEY_PPAGE },
1061 { "PageDown", KEY_NPAGE },
1071 { "F10", KEY_F(10) },
1072 { "F11", KEY_F(11) },
1073 { "F12", KEY_F(12) },
1077 get_key_value(const char *name)
1081 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1082 if (!strcasecmp(key_table[i].name, name))
1083 return key_table[i].value;
1085 if (strlen(name) == 3 && name[0] == '^' && name[1] == '[' && isprint(*name))
1086 return (int)name[2] + 0x80;
1087 if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1088 return (int)name[1] & 0x1f;
1089 if (strlen(name) == 1 && isprint(*name))
1095 get_key_name(int key_value)
1097 static char key_char[] = "'X'\0";
1098 const char *seq = NULL;
1101 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1102 if (key_table[key].value == key_value)
1103 seq = key_table[key].name;
1105 if (seq == NULL && key_value < 0x7f) {
1106 char *s = key_char + 1;
1108 if (key_value >= 0x20) {
1112 *s++ = 0x40 | (key_value & 0x1f);
1119 return seq ? seq : "(no key)";
1123 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1125 const char *sep = *pos > 0 ? ", " : "";
1126 const char *keyname = get_key_name(keybinding->alias);
1128 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1132 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1133 struct keymap *keymap, bool all)
1137 for (i = 0; i < keymap->size; i++) {
1138 if (keymap->data[i].request == request) {
1139 if (!append_key(buf, pos, &keymap->data[i]))
1149 #define get_view_key(view, request) get_keys(&(view)->ops->keymap, request, FALSE)
1152 get_keys(struct keymap *keymap, enum request request, bool all)
1154 static char buf[BUFSIZ];
1160 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1161 return "Too many keybindings!";
1162 if (pos > 0 && !all)
1165 if (!is_generic_keymap(keymap)) {
1166 /* Only the generic keymap includes the default keybindings when
1167 * listing all keys. */
1171 if (!append_keymap_request_keys(buf, &pos, request, &generic_keymap, all))
1172 return "Too many keybindings!";
1177 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1178 if (default_keybindings[i].request == request) {
1179 if (!append_key(buf, &pos, &default_keybindings[i]))
1180 return "Too many keybindings!";
1189 enum run_request_flag {
1190 RUN_REQUEST_DEFAULT = 0,
1191 RUN_REQUEST_FORCE = 1,
1192 RUN_REQUEST_SILENT = 2,
1193 RUN_REQUEST_CONFIRM = 4,
1194 RUN_REQUEST_EXIT = 8,
1195 RUN_REQUEST_INTERNAL = 16,
1198 struct run_request {
1199 struct keymap *keymap;
1208 static struct run_request *run_request;
1209 static size_t run_requests;
1211 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1214 add_run_request(struct keymap *keymap, int key, const char **argv, enum run_request_flag flags)
1216 bool force = flags & RUN_REQUEST_FORCE;
1217 struct run_request *req;
1219 if (!force && get_keybinding(keymap, key) != key)
1222 if (!realloc_run_requests(&run_request, run_requests, 1))
1225 if (!argv_copy(&run_request[run_requests].argv, argv))
1228 req = &run_request[run_requests++];
1229 req->silent = flags & RUN_REQUEST_SILENT;
1230 req->confirm = flags & RUN_REQUEST_CONFIRM;
1231 req->exit = flags & RUN_REQUEST_EXIT;
1232 req->internal = flags & RUN_REQUEST_INTERNAL;
1233 req->keymap = keymap;
1236 add_keybinding(keymap, REQ_NONE + run_requests, key);
1240 static struct run_request *
1241 get_run_request(enum request request)
1243 if (request <= REQ_NONE || request > REQ_NONE + run_requests)
1245 return &run_request[request - REQ_NONE - 1];
1249 add_builtin_run_requests(void)
1251 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1252 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1253 const char *commit[] = { "git", "commit", NULL };
1254 const char *gc[] = { "git", "gc", NULL };
1256 add_run_request(get_keymap("main"), 'C', cherry_pick, RUN_REQUEST_CONFIRM);
1257 add_run_request(get_keymap("status"), 'C', commit, RUN_REQUEST_DEFAULT);
1258 add_run_request(get_keymap("branch"), 'C', checkout, RUN_REQUEST_CONFIRM);
1259 add_run_request(get_keymap("generic"), 'G', gc, RUN_REQUEST_CONFIRM);
1263 * User config file handling.
1266 #define OPT_ERR_INFO \
1267 OPT_ERR_(INTEGER_VALUE_OUT_OF_BOUND, "Integer value out of bound"), \
1268 OPT_ERR_(INVALID_STEP_VALUE, "Invalid step value"), \
1269 OPT_ERR_(NO_OPTION_VALUE, "No option value"), \
1270 OPT_ERR_(NO_VALUE_ASSIGNED, "No value assigned"), \
1271 OPT_ERR_(OBSOLETE_REQUEST_NAME, "Obsolete request name"), \
1272 OPT_ERR_(OUT_OF_MEMORY, "Out of memory"), \
1273 OPT_ERR_(TOO_MANY_OPTION_ARGUMENTS, "Too many option arguments"), \
1274 OPT_ERR_(FILE_DOES_NOT_EXIST, "File does not exist"), \
1275 OPT_ERR_(UNKNOWN_ATTRIBUTE, "Unknown attribute"), \
1276 OPT_ERR_(UNKNOWN_COLOR, "Unknown color"), \
1277 OPT_ERR_(UNKNOWN_COLOR_NAME, "Unknown color name"), \
1278 OPT_ERR_(UNKNOWN_KEY, "Unknown key"), \
1279 OPT_ERR_(UNKNOWN_KEY_MAP, "Unknown key map"), \
1280 OPT_ERR_(UNKNOWN_OPTION_COMMAND, "Unknown option command"), \
1281 OPT_ERR_(UNKNOWN_REQUEST_NAME, "Unknown request name"), \
1282 OPT_ERR_(UNKNOWN_VARIABLE_NAME, "Unknown variable name"), \
1283 OPT_ERR_(UNMATCHED_QUOTATION, "Unmatched quotation"), \
1284 OPT_ERR_(WRONG_NUMBER_OF_ARGUMENTS, "Wrong number of arguments"),
1287 #define OPT_ERR_(name, msg) OPT_ERR_ ## name
1293 static const char *option_errors[] = {
1294 #define OPT_ERR_(name, msg) msg
1299 static const struct enum_map color_map[] = {
1300 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1312 static const struct enum_map attr_map[] = {
1313 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1320 ATTR_MAP(UNDERLINE),
1323 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1325 static enum option_code
1326 parse_step(double *opt, const char *arg)
1329 if (!strchr(arg, '%'))
1332 /* "Shift down" so 100% and 1 does not conflict. */
1333 *opt = (*opt - 1) / 100;
1336 return OPT_ERR_INVALID_STEP_VALUE;
1340 return OPT_ERR_INVALID_STEP_VALUE;
1345 static enum option_code
1346 parse_int(int *opt, const char *arg, int min, int max)
1348 int value = atoi(arg);
1350 if (min <= value && value <= max) {
1355 return OPT_ERR_INTEGER_VALUE_OUT_OF_BOUND;
1358 #define parse_id(opt, arg) \
1359 parse_int(opt, arg, 4, SIZEOF_REV - 1)
1362 set_color(int *color, const char *name)
1364 if (map_enum(color, color_map, name))
1366 if (!prefixcmp(name, "color"))
1367 return parse_int(color, name + 5, 0, 255) == OPT_OK;
1368 /* Used when reading git colors. Git expects a plain int w/o prefix. */
1369 return parse_int(color, name, 0, 255) == OPT_OK;
1372 /* Wants: object fgcolor bgcolor [attribute] */
1373 static enum option_code
1374 option_color_command(int argc, const char *argv[])
1376 struct line_info *info;
1379 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1381 if (*argv[0] == '"' || *argv[0] == '\'') {
1382 info = add_custom_color(argv[0]);
1384 info = get_line_info(argv[0]);
1387 static const struct enum_map obsolete[] = {
1388 ENUM_MAP("main-delim", LINE_DELIMITER),
1389 ENUM_MAP("main-date", LINE_DATE),
1390 ENUM_MAP("main-author", LINE_AUTHOR),
1391 ENUM_MAP("blame-id", LINE_ID),
1395 if (!map_enum(&index, obsolete, argv[0]))
1396 return OPT_ERR_UNKNOWN_COLOR_NAME;
1397 info = &line_info[index];
1400 if (!set_color(&info->fg, argv[1]) ||
1401 !set_color(&info->bg, argv[2]))
1402 return OPT_ERR_UNKNOWN_COLOR;
1405 while (argc-- > 3) {
1408 if (!set_attribute(&attr, argv[argc]))
1409 return OPT_ERR_UNKNOWN_ATTRIBUTE;
1416 static enum option_code
1417 parse_bool_matched(bool *opt, const char *arg, bool *matched)
1419 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1422 *matched = *opt || (!strcmp(arg, "0") || !strcmp(arg, "false") || !strcmp(arg, "no"));
1426 #define parse_bool(opt, arg) parse_bool_matched(opt, arg, NULL)
1428 static enum option_code
1429 parse_enum_do(unsigned int *opt, const char *arg,
1430 const struct enum_map *map, size_t map_size)
1434 assert(map_size > 1);
1436 if (map_enum_do(map, map_size, (int *) opt, arg))
1439 parse_bool(&is_true, arg);
1440 *opt = is_true ? map[1].value : map[0].value;
1444 #define parse_enum(opt, arg, map) \
1445 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1447 static enum option_code
1448 parse_string(char *opt, const char *arg, size_t optsize)
1450 int arglen = strlen(arg);
1455 if (arglen == 1 || arg[arglen - 1] != arg[0])
1456 return OPT_ERR_UNMATCHED_QUOTATION;
1457 arg += 1; arglen -= 2;
1459 string_ncopy_do(opt, optsize, arg, arglen);
1464 static enum option_code
1465 parse_encoding(struct encoding **encoding_ref, const char *arg, bool priority)
1467 char buf[SIZEOF_STR];
1468 enum option_code code = parse_string(buf, arg, sizeof(buf));
1470 if (code == OPT_OK) {
1471 struct encoding *encoding = *encoding_ref;
1473 if (encoding && !priority)
1475 encoding = encoding_open(buf);
1477 *encoding_ref = encoding;
1483 static enum option_code
1484 parse_args(const char ***args, const char *argv[])
1486 if (!argv_copy(args, argv))
1487 return OPT_ERR_OUT_OF_MEMORY;
1491 /* Wants: name = value */
1492 static enum option_code
1493 option_set_command(int argc, const char *argv[])
1496 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1498 if (strcmp(argv[1], "="))
1499 return OPT_ERR_NO_VALUE_ASSIGNED;
1501 if (!strcmp(argv[0], "blame-options"))
1502 return parse_args(&opt_blame_argv, argv + 2);
1504 if (!strcmp(argv[0], "diff-options"))
1505 return parse_args(&opt_diff_argv, argv + 2);
1508 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1510 if (!strcmp(argv[0], "show-author"))
1511 return parse_enum(&opt_author, argv[2], author_map);
1513 if (!strcmp(argv[0], "show-date"))
1514 return parse_enum(&opt_date, argv[2], date_map);
1516 if (!strcmp(argv[0], "show-rev-graph"))
1517 return parse_bool(&opt_rev_graph, argv[2]);
1519 if (!strcmp(argv[0], "show-refs"))
1520 return parse_bool(&opt_show_refs, argv[2]);
1522 if (!strcmp(argv[0], "show-changes"))
1523 return parse_bool(&opt_show_changes, argv[2]);
1525 if (!strcmp(argv[0], "show-notes")) {
1526 bool matched = FALSE;
1527 enum option_code res = parse_bool_matched(&opt_notes, argv[2], &matched);
1529 if (res == OPT_OK && matched) {
1535 strcpy(opt_notes_arg, "--show-notes=");
1536 res = parse_string(opt_notes_arg + 8, argv[2],
1537 sizeof(opt_notes_arg) - 8);
1538 if (res == OPT_OK && opt_notes_arg[8] == '\0')
1539 opt_notes_arg[7] = '\0';
1543 if (!strcmp(argv[0], "show-line-numbers"))
1544 return parse_bool(&opt_line_number, argv[2]);
1546 if (!strcmp(argv[0], "line-graphics"))
1547 return parse_enum(&opt_line_graphics, argv[2], graphic_map);
1549 if (!strcmp(argv[0], "line-number-interval"))
1550 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1552 if (!strcmp(argv[0], "author-width"))
1553 return parse_int(&opt_author_width, argv[2], 0, 1024);
1555 if (!strcmp(argv[0], "filename-width"))
1556 return parse_int(&opt_filename_width, argv[2], 0, 1024);
1558 if (!strcmp(argv[0], "show-filename"))
1559 return parse_enum(&opt_filename, argv[2], filename_map);
1561 if (!strcmp(argv[0], "horizontal-scroll"))
1562 return parse_step(&opt_hscroll, argv[2]);
1564 if (!strcmp(argv[0], "split-view-height"))
1565 return parse_step(&opt_scale_split_view, argv[2]);
1567 if (!strcmp(argv[0], "vertical-split"))
1568 return parse_bool(&opt_vsplit, argv[2]);
1570 if (!strcmp(argv[0], "tab-size"))
1571 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1573 if (!strcmp(argv[0], "diff-context")) {
1574 enum option_code code = parse_int(&opt_diff_context, argv[2], 0, 999999);
1577 update_diff_context_arg(opt_diff_context);
1581 if (!strcmp(argv[0], "ignore-space")) {
1582 enum option_code code = parse_enum(&opt_ignore_space, argv[2], ignore_space_map);
1585 update_ignore_space_arg();
1589 if (!strcmp(argv[0], "commit-order")) {
1590 enum option_code code = parse_enum(&opt_commit_order, argv[2], commit_order_map);
1593 update_commit_order_arg();
1597 if (!strcmp(argv[0], "status-untracked-dirs"))
1598 return parse_bool(&opt_untracked_dirs_content, argv[2]);
1600 if (!strcmp(argv[0], "read-git-colors"))
1601 return parse_bool(&opt_read_git_colors, argv[2]);
1603 if (!strcmp(argv[0], "ignore-case"))
1604 return parse_bool(&opt_ignore_case, argv[2]);
1606 if (!strcmp(argv[0], "focus-child"))
1607 return parse_bool(&opt_focus_child, argv[2]);
1609 if (!strcmp(argv[0], "wrap-lines"))
1610 return parse_bool(&opt_wrap_lines, argv[2]);
1612 if (!strcmp(argv[0], "show-id"))
1613 return parse_bool(&opt_show_id, argv[2]);
1615 if (!strcmp(argv[0], "id-width"))
1616 return parse_id(&opt_id_cols, argv[2]);
1618 if (!strcmp(argv[0], "title-overflow")) {
1620 enum option_code code;
1623 * "title-overflow" is considered a boolint.
1624 * We try to parse it as a boolean (and set the value to 50 if true),
1625 * otherwise we parse it as an integer and use the given value.
1627 code = parse_bool_matched(&opt_show_title_overflow, argv[2], &matched);
1628 if (code == OPT_OK && matched) {
1629 if (opt_show_title_overflow)
1630 opt_title_overflow = 50;
1632 code = parse_int(&opt_title_overflow, argv[2], 2, 1024);
1634 opt_show_title_overflow = TRUE;
1640 if (!strcmp(argv[0], "editor-line-number"))
1641 return parse_bool(&opt_editor_lineno, argv[2]);
1643 return OPT_ERR_UNKNOWN_VARIABLE_NAME;
1646 /* Wants: mode request key */
1647 static enum option_code
1648 option_bind_command(int argc, const char *argv[])
1650 enum request request;
1651 struct keymap *keymap;
1655 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1657 if (!(keymap = get_keymap(argv[0])))
1658 return OPT_ERR_UNKNOWN_KEY_MAP;
1660 key = get_key_value(argv[1]);
1662 return OPT_ERR_UNKNOWN_KEY;
1664 request = get_request(argv[2]);
1665 if (request == REQ_UNKNOWN) {
1666 static const struct enum_map obsolete[] = {
1667 ENUM_MAP("cherry-pick", REQ_NONE),
1668 ENUM_MAP("screen-resize", REQ_NONE),
1669 ENUM_MAP("tree-parent", REQ_PARENT),
1673 if (map_enum(&alias, obsolete, argv[2])) {
1674 if (alias != REQ_NONE)
1675 add_keybinding(keymap, alias, key);
1676 return OPT_ERR_OBSOLETE_REQUEST_NAME;
1680 if (request == REQ_UNKNOWN) {
1681 char first = *argv[2]++;
1684 enum run_request_flag flags = RUN_REQUEST_FORCE;
1687 if (*argv[2] == '@') {
1688 flags |= RUN_REQUEST_SILENT;
1689 } else if (*argv[2] == '?') {
1690 flags |= RUN_REQUEST_CONFIRM;
1691 } else if (*argv[2] == '<') {
1692 flags |= RUN_REQUEST_EXIT;
1699 return add_run_request(keymap, key, argv + 2, flags)
1700 ? OPT_OK : OPT_ERR_OUT_OF_MEMORY;
1701 } else if (first == ':') {
1702 return add_run_request(keymap, key, argv + 2, RUN_REQUEST_FORCE | RUN_REQUEST_INTERNAL)
1703 ? OPT_OK : OPT_ERR_OUT_OF_MEMORY;
1705 return OPT_ERR_UNKNOWN_REQUEST_NAME;
1709 add_keybinding(keymap, request, key);
1715 static enum option_code load_option_file(const char *path);
1717 static enum option_code
1718 option_source_command(int argc, const char *argv[])
1721 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1723 return load_option_file(argv[0]);
1726 static enum option_code
1727 set_option(const char *opt, char *value)
1729 const char *argv[SIZEOF_ARG];
1732 if (!argv_from_string(argv, &argc, value))
1733 return OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
1735 if (!strcmp(opt, "color"))
1736 return option_color_command(argc, argv);
1738 if (!strcmp(opt, "set"))
1739 return option_set_command(argc, argv);
1741 if (!strcmp(opt, "bind"))
1742 return option_bind_command(argc, argv);
1744 if (!strcmp(opt, "source"))
1745 return option_source_command(argc, argv);
1747 return OPT_ERR_UNKNOWN_OPTION_COMMAND;
1750 struct config_state {
1757 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
1759 struct config_state *config = data;
1760 enum option_code status = OPT_ERR_NO_OPTION_VALUE;
1764 /* Check for comment markers, since read_properties() will
1765 * only ensure opt and value are split at first " \t". */
1766 optlen = strcspn(opt, "#");
1770 if (opt[optlen] == 0) {
1771 /* Look for comment endings in the value. */
1772 size_t len = strcspn(value, "#");
1774 if (len < valuelen) {
1776 value[valuelen] = 0;
1779 status = set_option(opt, value);
1782 if (status != OPT_OK) {
1783 warn("%s line %d: %s near '%.*s'", config->path, config->lineno,
1784 option_errors[status], (int) optlen, opt);
1785 config->errors = TRUE;
1788 /* Always keep going if errors are encountered. */
1792 static enum option_code
1793 load_option_file(const char *path)
1795 struct config_state config = { path, 0, FALSE };
1798 /* Do not read configuration from stdin if set to "" */
1799 if (!path || !strlen(path))
1802 /* It's OK that the file doesn't exist. */
1803 if (!io_open(&io, "%s", path))
1804 return OPT_ERR_FILE_DOES_NOT_EXIST;
1806 if (io_load(&io, " \t", read_option, &config) == ERR ||
1807 config.errors == TRUE)
1808 warn("Errors while loading %s.", path);
1815 const char *home = getenv("HOME");
1816 const char *tigrc_user = getenv("TIGRC_USER");
1817 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1818 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
1819 const bool diff_opts_from_args = !!opt_diff_argv;
1820 char buf[SIZEOF_STR];
1823 tigrc_system = SYSCONFDIR "/tigrc";
1824 load_option_file(tigrc_system);
1827 if (!home || !string_format(buf, "%s/.tigrc", home))
1831 load_option_file(tigrc_user);
1833 /* Add _after_ loading config files to avoid adding run requests
1834 * that conflict with keybindings. */
1835 add_builtin_run_requests();
1837 if (!diff_opts_from_args && tig_diff_opts && *tig_diff_opts) {
1838 static const char *diff_opts[SIZEOF_ARG] = { NULL };
1841 if (!string_format(buf, "%s", tig_diff_opts) ||
1842 !argv_from_string(diff_opts, &argc, buf))
1843 die("TIG_DIFF_OPTS contains too many arguments");
1844 else if (!argv_copy(&opt_diff_argv, diff_opts))
1845 die("Failed to format TIG_DIFF_OPTS arguments");
1859 /* The display array of active views and the index of the current view. */
1860 static struct view *display[2];
1861 static WINDOW *display_win[2];
1862 static WINDOW *display_title[2];
1863 static WINDOW *display_sep;
1865 static unsigned int current_view;
1867 #define foreach_displayed_view(view, i) \
1868 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1870 #define displayed_views() (display[1] != NULL ? 2 : 1)
1872 /* Current head and commit ID */
1873 static char ref_blob[SIZEOF_REF] = "";
1874 static char ref_commit[SIZEOF_REF] = "HEAD";
1875 static char ref_head[SIZEOF_REF] = "HEAD";
1876 static char ref_branch[SIZEOF_REF] = "";
1877 static char ref_status[SIZEOF_STR] = "";
1881 VIEW_ALWAYS_LINENO = 1 << 0,
1882 VIEW_CUSTOM_STATUS = 1 << 1,
1883 VIEW_ADD_DESCRIBE_REF = 1 << 2,
1884 VIEW_ADD_PAGER_REFS = 1 << 3,
1885 VIEW_OPEN_DIFF = 1 << 4,
1886 VIEW_NO_REF = 1 << 5,
1887 VIEW_NO_GIT_DIR = 1 << 6,
1888 VIEW_DIFF_LIKE = 1 << 7,
1889 VIEW_STDIN = 1 << 8,
1890 VIEW_SEND_CHILD_ENTER = 1 << 9,
1891 VIEW_FILE_FILTER = 1 << 10,
1894 #define view_has_flags(view, flag) ((view)->ops->flags & (flag))
1897 unsigned long offset; /* Offset of the window top */
1898 unsigned long col; /* Offset from the window side. */
1899 unsigned long lineno; /* Current line number */
1903 const char *name; /* View name */
1904 const char *id; /* Points to either of ref_{head,commit,blob} */
1906 struct view_ops *ops; /* View operations */
1908 char ref[SIZEOF_REF]; /* Hovered commit reference */
1909 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1911 int height, width; /* The width and height of the main window */
1912 WINDOW *win; /* The main window */
1915 struct position pos; /* Current position. */
1916 struct position prev_pos; /* Previous position. */
1919 char grep[SIZEOF_STR]; /* Search string */
1920 regex_t *regex; /* Pre-compiled regexp */
1922 /* If non-NULL, points to the view that opened this view. If this view
1923 * is closed tig will switch back to the parent view. */
1924 struct view *parent;
1928 size_t lines; /* Total number of lines */
1929 struct line *line; /* Line index */
1930 unsigned int digits; /* Number of digits in the lines member. */
1932 /* Number of lines with custom status, not to be counted in the
1934 unsigned int custom_lines;
1937 struct line *curline; /* Line currently being drawn. */
1938 enum line_type curtype; /* Attribute currently used for drawing. */
1939 unsigned long col; /* Column when drawing. */
1940 bool has_scrolled; /* View was scrolled. */
1943 const char **argv; /* Shell command arguments. */
1944 const char *dir; /* Directory from which to execute. */
1949 struct encoding *encoding;
1957 OPEN_DEFAULT = 0, /* Use default view switching. */
1958 OPEN_SPLIT = 1, /* Split current view. */
1959 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
1960 OPEN_REFRESH = 16, /* Refresh view using previous command. */
1961 OPEN_PREPARED = 32, /* Open already prepared command. */
1962 OPEN_EXTRA = 64, /* Open extra data from command. */
1966 /* What type of content being displayed. Used in the title bar. */
1968 /* What keymap does this view have */
1969 struct keymap keymap;
1970 /* Flags to control the view behavior. */
1971 enum view_flag flags;
1972 /* Size of private data. */
1973 size_t private_size;
1974 /* Open and reads in all view content. */
1975 bool (*open)(struct view *view, enum open_flags flags);
1976 /* Read one line; updates view->line. */
1977 bool (*read)(struct view *view, char *data);
1978 /* Draw one line; @lineno must be < view->height. */
1979 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1980 /* Depending on view handle a special requests. */
1981 enum request (*request)(struct view *view, enum request request, struct line *line);
1982 /* Search for regexp in a line. */
1983 bool (*grep)(struct view *view, struct line *line);
1985 void (*select)(struct view *view, struct line *line);
1988 #define VIEW_OPS(id, name, ref) name##_ops
1989 static struct view_ops VIEW_INFO(VIEW_OPS);
1991 static struct view views[] = {
1992 #define VIEW_DATA(id, name, ref) \
1993 { #name, ref, &name##_ops }
1994 VIEW_INFO(VIEW_DATA)
1997 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1999 #define foreach_view(view, i) \
2000 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2002 #define view_is_displayed(view) \
2003 (view == display[0] || view == display[1])
2005 #define view_has_line(view, line_) \
2006 ((view)->line <= (line_) && (line_) < (view)->line + (view)->lines)
2009 forward_request_to_child(struct view *child, enum request request)
2011 return displayed_views() == 2 && view_is_displayed(child) &&
2012 !strcmp(child->vid, child->id);
2016 view_request(struct view *view, enum request request)
2018 if (!view || !view->lines)
2021 if (request == REQ_ENTER && !opt_focus_child &&
2022 view_has_flags(view, VIEW_SEND_CHILD_ENTER)) {
2023 struct view *child = display[1];
2025 if (forward_request_to_child(child, request)) {
2026 view_request(child, request);
2031 if (request == REQ_REFRESH && view->unrefreshable) {
2032 report("This view can not be refreshed");
2036 return view->ops->request(view, request, &view->line[view->pos.lineno]);
2044 set_view_attr(struct view *view, enum line_type type)
2046 if (!view->curline->selected && view->curtype != type) {
2047 (void) wattrset(view->win, get_line_attr(type));
2048 wchgat(view->win, -1, 0, get_line_color(type), NULL);
2049 view->curtype = type;
2053 #define VIEW_MAX_LEN(view) ((view)->width + (view)->pos.col - (view)->col)
2056 draw_chars(struct view *view, enum line_type type, const char *string,
2057 int max_len, bool use_tilde)
2059 static char out_buffer[BUFSIZ * 2];
2062 int trimmed = FALSE;
2063 size_t skip = view->pos.col > view->col ? view->pos.col - view->col : 0;
2066 return VIEW_MAX_LEN(view) <= 0;
2068 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2070 set_view_attr(view, type);
2072 if (opt_iconv_out != ICONV_NONE) {
2073 size_t inlen = len + 1;
2074 char *instr = calloc(1, inlen);
2075 ICONV_CONST char *inbuf = (ICONV_CONST char *) instr;
2077 return VIEW_MAX_LEN(view) <= 0;
2079 strncpy(instr, string, len);
2081 char *outbuf = out_buffer;
2082 size_t outlen = sizeof(out_buffer);
2086 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2087 if (ret != (size_t) -1) {
2088 string = out_buffer;
2089 len = sizeof(out_buffer) - outlen;
2094 waddnstr(view->win, string, len);
2096 if (trimmed && use_tilde) {
2097 set_view_attr(view, LINE_DELIMITER);
2098 waddch(view->win, '~');
2104 return VIEW_MAX_LEN(view) <= 0;
2108 draw_space(struct view *view, enum line_type type, int max, int spaces)
2110 static char space[] = " ";
2112 spaces = MIN(max, spaces);
2114 while (spaces > 0) {
2115 int len = MIN(spaces, sizeof(space) - 1);
2117 if (draw_chars(view, type, space, len, FALSE))
2122 return VIEW_MAX_LEN(view) <= 0;
2126 draw_text_expanded(struct view *view, enum line_type type, const char *string, int max_len, bool use_tilde)
2128 static char text[SIZEOF_STR];
2131 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2133 if (draw_chars(view, type, text, max_len, use_tilde))
2138 return VIEW_MAX_LEN(view) <= 0;
2142 draw_text(struct view *view, enum line_type type, const char *string)
2144 return draw_text_expanded(view, type, string, VIEW_MAX_LEN(view), TRUE);
2148 draw_text_overflow(struct view *view, const char *text, bool on, int overflow, enum line_type type)
2151 int len = strlen(text);
2153 if (draw_text_expanded(view, type, text, overflow, FALSE))
2156 text = len > overflow ? text + overflow : "";
2157 type = LINE_OVERFLOW;
2160 if (*text && draw_chars(view, type, text, VIEW_MAX_LEN(view), TRUE))
2163 return VIEW_MAX_LEN(view) <= 0;
2166 #define draw_commit_title(view, text, offset) \
2167 draw_text_overflow(view, text, opt_show_title_overflow, opt_title_overflow + offset, LINE_DEFAULT)
2169 static bool PRINTF_LIKE(3, 4)
2170 draw_formatted(struct view *view, enum line_type type, const char *format, ...)
2172 char text[SIZEOF_STR];
2175 FORMAT_BUFFER(text, sizeof(text), format, retval, TRUE);
2176 return retval >= 0 ? draw_text(view, type, text) : VIEW_MAX_LEN(view) <= 0;
2180 draw_graphic(struct view *view, enum line_type type, const chtype graphic[], size_t size, bool separator)
2182 size_t skip = view->pos.col > view->col ? view->pos.col - view->col : 0;
2183 int max = VIEW_MAX_LEN(view);
2189 set_view_attr(view, type);
2190 /* Using waddch() instead of waddnstr() ensures that
2191 * they'll be rendered correctly for the cursor line. */
2192 for (i = skip; i < size; i++)
2193 waddch(view->win, graphic[i]);
2197 if (size < max && skip <= size)
2198 waddch(view->win, ' ');
2202 return VIEW_MAX_LEN(view) <= 0;
2206 draw_field(struct view *view, enum line_type type, const char *text, int width, bool trim)
2208 int max = MIN(VIEW_MAX_LEN(view), width + 1);
2209 int col = view->col;
2212 return draw_space(view, type, max, max);
2214 return draw_chars(view, type, text, max - 1, trim)
2215 || draw_space(view, LINE_DEFAULT, max - (view->col - col), max);
2219 draw_date(struct view *view, struct time *time)
2221 const char *date = mkdate(time, opt_date);
2222 int cols = opt_date == DATE_SHORT ? DATE_SHORT_WIDTH : DATE_WIDTH;
2224 if (opt_date == DATE_NO)
2227 return draw_field(view, LINE_DATE, date, cols, FALSE);
2231 draw_author(struct view *view, const struct ident *author)
2233 bool trim = author_trim(opt_author_width);
2234 const char *text = mkauthor(author, opt_author_width, opt_author);
2236 if (opt_author == AUTHOR_NO)
2239 return draw_field(view, LINE_AUTHOR, text, opt_author_width, trim);
2243 draw_id(struct view *view, enum line_type type, const char *id)
2245 return draw_field(view, type, id, opt_id_cols, FALSE);
2249 draw_filename(struct view *view, const char *filename, bool auto_enabled)
2251 bool trim = filename && strlen(filename) >= opt_filename_width;
2253 if (opt_filename == FILENAME_NO)
2256 if (opt_filename == FILENAME_AUTO && !auto_enabled)
2259 return draw_field(view, LINE_FILENAME, filename, opt_filename_width, trim);
2263 draw_mode(struct view *view, mode_t mode)
2265 const char *str = mkmode(mode);
2267 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r--"), FALSE);
2271 draw_lineno(struct view *view, unsigned int lineno)
2274 int digits3 = view->digits < 3 ? 3 : view->digits;
2275 int max = MIN(VIEW_MAX_LEN(view), digits3);
2277 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2279 if (!opt_line_number)
2282 lineno += view->pos.offset + 1;
2283 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2284 static char fmt[] = "%1ld";
2286 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2287 if (string_format(number, fmt, lineno))
2291 draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2293 draw_space(view, LINE_LINE_NUMBER, max, digits3);
2294 return draw_graphic(view, LINE_DEFAULT, &separator, 1, TRUE);
2298 draw_refs(struct view *view, struct ref_list *refs)
2302 if (!opt_show_refs || !refs)
2305 for (i = 0; i < refs->size; i++) {
2306 struct ref *ref = refs->refs[i];
2307 enum line_type type = get_line_type_from_ref(ref);
2309 if (draw_formatted(view, type, "[%s]", ref->name))
2312 if (draw_text(view, LINE_DEFAULT, " "))
2320 draw_view_line(struct view *view, unsigned int lineno)
2323 bool selected = (view->pos.offset + lineno == view->pos.lineno);
2325 assert(view_is_displayed(view));
2327 if (view->pos.offset + lineno >= view->lines)
2330 line = &view->line[view->pos.offset + lineno];
2332 wmove(view->win, lineno, 0);
2334 wclrtoeol(view->win);
2336 view->curline = line;
2337 view->curtype = LINE_NONE;
2338 line->selected = FALSE;
2339 line->dirty = line->cleareol = 0;
2342 set_view_attr(view, LINE_CURSOR);
2343 line->selected = TRUE;
2344 view->ops->select(view, line);
2347 return view->ops->draw(view, line, lineno);
2351 redraw_view_dirty(struct view *view)
2356 for (lineno = 0; lineno < view->height; lineno++) {
2357 if (view->pos.offset + lineno >= view->lines)
2359 if (!view->line[view->pos.offset + lineno].dirty)
2362 if (!draw_view_line(view, lineno))
2368 wnoutrefresh(view->win);
2372 redraw_view_from(struct view *view, int lineno)
2374 assert(0 <= lineno && lineno < view->height);
2376 for (; lineno < view->height; lineno++) {
2377 if (!draw_view_line(view, lineno))
2381 wnoutrefresh(view->win);
2385 redraw_view(struct view *view)
2388 redraw_view_from(view, 0);
2393 update_view_title(struct view *view)
2395 char buf[SIZEOF_STR];
2396 char state[SIZEOF_STR];
2397 size_t bufpos = 0, statelen = 0;
2398 WINDOW *window = display[0] == view ? display_title[0] : display_title[1];
2399 struct line *line = &view->line[view->pos.lineno];
2401 assert(view_is_displayed(view));
2403 if (!view_has_flags(view, VIEW_CUSTOM_STATUS) && view_has_line(view, line) &&
2405 unsigned int view_lines = view->pos.offset + view->height;
2406 unsigned int lines = view->lines
2407 ? MIN(view_lines, view->lines) * 100 / view->lines
2410 string_format_from(state, &statelen, " - %s %d of %zd (%d%%)",
2413 view->lines - view->custom_lines,
2419 time_t secs = time(NULL) - view->start_time;
2421 /* Three git seconds are a long time ... */
2423 string_format_from(state, &statelen, " loading %lds", secs);
2426 string_format_from(buf, &bufpos, "[%s]", view->name);
2427 if (*view->ref && bufpos < view->width) {
2428 size_t refsize = strlen(view->ref);
2429 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2431 if (minsize < view->width)
2432 refsize = view->width - minsize + 7;
2433 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2436 if (statelen && bufpos < view->width) {
2437 string_format_from(buf, &bufpos, "%s", state);
2440 if (view == display[current_view])
2441 wbkgdset(window, get_line_attr(LINE_TITLE_FOCUS));
2443 wbkgdset(window, get_line_attr(LINE_TITLE_BLUR));
2445 mvwaddnstr(window, 0, 0, buf, bufpos);
2447 wnoutrefresh(window);
2451 apply_step(double step, int value)
2455 value *= step + 0.01;
2456 return value ? value : 1;
2460 apply_horizontal_split(struct view *base, struct view *view)
2462 view->width = base->width;
2463 view->height = apply_step(opt_scale_split_view, base->height);
2464 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2465 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2466 base->height -= view->height;
2470 apply_vertical_split(struct view *base, struct view *view)
2472 view->height = base->height;
2473 view->width = apply_step(opt_scale_vsplit_view, base->width);
2474 view->width = MAX(view->width, MIN_VIEW_WIDTH);
2475 view->width = MIN(view->width, base->width - MIN_VIEW_WIDTH);
2476 base->width -= view->width;
2480 redraw_display_separator(bool clear)
2482 if (displayed_views() > 1 && opt_vsplit) {
2483 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2486 wclear(display_sep);
2487 wbkgd(display_sep, separator + get_line_attr(LINE_TITLE_BLUR));
2488 wnoutrefresh(display_sep);
2493 resize_display(void)
2496 struct view *base = display[0];
2497 struct view *view = display[1] ? display[1] : display[0];
2499 /* Setup window dimensions */
2501 getmaxyx(stdscr, base->height, base->width);
2502 set_int_environment_variable("COLUMNS", base->width);
2503 set_int_environment_variable("LINES", base->height);
2505 /* Make room for the status window. */
2510 apply_vertical_split(base, view);
2512 /* Make room for the separator bar. */
2515 apply_horizontal_split(base, view);
2518 /* Make room for the title bar. */
2522 /* Make room for the title bar. */
2527 foreach_displayed_view (view, i) {
2528 if (!display_win[i]) {
2529 display_win[i] = newwin(view->height, view->width, y, x);
2530 if (!display_win[i])
2531 die("Failed to create %s view", view->name);
2533 scrollok(display_win[i], FALSE);
2535 display_title[i] = newwin(1, view->width, y + view->height, x);
2536 if (!display_title[i])
2537 die("Failed to create title window");
2540 wresize(display_win[i], view->height, view->width);
2541 mvwin(display_win[i], y, x);
2542 wresize(display_title[i], 1, view->width);
2543 mvwin(display_title[i], y + view->height, x);
2546 if (i > 0 && opt_vsplit) {
2548 display_sep = newwin(view->height, 1, 0, x - 1);
2550 die("Failed to create separator window");
2553 wresize(display_sep, view->height, 1);
2554 mvwin(display_sep, 0, x - 1);
2558 view->win = display_win[i];
2561 x += view->width + 1;
2563 y += view->height + 1;
2566 redraw_display_separator(FALSE);
2570 redraw_display(bool clear)
2575 foreach_displayed_view (view, i) {
2579 update_view_title(view);
2582 redraw_display_separator(clear);
2589 #define TOGGLE_MENU \
2590 TOGGLE_(LINENO, '.', "line numbers", &opt_line_number, NULL) \
2591 TOGGLE_(DATE, 'D', "dates", &opt_date, date_map) \
2592 TOGGLE_(AUTHOR, 'A', "author", &opt_author, author_map) \
2593 TOGGLE_(GRAPHIC, '~', "graphics", &opt_line_graphics, graphic_map) \
2594 TOGGLE_(REV_GRAPH, 'g', "revision graph", &opt_rev_graph, NULL) \
2595 TOGGLE_(FILENAME, '#', "file names", &opt_filename, filename_map) \
2596 TOGGLE_(IGNORE_SPACE, 'W', "space changes", &opt_ignore_space, ignore_space_map) \
2597 TOGGLE_(COMMIT_ORDER, 'l', "commit order", &opt_commit_order, commit_order_map) \
2598 TOGGLE_(REFS, 'F', "reference display", &opt_show_refs, NULL) \
2599 TOGGLE_(CHANGES, 'C', "local change display", &opt_show_changes, NULL) \
2600 TOGGLE_(ID, 'X', "commit ID display", &opt_show_id, NULL) \
2601 TOGGLE_(FILES, '%', "file filtering", &opt_file_filter, NULL) \
2602 TOGGLE_(TITLE_OVERFLOW, '$', "commit title overflow display", &opt_show_title_overflow, NULL) \
2605 toggle_option(struct view *view, enum request request, char msg[SIZEOF_STR])
2608 enum request request;
2609 const struct enum_map *map;
2612 #define TOGGLE_(id, key, help, value, map) { REQ_TOGGLE_ ## id, map, (map != NULL ? ARRAY_SIZE(map) : 0) },
2616 const struct menu_item menu[] = {
2617 #define TOGGLE_(id, key, help, value, map) { key, help, value },
2624 if (request == REQ_OPTIONS) {
2625 if (!prompt_menu("Toggle option", menu, &i))
2628 while (i < ARRAY_SIZE(data) && data[i].request != request)
2630 if (i >= ARRAY_SIZE(data))
2631 die("Invalid request (%d)", request);
2634 if (data[i].map != NULL) {
2635 unsigned int *opt = menu[i].data;
2637 *opt = (*opt + 1) % data[i].map_size;
2638 if (data[i].map == ignore_space_map) {
2639 update_ignore_space_arg();
2640 string_format_size(msg, SIZEOF_STR,
2641 "Ignoring %s %s", enum_name(data[i].map[*opt]), menu[i].text);
2644 } else if (data[i].map == commit_order_map) {
2645 update_commit_order_arg();
2646 string_format_size(msg, SIZEOF_STR,
2647 "Using %s %s", enum_name(data[i].map[*opt]), menu[i].text);
2651 string_format_size(msg, SIZEOF_STR,
2652 "Displaying %s %s", enum_name(data[i].map[*opt]), menu[i].text);
2655 bool *option = menu[i].data;
2658 string_format_size(msg, SIZEOF_STR,
2659 "%sabling %s", *option ? "En" : "Dis", menu[i].text);
2661 if (option == &opt_file_filter)
2674 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2676 if (lineno >= view->lines)
2677 lineno = view->lines > 0 ? view->lines - 1 : 0;
2679 if (offset > lineno || offset + view->height <= lineno) {
2680 unsigned long half = view->height / 2;
2683 offset = lineno - half;
2688 if (offset != view->pos.offset || lineno != view->pos.lineno) {
2689 view->pos.offset = offset;
2690 view->pos.lineno = lineno;
2697 /* Scrolling backend */
2699 do_scroll_view(struct view *view, int lines)
2701 bool redraw_current_line = FALSE;
2703 /* The rendering expects the new offset. */
2704 view->pos.offset += lines;
2706 assert(0 <= view->pos.offset && view->pos.offset < view->lines);
2709 /* Move current line into the view. */
2710 if (view->pos.lineno < view->pos.offset) {
2711 view->pos.lineno = view->pos.offset;
2712 redraw_current_line = TRUE;
2713 } else if (view->pos.lineno >= view->pos.offset + view->height) {
2714 view->pos.lineno = view->pos.offset + view->height - 1;
2715 redraw_current_line = TRUE;
2718 assert(view->pos.offset <= view->pos.lineno && view->pos.lineno < view->lines);
2720 /* Redraw the whole screen if scrolling is pointless. */
2721 if (view->height < ABS(lines)) {
2725 int line = lines > 0 ? view->height - lines : 0;
2726 int end = line + ABS(lines);
2728 scrollok(view->win, TRUE);
2729 wscrl(view->win, lines);
2730 scrollok(view->win, FALSE);
2732 while (line < end && draw_view_line(view, line))
2735 if (redraw_current_line)
2736 draw_view_line(view, view->pos.lineno - view->pos.offset);
2737 wnoutrefresh(view->win);
2740 view->has_scrolled = TRUE;
2744 /* Scroll frontend */
2746 scroll_view(struct view *view, enum request request)
2750 assert(view_is_displayed(view));
2753 case REQ_SCROLL_FIRST_COL:
2755 redraw_view_from(view, 0);
2758 case REQ_SCROLL_LEFT:
2759 if (view->pos.col == 0) {
2760 report("Cannot scroll beyond the first column");
2763 if (view->pos.col <= apply_step(opt_hscroll, view->width))
2766 view->pos.col -= apply_step(opt_hscroll, view->width);
2767 redraw_view_from(view, 0);
2770 case REQ_SCROLL_RIGHT:
2771 view->pos.col += apply_step(opt_hscroll, view->width);
2775 case REQ_SCROLL_PAGE_DOWN:
2776 lines = view->height;
2777 case REQ_SCROLL_LINE_DOWN:
2778 if (view->pos.offset + lines > view->lines)
2779 lines = view->lines - view->pos.offset;
2781 if (lines == 0 || view->pos.offset + view->height >= view->lines) {
2782 report("Cannot scroll beyond the last line");
2787 case REQ_SCROLL_PAGE_UP:
2788 lines = view->height;
2789 case REQ_SCROLL_LINE_UP:
2790 if (lines > view->pos.offset)
2791 lines = view->pos.offset;
2794 report("Cannot scroll beyond the first line");
2802 die("request %d not handled in switch", request);
2805 do_scroll_view(view, lines);
2810 move_view(struct view *view, enum request request)
2812 int scroll_steps = 0;
2816 case REQ_MOVE_FIRST_LINE:
2817 steps = -view->pos.lineno;
2820 case REQ_MOVE_LAST_LINE:
2821 steps = view->lines - view->pos.lineno - 1;
2824 case REQ_MOVE_PAGE_UP:
2825 steps = view->height > view->pos.lineno
2826 ? -view->pos.lineno : -view->height;
2829 case REQ_MOVE_PAGE_DOWN:
2830 steps = view->pos.lineno + view->height >= view->lines
2831 ? view->lines - view->pos.lineno - 1 : view->height;
2845 die("request %d not handled in switch", request);
2848 if (steps <= 0 && view->pos.lineno == 0) {
2849 report("Cannot move beyond the first line");
2852 } else if (steps >= 0 && view->pos.lineno + 1 >= view->lines) {
2853 report("Cannot move beyond the last line");
2857 /* Move the current line */
2858 view->pos.lineno += steps;
2859 assert(0 <= view->pos.lineno && view->pos.lineno < view->lines);
2861 /* Check whether the view needs to be scrolled */
2862 if (view->pos.lineno < view->pos.offset ||
2863 view->pos.lineno >= view->pos.offset + view->height) {
2864 scroll_steps = steps;
2865 if (steps < 0 && -steps > view->pos.offset) {
2866 scroll_steps = -view->pos.offset;
2868 } else if (steps > 0) {
2869 if (view->pos.lineno == view->lines - 1 &&
2870 view->lines > view->height) {
2871 scroll_steps = view->lines - view->pos.offset - 1;
2872 if (scroll_steps >= view->height)
2873 scroll_steps -= view->height - 1;
2878 if (!view_is_displayed(view)) {
2879 view->pos.offset += scroll_steps;
2880 assert(0 <= view->pos.offset && view->pos.offset < view->lines);
2881 view->ops->select(view, &view->line[view->pos.lineno]);
2885 /* Repaint the old "current" line if we be scrolling */
2886 if (ABS(steps) < view->height)
2887 draw_view_line(view, view->pos.lineno - steps - view->pos.offset);
2890 do_scroll_view(view, scroll_steps);
2894 /* Draw the current line */
2895 draw_view_line(view, view->pos.lineno - view->pos.offset);
2897 wnoutrefresh(view->win);
2906 static void search_view(struct view *view, enum request request);
2909 grep_text(struct view *view, const char *text[])
2914 for (i = 0; text[i]; i++)
2915 if (*text[i] && !regexec(view->regex, text[i], 1, &pmatch, 0))
2921 select_view_line(struct view *view, unsigned long lineno)
2923 struct position old = view->pos;
2925 if (goto_view_line(view, view->pos.offset, lineno)) {
2926 if (view_is_displayed(view)) {
2927 if (old.offset != view->pos.offset) {
2930 draw_view_line(view, old.lineno - view->pos.offset);
2931 draw_view_line(view, view->pos.lineno - view->pos.offset);
2932 wnoutrefresh(view->win);
2935 view->ops->select(view, &view->line[view->pos.lineno]);
2941 find_next(struct view *view, enum request request)
2943 unsigned long lineno = view->pos.lineno;
2948 report("No previous search");
2950 search_view(view, request);
2960 case REQ_SEARCH_BACK:
2969 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2970 lineno += direction;
2972 /* Note, lineno is unsigned long so will wrap around in which case it
2973 * will become bigger than view->lines. */
2974 for (; lineno < view->lines; lineno += direction) {
2975 if (view->ops->grep(view, &view->line[lineno])) {
2976 select_view_line(view, lineno);
2977 report("Line %ld matches '%s'", lineno + 1, view->grep);
2982 report("No match found for '%s'", view->grep);
2986 search_view(struct view *view, enum request request)
2989 int regex_flags = opt_ignore_case ? REG_ICASE : 0;
2992 regfree(view->regex);
2995 view->regex = calloc(1, sizeof(*view->regex));
3000 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED | regex_flags);
3001 if (regex_err != 0) {
3002 char buf[SIZEOF_STR] = "unknown error";
3004 regerror(regex_err, view->regex, buf, sizeof(buf));
3005 report("Search failed: %s", buf);
3009 string_copy(view->grep, opt_search);
3011 find_next(view, request);
3015 * Incremental updating
3019 check_position(struct position *pos)
3021 return pos->lineno || pos->col || pos->offset;
3025 clear_position(struct position *pos)
3027 memset(pos, 0, sizeof(*pos));
3031 reset_view(struct view *view)
3035 for (i = 0; i < view->lines; i++)
3036 if (!view->line[i].dont_free)
3037 free(view->line[i].data);
3040 view->prev_pos = view->pos;
3041 clear_position(&view->pos);
3046 view->custom_lines = 0;
3047 view->update_secs = 0;
3050 struct format_context {
3052 char buf[SIZEOF_STR];
3058 format_expand_arg(struct format_context *format, const char *name)
3064 const char *value_if_empty;
3066 #define FORMAT_VAR(name, value, value_if_empty) \
3067 { name, STRING_SIZE(name), value, value_if_empty }
3068 FORMAT_VAR("%(directory)", opt_path, "."),
3069 FORMAT_VAR("%(file)", opt_file, ""),
3070 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3071 FORMAT_VAR("%(head)", ref_head, ""),
3072 FORMAT_VAR("%(commit)", ref_commit, ""),
3073 FORMAT_VAR("%(blob)", ref_blob, ""),
3074 FORMAT_VAR("%(branch)", ref_branch, ""),
3078 if (!prefixcmp(name, "%(prompt")) {
3079 const char *value = read_prompt("Command argument: ");
3081 return string_format_from(format->buf, &format->bufpos, "%s", value);
3084 for (i = 0; i < ARRAY_SIZE(vars); i++) {
3087 if (strncmp(name, vars[i].name, vars[i].namelen))
3090 if (vars[i].value == opt_file && !format->file_filter)
3093 value = *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3097 return string_format_from(format->buf, &format->bufpos, "%s", value);
3100 report("Unknown replacement: `%s`", name);
3105 format_append_arg(struct format_context *format, const char ***dst_argv, const char *arg)
3110 char *next = strstr(arg, "%(");
3111 int len = next ? next - arg : strlen(arg);
3113 if (len && !string_format_from(format->buf, &format->bufpos, "%.*s", len, arg))
3116 if (next && !format_expand_arg(format, next))
3119 arg = next ? strchr(next, ')') + 1 : NULL;
3122 return argv_append(dst_argv, format->buf);
3126 format_append_argv(struct format_context *format, const char ***dst_argv, const char *src_argv[])
3133 for (argc = 0; src_argv[argc]; argc++)
3134 if (!format_append_arg(format, dst_argv, src_argv[argc]))
3137 return src_argv[argc] == NULL;
3141 format_argv(struct view *view, const char ***dst_argv, const char *src_argv[], bool first, bool file_filter)
3143 struct format_context format = { view, "", 0, file_filter };
3146 argv_free(*dst_argv);
3148 for (argc = 0; src_argv[argc]; argc++) {
3149 const char *arg = src_argv[argc];
3151 if (!strcmp(arg, "%(fileargs)")) {
3152 if (file_filter && !argv_append_array(dst_argv, opt_file_argv))
3155 } else if (!strcmp(arg, "%(diffargs)")) {
3156 if (!format_append_argv(&format, dst_argv, opt_diff_argv))
3159 } else if (!strcmp(arg, "%(blameargs)")) {
3160 if (!format_append_argv(&format, dst_argv, opt_blame_argv))
3163 } else if (!strcmp(arg, "%(revargs)") ||
3164 (first && !strcmp(arg, "%(commit)"))) {
3165 if (!argv_append_array(dst_argv, opt_rev_argv))
3168 } else if (!format_append_arg(&format, dst_argv, arg)) {
3173 return src_argv[argc] == NULL;
3177 restore_view_position(struct view *view)
3179 /* A view without a previous view is the first view */
3180 if (!view->prev && opt_lineno && opt_lineno <= view->lines) {
3181 select_view_line(view, opt_lineno - 1);
3185 /* Ensure that the view position is in a valid state. */
3186 if (!check_position(&view->prev_pos) ||
3187 (view->pipe && view->lines <= view->prev_pos.lineno))
3188 return goto_view_line(view, view->pos.offset, view->pos.lineno);
3190 /* Changing the view position cancels the restoring. */
3191 /* FIXME: Changing back to the first line is not detected. */
3192 if (check_position(&view->pos)) {
3193 clear_position(&view->prev_pos);
3197 if (goto_view_line(view, view->prev_pos.offset, view->prev_pos.lineno) &&
3198 view_is_displayed(view))
3201 view->pos.col = view->prev_pos.col;
3202 clear_position(&view->prev_pos);
3208 end_update(struct view *view, bool force)
3212 while (!view->ops->read(view, NULL))
3216 io_kill(view->pipe);
3217 io_done(view->pipe);
3222 setup_update(struct view *view, const char *vid)
3225 /* XXX: Do not use string_copy_rev(), it copies until first space. */
3226 string_ncopy(view->vid, vid, strlen(vid));
3227 view->pipe = &view->io;
3228 view->start_time = time(NULL);
3232 begin_update(struct view *view, const char *dir, const char **argv, enum open_flags flags)
3234 bool use_stdin = view_has_flags(view, VIEW_STDIN) && opt_stdin;
3235 bool extra = !!(flags & (OPEN_EXTRA));
3236 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED | OPEN_EXTRA));
3237 bool refresh = flags & (OPEN_REFRESH | OPEN_PREPARED);
3238 enum io_type io_type = use_stdin ? IO_RD_STDIN : IO_RD;
3242 if ((!reload && !strcmp(view->vid, view->id)) ||
3243 ((flags & OPEN_REFRESH) && view->unrefreshable))
3248 io_done(view->pipe);
3250 end_update(view, TRUE);
3253 view->unrefreshable = use_stdin;
3255 if (!refresh && argv) {
3256 bool file_filter = !view_has_flags(view, VIEW_FILE_FILTER) || opt_file_filter;
3259 if (!format_argv(view, &view->argv, argv, !view->prev, file_filter)) {
3260 report("Failed to format %s arguments", view->name);
3264 /* Put the current ref_* value to the view title ref
3265 * member. This is needed by the blob view. Most other
3266 * views sets it automatically after loading because the
3267 * first line is a commit line. */
3268 string_copy_rev(view->ref, view->id);
3271 if (view->argv && view->argv[0] &&
3272 !io_run(&view->io, io_type, view->dir, view->argv)) {
3273 report("Failed to open %s view", view->name);
3278 setup_update(view, view->id);
3284 update_view(struct view *view)
3287 /* Clear the view and redraw everything since the tree sorting
3288 * might have rearranged things. */
3289 bool redraw = view->lines == 0;
3290 bool can_read = TRUE;
3291 struct encoding *encoding = view->encoding ? view->encoding : opt_encoding;
3296 if (!io_can_read(view->pipe, FALSE)) {
3297 if (view->lines == 0 && view_is_displayed(view)) {
3298 time_t secs = time(NULL) - view->start_time;
3300 if (secs > 1 && secs > view->update_secs) {
3301 if (view->update_secs == 0)
3303 update_view_title(view);
3304 view->update_secs = secs;
3310 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3312 line = encoding_convert(encoding, line);
3315 if (!view->ops->read(view, line)) {
3316 report("Allocation failure");
3317 end_update(view, TRUE);
3323 unsigned long lines = view->lines;
3326 for (digits = 0; lines; digits++)
3329 /* Keep the displayed view in sync with line number scaling. */
3330 if (digits != view->digits) {
3331 view->digits = digits;
3332 if (opt_line_number || view_has_flags(view, VIEW_ALWAYS_LINENO))
3337 if (io_error(view->pipe)) {
3338 report("Failed to read: %s", io_strerror(view->pipe));
3339 end_update(view, TRUE);
3341 } else if (io_eof(view->pipe)) {
3342 end_update(view, FALSE);
3345 if (restore_view_position(view))
3348 if (!view_is_displayed(view))
3352 redraw_view_from(view, 0);
3354 redraw_view_dirty(view);
3356 /* Update the title _after_ the redraw so that if the redraw picks up a
3357 * commit reference in view->ref it'll be available here. */
3358 update_view_title(view);
3362 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3364 static struct line *
3365 add_line(struct view *view, const void *data, enum line_type type, size_t data_size, bool custom)
3369 if (!realloc_lines(&view->line, view->lines, 1))
3373 void *alloc_data = calloc(1, data_size);
3379 memcpy(alloc_data, data, data_size);
3383 line = &view->line[view->lines++];
3384 memset(line, 0, sizeof(*line));
3386 line->data = (void *) data;
3390 view->custom_lines++;
3392 line->lineno = view->lines - view->custom_lines;
3397 static struct line *
3398 add_line_alloc_(struct view *view, void **ptr, enum line_type type, size_t data_size, bool custom)
3400 struct line *line = add_line(view, NULL, type, data_size, custom);
3407 #define add_line_alloc(view, data_ptr, type, extra_size, custom) \
3408 add_line_alloc_(view, (void **) data_ptr, type, sizeof(**data_ptr) + extra_size, custom)
3410 static struct line *
3411 add_line_nodata(struct view *view, enum line_type type)
3413 return add_line(view, NULL, type, 0, FALSE);
3416 static struct line *
3417 add_line_static_data(struct view *view, const void *data, enum line_type type)
3419 struct line *line = add_line(view, data, type, 0, FALSE);
3422 line->dont_free = TRUE;
3426 static struct line *
3427 add_line_text(struct view *view, const char *text, enum line_type type)
3429 return add_line(view, text, type, strlen(text) + 1, FALSE);
3432 static struct line * PRINTF_LIKE(3, 4)
3433 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3435 char buf[SIZEOF_STR];
3438 FORMAT_BUFFER(buf, sizeof(buf), fmt, retval, FALSE);
3439 return retval >= 0 ? add_line_text(view, buf, type) : NULL;
3447 split_view(struct view *prev, struct view *view)
3450 current_view = opt_focus_child ? 1 : 0;
3451 view->parent = prev;
3454 if (prev->pos.lineno - prev->pos.offset >= prev->height) {
3455 /* Take the title line into account. */
3456 int lines = prev->pos.lineno - prev->pos.offset - prev->height + 1;
3458 /* Scroll the view that was split if the current line is
3459 * outside the new limited view. */
3460 do_scroll_view(prev, lines);
3463 if (view != prev && view_is_displayed(prev)) {
3464 /* "Blur" the previous view. */
3465 update_view_title(prev);
3470 maximize_view(struct view *view, bool redraw)
3472 memset(display, 0, sizeof(display));
3474 display[current_view] = view;
3477 redraw_display(FALSE);
3483 load_view(struct view *view, struct view *prev, enum open_flags flags)
3486 end_update(view, TRUE);
3487 if (view->ops->private_size) {
3489 view->private = calloc(1, view->ops->private_size);
3491 memset(view->private, 0, view->ops->private_size);
3494 /* When prev == view it means this is the first loaded view. */
3495 if (prev && view != prev) {
3499 if (!view->ops->open(view, flags))
3503 bool split = !!(flags & OPEN_SPLIT);
3506 split_view(prev, view);
3508 maximize_view(view, FALSE);
3512 restore_view_position(view);
3514 if (view->pipe && view->lines == 0) {
3515 /* Clear the old view and let the incremental updating refill
3518 if (!(flags & (OPEN_RELOAD | OPEN_REFRESH)))
3519 clear_position(&view->prev_pos);
3521 } else if (view_is_displayed(view)) {
3527 #define refresh_view(view) load_view(view, NULL, OPEN_REFRESH)
3528 #define reload_view(view) load_view(view, NULL, OPEN_RELOAD)
3531 open_view(struct view *prev, enum request request, enum open_flags flags)
3533 bool reload = !!(flags & (OPEN_RELOAD | OPEN_PREPARED));
3534 struct view *view = VIEW(request);
3535 int nviews = displayed_views();
3537 assert(flags ^ OPEN_REFRESH);
3539 if (view == prev && nviews == 1 && !reload) {
3540 report("Already in %s view", view->name);
3544 if (!view_has_flags(view, VIEW_NO_GIT_DIR) && !opt_git_dir[0]) {
3545 report("The %s view is disabled in pager view", view->name);
3549 load_view(view, prev ? prev : view, flags);
3553 open_argv(struct view *prev, struct view *view, const char *argv[], const char *dir, enum open_flags flags)
3555 enum request request = view - views + REQ_OFFSET + 1;
3558 end_update(view, TRUE);
3561 if (!argv_copy(&view->argv, argv)) {
3562 report("Failed to open %s view: %s", view->name, io_strerror(&view->io));
3564 open_view(prev, request, flags | OPEN_PREPARED);
3569 open_external_viewer(const char *argv[], const char *dir, bool confirm, const char *notice)
3573 def_prog_mode(); /* save current tty modes */
3574 endwin(); /* restore original tty modes */
3575 ok = io_run_fg(argv, dir);
3578 fprintf(stderr, "%s", notice);
3579 fprintf(stderr, "Press Enter to continue");
3583 redraw_display(TRUE);
3588 open_mergetool(const char *file)
3590 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3592 open_external_viewer(mergetool_argv, opt_cdup, TRUE, "");
3595 #define EDITOR_LINENO_MSG \
3596 "*** Your editor reported an error while opening the file.\n" \
3597 "*** This is probably because it doesn't support the line\n" \
3598 "*** number argument added automatically. The line number\n" \
3599 "*** has been disabled for now. You can permanently disable\n" \
3600 "*** it by adding the following line to ~/.tigrc\n" \
3601 "*** set editor-line-number = no\n"
3604 open_editor(const char *file, unsigned int lineno)
3606 const char *editor_argv[SIZEOF_ARG + 3] = { "vi", file, NULL };
3607 char editor_cmd[SIZEOF_STR];
3608 char lineno_cmd[SIZEOF_STR];
3612 editor = getenv("GIT_EDITOR");
3613 if (!editor && *opt_editor)
3614 editor = opt_editor;
3616 editor = getenv("VISUAL");
3618 editor = getenv("EDITOR");
3622 string_ncopy(editor_cmd, editor, strlen(editor));
3623 if (!argv_from_string_no_quotes(editor_argv, &argc, editor_cmd)) {
3624 report("Failed to read editor command");
3628 if (lineno && opt_editor_lineno && string_format(lineno_cmd, "+%u", lineno))
3629 editor_argv[argc++] = lineno_cmd;
3630 editor_argv[argc] = file;
3631 if (!open_external_viewer(editor_argv, opt_cdup, TRUE, EDITOR_LINENO_MSG))
3632 opt_editor_lineno = FALSE;
3635 static enum request run_prompt_command(struct view *view, char *cmd);
3638 open_run_request(struct view *view, enum request request)
3640 struct run_request *req = get_run_request(request);
3641 const char **argv = NULL;
3642 bool confirmed = FALSE;
3647 report("Unknown run request");
3651 if (format_argv(view, &argv, req->argv, FALSE, TRUE)) {
3652 if (req->internal) {
3653 char cmd[SIZEOF_STR];
3655 if (argv_to_string(argv, cmd, sizeof(cmd), " ")) {
3656 request = run_prompt_command(view, cmd);
3660 confirmed = !req->confirm;
3663 char cmd[SIZEOF_STR], prompt[SIZEOF_STR];
3665 if (argv_to_string(argv, cmd, sizeof(cmd), " ") &&
3666 string_format(prompt, "Run `%s`?", cmd) &&
3667 prompt_yesno(prompt)) {
3672 if (confirmed && argv_remove_quotes(argv)) {
3676 open_external_viewer(argv, NULL, !req->exit, "");
3685 if (request == REQ_NONE) {
3686 if (req->confirm && !confirmed)
3692 else if (!view->unrefreshable)
3693 request = REQ_REFRESH;
3699 * User request switch noodle
3703 view_driver(struct view *view, enum request request)
3707 if (request == REQ_NONE)
3710 if (request > REQ_NONE) {
3711 request = open_run_request(view, request);
3713 // exit quickly rather than going through view_request and back
3714 if (request == REQ_QUIT)
3718 request = view_request(view, request);
3719 if (request == REQ_NONE)
3725 case REQ_MOVE_PAGE_UP:
3726 case REQ_MOVE_PAGE_DOWN:
3727 case REQ_MOVE_FIRST_LINE:
3728 case REQ_MOVE_LAST_LINE:
3729 move_view(view, request);
3732 case REQ_SCROLL_FIRST_COL:
3733 case REQ_SCROLL_LEFT:
3734 case REQ_SCROLL_RIGHT:
3735 case REQ_SCROLL_LINE_DOWN:
3736 case REQ_SCROLL_LINE_UP:
3737 case REQ_SCROLL_PAGE_DOWN:
3738 case REQ_SCROLL_PAGE_UP:
3739 scroll_view(view, request);
3747 case REQ_VIEW_BRANCH:
3748 case REQ_VIEW_BLAME:
3750 case REQ_VIEW_STATUS:
3751 case REQ_VIEW_STAGE:
3752 case REQ_VIEW_PAGER:
3753 open_view(view, request, OPEN_DEFAULT);
3761 view = view->parent;
3762 line = view->pos.lineno;
3763 move_view(view, request);
3764 if (view_is_displayed(view))
3765 update_view_title(view);
3766 if (line != view->pos.lineno)
3767 view_request(view, REQ_ENTER);
3769 move_view(view, request);
3775 int nviews = displayed_views();
3776 int next_view = (current_view + 1) % nviews;
3778 if (next_view == current_view) {
3779 report("Only one view is displayed");
3783 current_view = next_view;
3784 /* Blur out the title of the previous view. */
3785 update_view_title(view);
3790 report("Refreshing is not yet supported for the %s view", view->name);
3794 if (displayed_views() == 2)
3795 maximize_view(view, TRUE);
3799 case REQ_TOGGLE_LINENO:
3800 case REQ_TOGGLE_DATE:
3801 case REQ_TOGGLE_AUTHOR:
3802 case REQ_TOGGLE_FILENAME:
3803 case REQ_TOGGLE_GRAPHIC:
3804 case REQ_TOGGLE_REV_GRAPH:
3805 case REQ_TOGGLE_REFS:
3806 case REQ_TOGGLE_CHANGES:
3807 case REQ_TOGGLE_IGNORE_SPACE:
3809 case REQ_TOGGLE_FILES:
3810 case REQ_TOGGLE_TITLE_OVERFLOW:
3812 char action[SIZEOF_STR] = "";
3813 bool reload = toggle_option(view, request, action);
3815 if (reload && view_has_flags(view, VIEW_DIFF_LIKE))
3818 redraw_display(FALSE);
3821 report("%s", action);
3825 case REQ_TOGGLE_SORT_FIELD:
3826 case REQ_TOGGLE_SORT_ORDER:
3827 report("Sorting is not yet supported for the %s view", view->name);
3830 case REQ_DIFF_CONTEXT_UP:
3831 case REQ_DIFF_CONTEXT_DOWN:
3832 report("Changing the diff context is not yet supported for the %s view", view->name);
3836 case REQ_SEARCH_BACK:
3837 search_view(view, request);
3842 find_next(view, request);
3845 case REQ_STOP_LOADING:
3846 foreach_view(view, i) {
3848 report("Stopped loading the %s view", view->name),
3849 end_update(view, TRUE);
3853 case REQ_SHOW_VERSION:
3854 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3857 case REQ_SCREEN_REDRAW:
3858 redraw_display(TRUE);
3862 report("Nothing to edit");
3866 report("Nothing to enter");
3869 case REQ_VIEW_CLOSE:
3870 /* XXX: Mark closed views by letting view->prev point to the
3871 * view itself. Parents to closed view should never be
3873 if (view->prev && view->prev != view) {
3874 maximize_view(view->prev, TRUE);
3883 report("Unknown key, press %s for help",
3884 get_view_key(view, REQ_VIEW_HELP));
3893 * View backend utilities
3903 const enum sort_field *fields;
3904 size_t size, current;
3908 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3909 #define get_sort_field(state) ((state).fields[(state).current])
3910 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3913 sort_view(struct view *view, enum request request, struct sort_state *state,
3914 int (*compare)(const void *, const void *))
3917 case REQ_TOGGLE_SORT_FIELD:
3918 state->current = (state->current + 1) % state->size;
3921 case REQ_TOGGLE_SORT_ORDER:
3922 state->reverse = !state->reverse;
3925 die("Not a sort request");
3928 qsort(view->line, view->lines, sizeof(*view->line), compare);
3933 update_diff_context(enum request request)
3935 int diff_context = opt_diff_context;
3938 case REQ_DIFF_CONTEXT_UP:
3939 opt_diff_context += 1;
3940 update_diff_context_arg(opt_diff_context);
3943 case REQ_DIFF_CONTEXT_DOWN:
3944 if (opt_diff_context == 0) {
3945 report("Diff context cannot be less than zero");
3948 opt_diff_context -= 1;
3949 update_diff_context_arg(opt_diff_context);
3953 die("Not a diff context request");
3956 return diff_context != opt_diff_context;
3959 DEFINE_ALLOCATOR(realloc_authors, struct ident *, 256)
3961 /* Small author cache to reduce memory consumption. It uses binary
3962 * search to lookup or find place to position new entries. No entries
3963 * are ever freed. */
3964 static struct ident *
3965 get_author(const char *name, const char *email)
3967 static struct ident **authors;
3968 static size_t authors_size;
3969 int from = 0, to = authors_size - 1;
3970 struct ident *ident;
3972 while (from <= to) {
3973 size_t pos = (to + from) / 2;
3974 int cmp = strcmp(name, authors[pos]->name);
3977 return authors[pos];
3985 if (!realloc_authors(&authors, authors_size, 1))
3987 ident = calloc(1, sizeof(*ident));
3990 ident->name = strdup(name);
3991 ident->email = strdup(email);
3992 if (!ident->name || !ident->email) {
3993 free((void *) ident->name);
3998 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3999 authors[from] = ident;
4006 parse_timesec(struct time *time, const char *sec)
4008 time->sec = (time_t) atol(sec);
4012 parse_timezone(struct time *time, const char *zone)
4016 tz = ('0' - zone[1]) * 60 * 60 * 10;
4017 tz += ('0' - zone[2]) * 60 * 60;
4018 tz += ('0' - zone[3]) * 60 * 10;
4019 tz += ('0' - zone[4]) * 60;
4028 /* Parse author lines where the name may be empty:
4029 * author <email@address.tld> 1138474660 +0100
4032 parse_author_line(char *ident, const struct ident **author, struct time *time)
4034 char *nameend = strchr(ident, '<');
4035 char *emailend = strchr(ident, '>');
4036 const char *name, *email = "";
4038 if (nameend && emailend)
4039 *nameend = *emailend = 0;
4040 name = chomp_string(ident);
4042 email = chomp_string(nameend + 1);
4044 name = *email ? email : unknown_ident.name;
4046 email = *name ? name : unknown_ident.email;
4048 *author = get_author(name, email);
4050 /* Parse epoch and timezone */
4051 if (time && emailend && emailend[1] == ' ') {
4052 char *secs = emailend + 2;
4053 char *zone = strchr(secs, ' ');
4055 parse_timesec(time, secs);
4057 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4058 parse_timezone(time, zone + 1);
4062 static struct line *
4063 find_line_by_type(struct view *view, struct line *line, enum line_type type, int direction)
4065 for (; view_has_line(view, line); line += direction)
4066 if (line->type == type)
4072 #define find_prev_line_by_type(view, line, type) \
4073 find_line_by_type(view, line, type, -1)
4075 #define find_next_line_by_type(view, line, type) \
4076 find_line_by_type(view, line, type, 1)
4082 struct blame_commit {
4083 char id[SIZEOF_REV]; /* SHA1 ID. */
4084 char title[128]; /* First line of the commit message. */
4085 const struct ident *author; /* Author of the commit. */
4086 struct time time; /* Date from the author ident. */
4087 char filename[128]; /* Name of file. */
4088 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4089 char parent_filename[128]; /* Parent/previous name of file. */
4092 struct blame_header {
4093 char id[SIZEOF_REV]; /* SHA1 ID. */
4100 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4102 const char *pos = *posref;
4105 pos = strchr(pos + 1, ' ');
4106 if (!pos || !isdigit(pos[1]))
4108 *number = atoi(pos + 1);
4109 if (*number < min || *number > max)
4117 parse_blame_header(struct blame_header *header, const char *text, size_t max_lineno)
4119 const char *pos = text + SIZEOF_REV - 2;
4121 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4124 string_ncopy(header->id, text, SIZEOF_REV);
4126 if (!parse_number(&pos, &header->orig_lineno, 1, 9999999) ||
4127 !parse_number(&pos, &header->lineno, 1, max_lineno) ||
4128 !parse_number(&pos, &header->group, 1, max_lineno - header->lineno + 1))
4135 match_blame_header(const char *name, char **line)
4137 size_t namelen = strlen(name);
4138 bool matched = !strncmp(name, *line, namelen);
4147 parse_blame_info(struct blame_commit *commit, char *line)
4149 if (match_blame_header("author ", &line)) {
4150 parse_author_line(line, &commit->author, NULL);
4152 } else if (match_blame_header("author-time ", &line)) {
4153 parse_timesec(&commit->time, line);
4155 } else if (match_blame_header("author-tz ", &line)) {
4156 parse_timezone(&commit->time, line);
4158 } else if (match_blame_header("summary ", &line)) {
4159 string_ncopy(commit->title, line, strlen(line));
4161 } else if (match_blame_header("previous ", &line)) {
4162 if (strlen(line) <= SIZEOF_REV)
4164 string_copy_rev(commit->parent_id, line);
4166 string_ncopy(commit->parent_filename, line, strlen(line));
4168 } else if (match_blame_header("filename ", &line)) {
4169 string_ncopy(commit->filename, line, strlen(line));
4181 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4183 if (draw_lineno(view, lineno))
4186 if (line->wrapped && draw_text(view, LINE_DELIMITER, "+"))
4189 draw_text(view, line->type, line->data);
4194 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4196 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4197 char ref[SIZEOF_STR];
4199 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4202 /* This is the only fatal call, since it can "corrupt" the buffer. */
4203 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4210 add_pager_refs(struct view *view, const char *commit_id)
4212 char buf[SIZEOF_STR];
4213 struct ref_list *list;
4214 size_t bufpos = 0, i;
4215 const char *sep = "Refs: ";
4216 bool is_tag = FALSE;
4218 list = get_ref_list(commit_id);
4220 if (view_has_flags(view, VIEW_ADD_DESCRIBE_REF))
4221 goto try_add_describe_ref;
4225 for (i = 0; i < list->size; i++) {
4226 struct ref *ref = list->refs[i];
4227 const char *fmt = ref->tag ? "%s[%s]" :
4228 ref->remote ? "%s<%s>" : "%s%s";
4230 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4237 if (!is_tag && view_has_flags(view, VIEW_ADD_DESCRIBE_REF)) {
4238 try_add_describe_ref:
4239 /* Add <tag>-g<commit_id> "fake" reference. */
4240 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4247 add_line_text(view, buf, LINE_PP_REFS);
4250 static struct line *
4251 pager_wrap_line(struct view *view, const char *data, enum line_type type)
4253 size_t first_line = 0;
4254 bool has_first_line = FALSE;
4255 size_t datalen = strlen(data);
4258 while (datalen > 0 || !has_first_line) {
4259 bool wrapped = !!first_line;
4260 size_t linelen = string_expanded_length(data, datalen, opt_tab_size, view->width - !!wrapped);
4264 line = add_line(view, NULL, type, linelen + 1, wrapped);
4267 if (!has_first_line) {
4268 first_line = view->lines - 1;
4269 has_first_line = TRUE;
4273 lineno = line->lineno;
4275 line->wrapped = wrapped;
4276 line->lineno = lineno;
4279 strncpy(text, data, linelen);
4286 return has_first_line ? &view->line[first_line] : NULL;
4290 pager_common_read(struct view *view, const char *data, enum line_type type)
4297 if (opt_wrap_lines) {
4298 line = pager_wrap_line(view, data, type);
4300 line = add_line_text(view, data, type);
4306 if (line->type == LINE_COMMIT && view_has_flags(view, VIEW_ADD_PAGER_REFS))
4307 add_pager_refs(view, data + STRING_SIZE("commit "));
4313 pager_read(struct view *view, char *data)
4318 return pager_common_read(view, data, get_line_type(data));
4322 pager_request(struct view *view, enum request request, struct line *line)
4326 if (request != REQ_ENTER)
4329 if (line->type == LINE_COMMIT && view_has_flags(view, VIEW_OPEN_DIFF)) {
4330 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4334 /* Always scroll the view even if it was split. That way
4335 * you can use Enter to scroll through the log view and
4336 * split open each commit diff. */
4337 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4339 /* FIXME: A minor workaround. Scrolling the view will call report_clear()
4340 * but if we are scrolling a non-current view this won't properly
4341 * update the view title. */
4343 update_view_title(view);
4349 pager_grep(struct view *view, struct line *line)
4351 const char *text[] = { line->data, NULL };
4353 return grep_text(view, text);
4357 pager_select(struct view *view, struct line *line)
4359 if (line->type == LINE_COMMIT) {
4360 char *text = (char *)line->data + STRING_SIZE("commit ");
4362 if (!view_has_flags(view, VIEW_NO_REF))
4363 string_copy_rev(view->ref, text);
4364 string_copy_rev(ref_commit, text);
4369 pager_open(struct view *view, enum open_flags flags)
4371 if (display[0] == NULL) {
4372 if (!io_open(&view->io, "%s", ""))
4373 die("Failed to open stdin");
4374 flags = OPEN_PREPARED;
4376 } else if (!view->pipe && !view->lines && !(flags & OPEN_PREPARED)) {
4377 report("No pager content, press %s to run command from prompt",
4378 get_view_key(view, REQ_PROMPT));
4382 return begin_update(view, NULL, NULL, flags);
4385 static struct view_ops pager_ops = {
4388 VIEW_OPEN_DIFF | VIEW_NO_REF | VIEW_NO_GIT_DIR,
4399 log_open(struct view *view, enum open_flags flags)
4401 static const char *log_argv[] = {
4402 "git", "log", opt_encoding_arg, "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4405 return begin_update(view, NULL, log_argv, flags);
4409 log_request(struct view *view, enum request request, struct line *line)
4417 return pager_request(view, request, line);
4421 static struct view_ops log_ops = {
4424 VIEW_ADD_PAGER_REFS | VIEW_OPEN_DIFF | VIEW_SEND_CHILD_ENTER,
4435 bool after_commit_title;
4436 bool reading_diff_stat;
4440 #define DIFF_LINE_COMMIT_TITLE 1
4443 diff_open(struct view *view, enum open_flags flags)
4445 static const char *diff_argv[] = {
4446 "git", "show", opt_encoding_arg, "--pretty=fuller", "--no-color", "--root",
4447 "--patch-with-stat",
4448 opt_notes_arg, opt_diff_context_arg, opt_ignore_space_arg,
4449 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4452 return begin_update(view, NULL, diff_argv, flags);
4456 diff_common_read(struct view *view, const char *data, struct diff_state *state)
4458 enum line_type type = get_line_type(data);
4460 if (!view->lines && type != LINE_COMMIT)
4461 state->reading_diff_stat = TRUE;
4463 if (state->reading_diff_stat) {
4464 size_t len = strlen(data);
4465 char *pipe = strchr(data, '|');
4466 bool has_histogram = data[len - 1] == '-' || data[len - 1] == '+';
4467 bool has_bin_diff = pipe && strstr(pipe, "Bin") && strstr(pipe, "->");
4468 bool has_rename = data[len - 1] == '0' && (strstr(data, "=>") || !strncmp(data, " ...", 4));
4470 if (pipe && (has_histogram || has_bin_diff || has_rename)) {
4471 return add_line_text(view, data, LINE_DIFF_STAT) != NULL;
4473 state->reading_diff_stat = FALSE;
4476 } else if (!strcmp(data, "---")) {
4477 state->reading_diff_stat = TRUE;
4480 if (!state->after_commit_title && !prefixcmp(data, " ")) {
4481 struct line *line = add_line_text(view, data, LINE_COMMIT);
4484 line->user_flags |= DIFF_LINE_COMMIT_TITLE;
4485 state->after_commit_title = TRUE;
4486 return line != NULL;
4489 if (type == LINE_DIFF_HEADER) {
4490 const int len = line_info[LINE_DIFF_HEADER].linelen;
4492 if (!strncmp(data + len, "combined ", strlen("combined ")) ||
4493 !strncmp(data + len, "cc ", strlen("cc ")))
4494 state->combined_diff = TRUE;
4497 /* ADD2 and DEL2 are only valid in combined diff hunks */
4498 if (!state->combined_diff && (type == LINE_DIFF_ADD2 || type == LINE_DIFF_DEL2))
4499 type = LINE_DEFAULT;
4501 return pager_common_read(view, data, type);
4505 diff_find_stat_entry(struct view *view, struct line *line, enum line_type type)
4507 struct line *marker = find_next_line_by_type(view, line, type);
4510 line == find_prev_line_by_type(view, marker, LINE_DIFF_HEADER);
4514 diff_common_enter(struct view *view, enum request request, struct line *line)
4516 if (line->type == LINE_DIFF_STAT) {
4517 int file_number = 0;
4519 while (view_has_line(view, line) && line->type == LINE_DIFF_STAT) {
4524 for (line = view->line; view_has_line(view, line); line++) {
4525 line = find_next_line_by_type(view, line, LINE_DIFF_HEADER);
4529 if (diff_find_stat_entry(view, line, LINE_DIFF_INDEX)
4530 || diff_find_stat_entry(view, line, LINE_DIFF_SIMILARITY)) {
4531 if (file_number == 1) {
4539 report("Failed to find file diff");
4543 select_view_line(view, line - view->line);
4548 return pager_request(view, request, line);
4553 diff_common_draw_part(struct view *view, enum line_type *type, char **text, char c, enum line_type next_type)
4555 char *sep = strchr(*text, c);
4559 draw_text(view, *type, *text);
4569 diff_common_draw(struct view *view, struct line *line, unsigned int lineno)
4571 char *text = line->data;
4572 enum line_type type = line->type;
4574 if (draw_lineno(view, lineno))
4577 if (line->wrapped && draw_text(view, LINE_DELIMITER, "+"))
4580 if (type == LINE_DIFF_STAT) {
4581 diff_common_draw_part(view, &type, &text, '|', LINE_DEFAULT);
4582 if (diff_common_draw_part(view, &type, &text, 'B', LINE_DEFAULT)) {
4583 /* Handle binary diffstat: Bin <deleted> -> <added> bytes */
4584 diff_common_draw_part(view, &type, &text, ' ', LINE_DIFF_DEL);
4585 diff_common_draw_part(view, &type, &text, '-', LINE_DEFAULT);
4586 diff_common_draw_part(view, &type, &text, ' ', LINE_DIFF_ADD);
4587 diff_common_draw_part(view, &type, &text, 'b', LINE_DEFAULT);
4590 diff_common_draw_part(view, &type, &text, '+', LINE_DIFF_ADD);
4591 diff_common_draw_part(view, &type, &text, '-', LINE_DIFF_DEL);
4595 if (line->user_flags & DIFF_LINE_COMMIT_TITLE)
4596 draw_commit_title(view, text, 4);
4598 draw_text(view, type, text);
4603 diff_read(struct view *view, char *data)
4605 struct diff_state *state = view->private;
4608 /* Fall back to retry if no diff will be shown. */
4609 if (view->lines == 0 && opt_file_argv) {
4610 int pos = argv_size(view->argv)
4611 - argv_size(opt_file_argv) - 1;
4613 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4614 for (; view->argv[pos]; pos++) {
4615 free((void *) view->argv[pos]);
4616 view->argv[pos] = NULL;
4620 io_done(view->pipe);
4621 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4628 return diff_common_read(view, data, state);
4632 diff_blame_line(const char *ref, const char *file, unsigned long lineno,
4633 struct blame_header *header, struct blame_commit *commit)
4635 char line_arg[SIZEOF_STR];
4636 const char *blame_argv[] = {
4637 "git", "blame", opt_encoding_arg, "-p", line_arg, ref, "--", file, NULL
4643 if (!string_format(line_arg, "-L%ld,+1", lineno))
4646 if (!io_run(&io, IO_RD, opt_cdup, blame_argv))
4649 while ((buf = io_get(&io, '\n', TRUE))) {
4651 if (!parse_blame_header(header, buf, 9999999))
4655 } else if (parse_blame_info(commit, buf)) {
4669 diff_get_lineno(struct view *view, struct line *line)
4671 const struct line *header, *chunk;
4673 unsigned int lineno;
4675 /* Verify that we are after a diff header and one of its chunks */
4676 header = find_prev_line_by_type(view, line, LINE_DIFF_HEADER);
4677 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
4678 if (!header || !chunk || chunk < header)
4682 * In a chunk header, the number after the '+' sign is the number of its
4683 * following line, in the new version of the file. We increment this
4684 * number for each non-deletion line, until the given line position.
4686 data = strchr(chunk->data, '+');
4690 lineno = atoi(data);
4692 while (chunk++ < line)
4693 if (chunk->type != LINE_DIFF_DEL)
4700 parse_chunk_lineno(int *lineno, const char *chunk, int marker)
4702 return prefixcmp(chunk, "@@ -") ||
4703 !(chunk = strchr(chunk, marker)) ||
4704 parse_int(lineno, chunk + 1, 0, 9999999) != OPT_OK;
4708 diff_trace_origin(struct view *view, struct line *line)
4710 struct line *diff = find_prev_line_by_type(view, line, LINE_DIFF_HEADER);
4711 struct line *chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
4712 const char *chunk_data;
4713 int chunk_marker = line->type == LINE_DIFF_DEL ? '-' : '+';
4715 const char *file = NULL;
4716 char ref[SIZEOF_REF];
4717 struct blame_header header;
4718 struct blame_commit commit;
4720 if (!diff || !chunk || chunk == line) {
4721 report("The line to trace must be inside a diff chunk");
4725 for (; diff < line && !file; diff++) {
4726 const char *data = diff->data;
4728 if (!prefixcmp(data, "--- a/")) {
4729 file = data + STRING_SIZE("--- a/");
4734 if (diff == line || !file) {
4735 report("Failed to read the file name");
4739 chunk_data = chunk->data;
4741 if (parse_chunk_lineno(&lineno, chunk_data, chunk_marker)) {
4742 report("Failed to read the line number");
4747 report("This is the origin of the line");
4751 for (chunk += 1; chunk < line; chunk++) {
4752 if (chunk->type == LINE_DIFF_ADD) {
4753 lineno += chunk_marker == '+';
4754 } else if (chunk->type == LINE_DIFF_DEL) {
4755 lineno += chunk_marker == '-';
4761 if (chunk_marker == '+')
4762 string_copy(ref, view->vid);
4764 string_format(ref, "%s^", view->vid);
4766 if (!diff_blame_line(ref, file, lineno, &header, &commit)) {
4767 report("Failed to read blame data");
4771 string_ncopy(opt_file, commit.filename, strlen(commit.filename));
4772 string_copy(opt_ref, header.id);
4773 opt_goto_line = header.orig_lineno - 1;
4775 return REQ_VIEW_BLAME;
4779 diff_get_pathname(struct view *view, struct line *line)
4781 const struct line *header;
4782 const char *dst = NULL;
4783 const char *prefixes[] = { " b/", "cc ", "combined " };
4786 header = find_prev_line_by_type(view, line, LINE_DIFF_HEADER);
4790 for (i = 0; i < ARRAY_SIZE(prefixes) && !dst; i++)
4791 dst = strstr(header->data, prefixes[i]);
4793 return dst ? dst + strlen(prefixes[--i]) : NULL;
4797 diff_request(struct view *view, enum request request, struct line *line)
4802 case REQ_VIEW_BLAME:
4803 return diff_trace_origin(view, line);
4805 case REQ_DIFF_CONTEXT_UP:
4806 case REQ_DIFF_CONTEXT_DOWN:
4807 if (!update_diff_context(request))
4814 file = diff_get_pathname(view, line);
4815 if (!file || access(file, R_OK))
4816 return pager_request(view, request, line);
4817 open_editor(file, diff_get_lineno(view, line));
4821 return diff_common_enter(view, request, line);
4828 return pager_request(view, request, line);
4833 diff_select(struct view *view, struct line *line)
4835 if (line->type == LINE_DIFF_STAT) {
4836 string_format(view->ref, "Press '%s' to jump to file diff",
4837 get_view_key(view, REQ_ENTER));
4839 const char *file = diff_get_pathname(view, line);
4842 string_format(view->ref, "Changes to '%s'", file);
4843 string_format(opt_file, "%s", file);
4846 string_ncopy(view->ref, view->id, strlen(view->id));
4847 pager_select(view, line);
4852 static struct view_ops diff_ops = {
4855 VIEW_DIFF_LIKE | VIEW_ADD_DESCRIBE_REF | VIEW_ADD_PAGER_REFS | VIEW_STDIN | VIEW_FILE_FILTER,
4856 sizeof(struct diff_state),
4870 help_draw(struct view *view, struct line *line, unsigned int lineno)
4872 if (line->type == LINE_HELP_KEYMAP) {
4873 struct keymap *keymap = line->data;
4875 draw_formatted(view, line->type, "[%c] %s bindings",
4876 keymap->hidden ? '+' : '-', keymap->name);
4879 return pager_draw(view, line, lineno);
4884 help_open_keymap_title(struct view *view, struct keymap *keymap)
4886 add_line_static_data(view, keymap, LINE_HELP_KEYMAP);
4887 return keymap->hidden;
4891 help_open_keymap(struct view *view, struct keymap *keymap)
4893 const char *group = NULL;
4894 char buf[SIZEOF_STR];
4895 bool add_title = TRUE;
4898 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4899 const char *key = NULL;
4901 if (req_info[i].request == REQ_NONE)
4904 if (!req_info[i].request) {
4905 group = req_info[i].help;
4909 key = get_keys(keymap, req_info[i].request, TRUE);
4913 if (add_title && help_open_keymap_title(view, keymap))
4918 add_line_text(view, group, LINE_HELP_GROUP);
4922 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4923 enum_name(req_info[i]), req_info[i].help);
4926 group = "External commands:";
4928 for (i = 0; i < run_requests; i++) {
4929 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4932 if (!req || req->keymap != keymap)
4935 key = get_key_name(req->key);
4937 key = "(no key defined)";
4939 if (add_title && help_open_keymap_title(view, keymap))
4944 add_line_text(view, group, LINE_HELP_GROUP);
4948 if (!argv_to_string(req->argv, buf, sizeof(buf), " "))
4951 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4956 help_open(struct view *view, enum open_flags flags)
4958 struct keymap *keymap;
4961 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4962 add_line_text(view, "", LINE_DEFAULT);
4964 for (keymap = keymaps; keymap; keymap = keymap->next)
4965 help_open_keymap(view, keymap);
4971 help_request(struct view *view, enum request request, struct line *line)
4975 if (line->type == LINE_HELP_KEYMAP) {
4976 struct keymap *keymap = line->data;
4978 keymap->hidden = !keymap->hidden;
4984 return pager_request(view, request, line);
4988 static struct view_ops help_ops = {