1 /* Copyright (c) 2006 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.
15 #define VERSION "tig-0.3"
36 #define __NORETURN __attribute__((__noreturn__))
41 static void __NORETURN
die(const char *err
, ...);
42 static void report(const char *msg
, ...);
43 static int read_properties(FILE *pipe
, const char *separators
, int (*read
)(char *, int, char *, int));
44 static void set_nonblocking_input(bool loading
);
45 static size_t utf8_length(const char *string
, size_t max_width
, int *coloffset
, int *trimmed
);
47 #define ABS(x) ((x) >= 0 ? (x) : -(x))
48 #define MIN(x, y) ((x) < (y) ? (x) : (y))
50 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
51 #define STRING_SIZE(x) (sizeof(x) - 1)
53 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
54 #define SIZEOF_CMD 1024 /* Size of command buffer. */
55 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
57 /* This color name can be used to refer to the default term colors. */
58 #define COLOR_DEFAULT (-1)
60 /* The format and size of the date column in the main view. */
61 #define DATE_FORMAT "%Y-%m-%d %H:%M"
62 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
64 #define AUTHOR_COLS 20
66 /* The default interval between line numbers. */
67 #define NUMBER_INTERVAL 1
71 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
73 #define TIG_LS_REMOTE \
74 "git ls-remote . 2>/dev/null"
76 #define TIG_DIFF_CMD \
77 "git show --patch-with-stat --find-copies-harder -B -C %s"
80 "git log --cc --stat -n100 %s"
82 #define TIG_MAIN_CMD \
83 "git log --topo-order --stat --pretty=raw %s"
85 /* XXX: Needs to be defined to the empty string. */
86 #define TIG_HELP_CMD ""
87 #define TIG_PAGER_CMD ""
89 /* Some ascii-shorthands fitted into the ncurses namespace. */
91 #define KEY_RETURN '\r'
96 char *name
; /* Ref name; tag or head names are shortened. */
97 char id
[41]; /* Commit SHA1 ID */
98 unsigned int tag
:1; /* Is it a tag? */
99 unsigned int next
:1; /* For ref lists: are there more refs? */
102 static struct ref
**get_refs(char *id
);
111 set_from_int_map(struct int_map
*map
, size_t map_size
,
112 int *value
, const char *name
, int namelen
)
117 for (i
= 0; i
< map_size
; i
++)
118 if (namelen
== map
[i
].namelen
&&
119 !strncasecmp(name
, map
[i
].name
, namelen
)) {
120 *value
= map
[i
].value
;
133 string_ncopy(char *dst
, const char *src
, int dstlen
)
135 strncpy(dst
, src
, dstlen
- 1);
140 /* Shorthand for safely copying into a fixed buffer. */
141 #define string_copy(dst, src) \
142 string_ncopy(dst, src, sizeof(dst))
145 chomp_string(char *name
)
149 while (isspace(*name
))
152 namelen
= strlen(name
) - 1;
153 while (namelen
> 0 && isspace(name
[namelen
]))
160 string_nformat(char *buf
, size_t bufsize
, int *bufpos
, const char *fmt
, ...)
163 int pos
= bufpos ?
*bufpos
: 0;
166 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
172 return pos
>= bufsize ? FALSE
: TRUE
;
175 #define string_format(buf, fmt, args...) \
176 string_nformat(buf, sizeof(buf), NULL, fmt, args)
178 #define string_format_from(buf, from, fmt, args...) \
179 string_nformat(buf, sizeof(buf), from, fmt, args)
182 string_enum_compare(const char *str1
, const char *str2
, int len
)
186 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
188 /* Diff-Header == DIFF_HEADER */
189 for (i
= 0; i
< len
; i
++) {
190 if (toupper(str1
[i
]) == toupper(str2
[i
]))
193 if (string_enum_sep(str1
[i
]) &&
194 string_enum_sep(str2
[i
]))
197 return str1
[i
] - str2
[i
];
205 * NOTE: The following is a slightly modified copy of the git project's shell
206 * quoting routines found in the quote.c file.
208 * Help to copy the thing properly quoted for the shell safety. any single
209 * quote is replaced with '\'', any exclamation point is replaced with '\!',
210 * and the whole thing is enclosed in a
213 * original sq_quote result
214 * name ==> name ==> 'name'
215 * a b ==> a b ==> 'a b'
216 * a'b ==> a'\''b ==> 'a'\''b'
217 * a!b ==> a'\!'b ==> 'a'\!'b'
221 sq_quote(char buf
[SIZEOF_CMD
], size_t bufsize
, const char *src
)
225 #define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
228 while ((c
= *src
++)) {
229 if (c
== '\'' || c
== '!') {
249 /* XXX: Keep the view request first and in sync with views[]. */ \
250 REQ_GROUP("View switching") \
251 REQ_(VIEW_MAIN, "Show main view"), \
252 REQ_(VIEW_DIFF, "Show diff view"), \
253 REQ_(VIEW_LOG, "Show log view"), \
254 REQ_(VIEW_HELP, "Show help page"), \
255 REQ_(VIEW_PAGER, "Show pager view"), \
257 REQ_GROUP("View manipulation") \
258 REQ_(ENTER, "Enter current line and scroll"), \
259 REQ_(NEXT, "Move to next"), \
260 REQ_(PREVIOUS, "Move to previous"), \
261 REQ_(VIEW_NEXT, "Move focus to next view"), \
262 REQ_(VIEW_CLOSE, "Close the current view"), \
263 REQ_(QUIT, "Close all views and quit"), \
265 REQ_GROUP("Cursor navigation") \
266 REQ_(MOVE_UP, "Move cursor one line up"), \
267 REQ_(MOVE_DOWN, "Move cursor one line down"), \
268 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
269 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
270 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
271 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
273 REQ_GROUP("Scrolling") \
274 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
275 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
276 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
277 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
280 REQ_(PROMPT, "Bring up the prompt"), \
281 REQ_(SCREEN_UPDATE, "Update the screen"), \
282 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
283 REQ_(SCREEN_RESIZE, "Resize the screen"), \
284 REQ_(SHOW_VERSION, "Show version information"), \
285 REQ_(STOP_LOADING, "Stop all loading views"), \
286 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
287 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization")
290 /* User action requests. */
292 #define REQ_GROUP(help)
293 #define REQ_(req, help) REQ_##req
295 /* Offset all requests to avoid conflicts with ncurses getch values. */
296 REQ_OFFSET
= KEY_MAX
+ 1,
304 struct request_info
{
305 enum request request
;
311 static struct request_info req_info
[] = {
312 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
313 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
320 get_request(const char *name
)
322 int namelen
= strlen(name
);
325 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
326 if (req_info
[i
].namelen
== namelen
&&
327 !string_enum_compare(req_info
[i
].name
, name
, namelen
))
328 return req_info
[i
].request
;
338 static const char usage
[] =
339 VERSION
" (" __DATE__
")\n"
341 "Usage: tig [options]\n"
342 " or: tig [options] [--] [git log options]\n"
343 " or: tig [options] log [git log options]\n"
344 " or: tig [options] diff [git diff options]\n"
345 " or: tig [options] show [git show options]\n"
346 " or: tig [options] < [git command output]\n"
349 " -l Start up in log view\n"
350 " -d Start up in diff view\n"
351 " -n[I], --line-number[=I] Show line numbers with given interval\n"
352 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
353 " -- Mark end of tig options\n"
354 " -v, --version Show version and exit\n"
355 " -h, --help Show help message and exit\n";
357 /* Option and state variables. */
358 static bool opt_line_number
= FALSE
;
359 static bool opt_rev_graph
= TRUE
;
360 static int opt_num_interval
= NUMBER_INTERVAL
;
361 static int opt_tab_size
= TABSIZE
;
362 static enum request opt_request
= REQ_VIEW_MAIN
;
363 static char opt_cmd
[SIZEOF_CMD
] = "";
364 static char opt_encoding
[20] = "";
365 static bool opt_utf8
= TRUE
;
366 static FILE *opt_pipe
= NULL
;
374 check_option(char *opt
, char short_name
, char *name
, enum option_type type
, ...)
384 int namelen
= strlen(name
);
388 if (strncmp(opt
, name
, namelen
))
391 if (opt
[namelen
] == '=')
392 value
= opt
+ namelen
+ 1;
395 if (!short_name
|| opt
[1] != short_name
)
400 va_start(args
, type
);
401 if (type
== OPT_INT
) {
402 number
= va_arg(args
, int *);
404 *number
= atoi(value
);
411 /* Returns the index of log or diff command or -1 to exit. */
413 parse_options(int argc
, char *argv
[])
417 for (i
= 1; i
< argc
; i
++) {
420 if (!strcmp(opt
, "-l")) {
421 opt_request
= REQ_VIEW_LOG
;
425 if (!strcmp(opt
, "-d")) {
426 opt_request
= REQ_VIEW_DIFF
;
430 if (check_option(opt
, 'n', "line-number", OPT_INT
, &opt_num_interval
)) {
431 opt_line_number
= TRUE
;
435 if (check_option(opt
, 'b', "tab-size", OPT_INT
, &opt_tab_size
)) {
436 opt_tab_size
= MIN(opt_tab_size
, TABSIZE
);
440 if (check_option(opt
, 'v', "version", OPT_NONE
)) {
441 printf("tig version %s\n", VERSION
);
445 if (check_option(opt
, 'h', "help", OPT_NONE
)) {
450 if (!strcmp(opt
, "--")) {
455 if (!strcmp(opt
, "log") ||
456 !strcmp(opt
, "diff") ||
457 !strcmp(opt
, "show")) {
458 opt_request
= opt
[0] == 'l'
459 ? REQ_VIEW_LOG
: REQ_VIEW_DIFF
;
463 if (opt
[0] && opt
[0] != '-')
466 die("unknown option '%s'\n\n%s", opt
, usage
);
469 if (!isatty(STDIN_FILENO
)) {
470 opt_request
= REQ_VIEW_PAGER
;
473 } else if (i
< argc
) {
476 if (opt_request
== REQ_VIEW_MAIN
)
477 /* XXX: This is vulnerable to the user overriding
478 * options required for the main view parser. */
479 string_copy(opt_cmd
, "git log --stat --pretty=raw");
481 string_copy(opt_cmd
, "git");
482 buf_size
= strlen(opt_cmd
);
484 while (buf_size
< sizeof(opt_cmd
) && i
< argc
) {
485 opt_cmd
[buf_size
++] = ' ';
486 buf_size
= sq_quote(opt_cmd
, buf_size
, argv
[i
++]);
489 if (buf_size
>= sizeof(opt_cmd
))
490 die("command too long");
492 opt_cmd
[buf_size
] = 0;
496 if (*opt_encoding
&& strcasecmp(opt_encoding
, "UTF-8"))
504 * Line-oriented content detection.
508 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
509 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
510 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
511 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
512 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
513 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
514 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
515 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
516 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
517 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
518 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
519 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
520 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
521 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
522 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
523 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
524 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
525 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
526 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
527 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
528 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
529 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
530 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
531 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
532 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
533 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
534 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
535 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
536 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
537 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
538 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
539 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
540 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
541 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
542 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
543 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
544 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
545 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
548 #define LINE(type, line, fg, bg, attr) \
555 const char *name
; /* Option name. */
556 int namelen
; /* Size of option name. */
557 const char *line
; /* The start of line to match. */
558 int linelen
; /* Size of string to match. */
559 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
562 static struct line_info line_info
[] = {
563 #define LINE(type, line, fg, bg, attr) \
564 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
569 static enum line_type
570 get_line_type(char *line
)
572 int linelen
= strlen(line
);
575 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
576 /* Case insensitive search matches Signed-off-by lines better. */
577 if (linelen
>= line_info
[type
].linelen
&&
578 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
585 get_line_attr(enum line_type type
)
587 assert(type
< ARRAY_SIZE(line_info
));
588 return COLOR_PAIR(type
) | line_info
[type
].attr
;
591 static struct line_info
*
592 get_line_info(char *name
, int namelen
)
596 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
597 if (namelen
== line_info
[type
].namelen
&&
598 !string_enum_compare(line_info
[type
].name
, name
, namelen
))
599 return &line_info
[type
];
607 int default_bg
= COLOR_BLACK
;
608 int default_fg
= COLOR_WHITE
;
613 if (use_default_colors() != ERR
) {
618 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
619 struct line_info
*info
= &line_info
[type
];
620 int bg
= info
->bg
== COLOR_DEFAULT ? default_bg
: info
->bg
;
621 int fg
= info
->fg
== COLOR_DEFAULT ? default_fg
: info
->fg
;
623 init_pair(type
, fg
, bg
);
629 void *data
; /* User data */
639 enum request request
;
640 struct keybinding
*next
;
643 static struct keybinding default_keybindings
[] = {
645 { 'm', REQ_VIEW_MAIN
},
646 { 'd', REQ_VIEW_DIFF
},
647 { 'l', REQ_VIEW_LOG
},
648 { 'p', REQ_VIEW_PAGER
},
649 { 'h', REQ_VIEW_HELP
},
650 { '?', REQ_VIEW_HELP
},
652 /* View manipulation */
653 { 'q', REQ_VIEW_CLOSE
},
654 { KEY_TAB
, REQ_VIEW_NEXT
},
655 { KEY_RETURN
, REQ_ENTER
},
656 { KEY_UP
, REQ_PREVIOUS
},
657 { KEY_DOWN
, REQ_NEXT
},
659 /* Cursor navigation */
660 { 'k', REQ_MOVE_UP
},
661 { 'j', REQ_MOVE_DOWN
},
662 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
663 { KEY_END
, REQ_MOVE_LAST_LINE
},
664 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
665 { ' ', REQ_MOVE_PAGE_DOWN
},
666 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
667 { 'b', REQ_MOVE_PAGE_UP
},
668 { '-', REQ_MOVE_PAGE_UP
},
671 { KEY_IC
, REQ_SCROLL_LINE_UP
},
672 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
673 { 'w', REQ_SCROLL_PAGE_UP
},
674 { 's', REQ_SCROLL_PAGE_DOWN
},
678 { 'z', REQ_STOP_LOADING
},
679 { 'v', REQ_SHOW_VERSION
},
680 { 'r', REQ_SCREEN_REDRAW
},
681 { 'n', REQ_TOGGLE_LINENO
},
682 { 'g', REQ_TOGGLE_REV_GRAPH
},
685 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
686 { ERR
, REQ_SCREEN_UPDATE
},
688 /* Use the ncurses SIGWINCH handler. */
689 { KEY_RESIZE
, REQ_SCREEN_RESIZE
},
692 #define KEYMAP_INFO \
701 #define KEYMAP_(name) KEYMAP_##name
706 static struct int_map keymap_table
[] = {
707 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
712 #define set_keymap(map, name) \
713 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
715 static struct keybinding
*keybindings
[ARRAY_SIZE(keymap_table
)];
718 add_keybinding(enum keymap keymap
, enum request request
, int key
)
720 struct keybinding
*keybinding
;
722 keybinding
= calloc(1, sizeof(*keybinding
));
724 die("Failed to allocate keybinding");
726 keybinding
->alias
= key
;
727 keybinding
->request
= request
;
728 keybinding
->next
= keybindings
[keymap
];
729 keybindings
[keymap
] = keybinding
;
732 /* Looks for a key binding first in the given map, then in the generic map, and
733 * lastly in the default keybindings. */
735 get_keybinding(enum keymap keymap
, int key
)
737 struct keybinding
*kbd
;
740 for (kbd
= keybindings
[keymap
]; kbd
; kbd
= kbd
->next
)
741 if (kbd
->alias
== key
)
744 for (kbd
= keybindings
[KEYMAP_GENERIC
]; kbd
; kbd
= kbd
->next
)
745 if (kbd
->alias
== key
)
748 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
749 if (default_keybindings
[i
].alias
== key
)
750 return default_keybindings
[i
].request
;
752 return (enum request
) key
;
761 static struct key key_table
[] = {
762 { "Enter", KEY_RETURN
},
764 { "Backspace", KEY_BACKSPACE
},
766 { "Escape", KEY_ESC
},
767 { "Left", KEY_LEFT
},
768 { "Right", KEY_RIGHT
},
770 { "Down", KEY_DOWN
},
771 { "Insert", KEY_IC
},
772 { "Delete", KEY_DC
},
773 { "Home", KEY_HOME
},
775 { "PageUp", KEY_PPAGE
},
776 { "PageDown", KEY_NPAGE
},
786 { "F10", KEY_F(10) },
787 { "F11", KEY_F(11) },
788 { "F12", KEY_F(12) },
792 get_key_value(const char *name
)
796 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
797 if (!strcasecmp(key_table
[i
].name
, name
))
798 return key_table
[i
].value
;
800 if (strlen(name
) == 1 && isprint(*name
))
807 get_key(enum request request
)
809 static char buf
[BUFSIZ
];
810 static char key_char
[] = "'X'";
817 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
818 struct keybinding
*keybinding
= &default_keybindings
[i
];
822 if (keybinding
->request
!= request
)
825 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
826 if (key_table
[key
].value
== keybinding
->alias
)
827 seq
= key_table
[key
].name
;
830 keybinding
->alias
< 127 &&
831 isprint(keybinding
->alias
)) {
832 key_char
[1] = (char) keybinding
->alias
;
839 if (!string_format_from(buf
, &pos
, "%s%s", sep
, seq
))
840 return "Too many keybindings!";
849 * User config file handling.
852 static struct int_map color_map
[] = {
853 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
865 #define set_color(color, name) \
866 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
868 static struct int_map attr_map
[] = {
869 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
879 #define set_attribute(attr, name) \
880 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
882 static int config_lineno
;
883 static bool config_errors
;
884 static char *config_msg
;
886 /* Wants: object fgcolor bgcolor [attr] */
888 option_color_command(int argc
, char *argv
[])
890 struct line_info
*info
;
892 if (argc
!= 3 && argc
!= 4) {
893 config_msg
= "Wrong number of arguments given to color command";
897 info
= get_line_info(argv
[0], strlen(argv
[0]));
899 config_msg
= "Unknown color name";
903 if (set_color(&info
->fg
, argv
[1]) == ERR
) {
904 config_msg
= "Unknown color";
908 if (set_color(&info
->bg
, argv
[2]) == ERR
) {
909 config_msg
= "Unknown color";
913 if (argc
== 4 && set_attribute(&info
->attr
, argv
[3]) == ERR
) {
914 config_msg
= "Unknown attribute";
921 /* Wants: name = value */
923 option_set_command(int argc
, char *argv
[])
926 config_msg
= "Wrong number of arguments given to set command";
930 if (strcmp(argv
[1], "=")) {
931 config_msg
= "No value assigned";
935 if (!strcmp(argv
[0], "show-rev-graph")) {
936 opt_rev_graph
= (!strcmp(argv
[2], "1") ||
937 !strcmp(argv
[2], "true") ||
938 !strcmp(argv
[2], "yes"));
942 if (!strcmp(argv
[0], "line-number-interval")) {
943 opt_num_interval
= atoi(argv
[2]);
947 if (!strcmp(argv
[0], "tab-size")) {
948 opt_tab_size
= atoi(argv
[2]);
952 if (!strcmp(argv
[0], "encoding")) {
953 string_copy(opt_encoding
, argv
[2]);
960 /* Wants: mode request key */
962 option_bind_command(int argc
, char *argv
[])
964 enum request request
;
969 config_msg
= "Wrong number of arguments given to bind command";
973 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
974 config_msg
= "Unknown key map";
978 key
= get_key_value(argv
[1]);
980 config_msg
= "Unknown key";
984 request
= get_request(argv
[2]);
985 if (request
== REQ_UNKNOWN
) {
986 config_msg
= "Unknown request name";
990 add_keybinding(keymap
, request
, key
);
996 set_option(char *opt
, char *value
)
1003 while (argc
< ARRAY_SIZE(argv
) && (valuelen
= strcspn(value
, " \t"))) {
1004 argv
[argc
++] = value
;
1011 while (isspace(*value
))
1015 if (!strcmp(opt
, "color"))
1016 return option_color_command(argc
, argv
);
1018 if (!strcmp(opt
, "set"))
1019 return option_set_command(argc
, argv
);
1021 if (!strcmp(opt
, "bind"))
1022 return option_bind_command(argc
, argv
);
1028 read_option(char *opt
, int optlen
, char *value
, int valuelen
)
1031 config_msg
= "Internal error";
1033 optlen
= strcspn(opt
, "#;");
1035 /* The whole line is a commend or empty. */
1038 } else if (opt
[optlen
] != 0) {
1039 /* Part of the option name is a comment, so the value part
1040 * should be ignored. */
1042 opt
[optlen
] = value
[valuelen
] = 0;
1044 /* Else look for comment endings in the value. */
1045 valuelen
= strcspn(value
, "#;");
1046 value
[valuelen
] = 0;
1049 if (set_option(opt
, value
) == ERR
) {
1050 fprintf(stderr
, "Error on line %d, near '%.*s' option: %s\n",
1051 config_lineno
, optlen
, opt
, config_msg
);
1052 config_errors
= TRUE
;
1055 /* Always keep going if errors are encountered. */
1062 char *home
= getenv("HOME");
1067 config_errors
= FALSE
;
1069 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1072 /* It's ok that the file doesn't exist. */
1073 file
= fopen(buf
, "r");
1077 if (read_properties(file
, " \t", read_option
) == ERR
||
1078 config_errors
== TRUE
)
1079 fprintf(stderr
, "Errors while loading %s.\n", buf
);
1092 /* The display array of active views and the index of the current view. */
1093 static struct view
*display
[2];
1094 static unsigned int current_view
;
1096 #define foreach_view(view, i) \
1097 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1099 #define displayed_views() (display[1] != NULL ? 2 : 1)
1101 /* Current head and commit ID */
1102 static char ref_commit
[SIZEOF_REF
] = "HEAD";
1103 static char ref_head
[SIZEOF_REF
] = "HEAD";
1106 const char *name
; /* View name */
1107 const char *cmd_fmt
; /* Default command line format */
1108 const char *cmd_env
; /* Command line set via environment */
1109 const char *id
; /* Points to either of ref_{head,commit} */
1111 struct view_ops
*ops
; /* View operations */
1113 enum keymap keymap
; /* What keymap does this view have */
1115 char cmd
[SIZEOF_CMD
]; /* Command buffer */
1116 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
1117 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
1119 int height
, width
; /* The width and height of the main window */
1120 WINDOW
*win
; /* The main window */
1121 WINDOW
*title
; /* The title window living below the main window */
1124 unsigned long offset
; /* Offset of the window top */
1125 unsigned long lineno
; /* Current line number */
1127 /* If non-NULL, points to the view that opened this view. If this view
1128 * is closed tig will switch back to the parent view. */
1129 struct view
*parent
;
1132 unsigned long lines
; /* Total number of lines */
1133 struct line
*line
; /* Line index */
1134 unsigned long line_size
;/* Total number of allocated lines */
1135 unsigned int digits
; /* Number of digits in the lines member. */
1143 /* What type of content being displayed. Used in the title bar. */
1145 /* Draw one line; @lineno must be < view->height. */
1146 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
1147 /* Read one line; updates view->line. */
1148 bool (*read
)(struct view
*view
, char *data
);
1149 /* Depending on view, change display based on current line. */
1150 bool (*enter
)(struct view
*view
, struct line
*line
);
1153 static struct view_ops pager_ops
;
1154 static struct view_ops main_ops
;
1156 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1157 { name, cmd, #env, ref, ops, map}
1159 #define VIEW_(id, name, ops, ref) \
1160 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1163 static struct view views
[] = {
1164 VIEW_(MAIN
, "main", &main_ops
, ref_head
),
1165 VIEW_(DIFF
, "diff", &pager_ops
, ref_commit
),
1166 VIEW_(LOG
, "log", &pager_ops
, ref_head
),
1167 VIEW_(HELP
, "help", &pager_ops
, "static"),
1168 VIEW_(PAGER
, "pager", &pager_ops
, "static"),
1171 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1175 draw_view_line(struct view
*view
, unsigned int lineno
)
1177 if (view
->offset
+ lineno
>= view
->lines
)
1180 return view
->ops
->draw(view
, &view
->line
[view
->offset
+ lineno
], lineno
);
1184 redraw_view_from(struct view
*view
, int lineno
)
1186 assert(0 <= lineno
&& lineno
< view
->height
);
1188 for (; lineno
< view
->height
; lineno
++) {
1189 if (!draw_view_line(view
, lineno
))
1193 redrawwin(view
->win
);
1194 wrefresh(view
->win
);
1198 redraw_view(struct view
*view
)
1201 redraw_view_from(view
, 0);
1206 update_view_title(struct view
*view
)
1208 if (view
== display
[current_view
])
1209 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
1211 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
1213 werase(view
->title
);
1214 wmove(view
->title
, 0, 0);
1217 wprintw(view
->title
, "[%s] %s", view
->name
, view
->ref
);
1219 wprintw(view
->title
, "[%s]", view
->name
);
1221 if (view
->lines
|| view
->pipe
) {
1222 unsigned int view_lines
= view
->offset
+ view
->height
;
1223 unsigned int lines
= view
->lines
1224 ?
MIN(view_lines
, view
->lines
) * 100 / view
->lines
1227 wprintw(view
->title
, " - %s %d of %d (%d%%)",
1235 time_t secs
= time(NULL
) - view
->start_time
;
1237 /* Three git seconds are a long time ... */
1239 wprintw(view
->title
, " %lds", secs
);
1242 wmove(view
->title
, 0, view
->width
- 1);
1243 wrefresh(view
->title
);
1247 resize_display(void)
1250 struct view
*base
= display
[0];
1251 struct view
*view
= display
[1] ? display
[1] : display
[0];
1253 /* Setup window dimensions */
1255 getmaxyx(stdscr
, base
->height
, base
->width
);
1257 /* Make room for the status window. */
1261 /* Horizontal split. */
1262 view
->width
= base
->width
;
1263 view
->height
= SCALE_SPLIT_VIEW(base
->height
);
1264 base
->height
-= view
->height
;
1266 /* Make room for the title bar. */
1270 /* Make room for the title bar. */
1275 foreach_view (view
, i
) {
1277 view
->win
= newwin(view
->height
, 0, offset
, 0);
1279 die("Failed to create %s view", view
->name
);
1281 scrollok(view
->win
, TRUE
);
1283 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
1285 die("Failed to create title window");
1288 wresize(view
->win
, view
->height
, view
->width
);
1289 mvwin(view
->win
, offset
, 0);
1290 mvwin(view
->title
, offset
+ view
->height
, 0);
1293 offset
+= view
->height
+ 1;
1298 redraw_display(void)
1303 foreach_view (view
, i
) {
1305 update_view_title(view
);
1310 update_display_cursor(void)
1312 struct view
*view
= display
[current_view
];
1314 /* Move the cursor to the right-most column of the cursor line.
1316 * XXX: This could turn out to be a bit expensive, but it ensures that
1317 * the cursor does not jump around. */
1319 wmove(view
->win
, view
->lineno
- view
->offset
, view
->width
- 1);
1320 wrefresh(view
->win
);
1328 /* Scrolling backend */
1330 do_scroll_view(struct view
*view
, int lines
, bool redraw
)
1332 /* The rendering expects the new offset. */
1333 view
->offset
+= lines
;
1335 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
1338 /* Redraw the whole screen if scrolling is pointless. */
1339 if (view
->height
< ABS(lines
)) {
1343 int line
= lines
> 0 ? view
->height
- lines
: 0;
1344 int end
= line
+ ABS(lines
);
1346 wscrl(view
->win
, lines
);
1348 for (; line
< end
; line
++) {
1349 if (!draw_view_line(view
, line
))
1354 /* Move current line into the view. */
1355 if (view
->lineno
< view
->offset
) {
1356 view
->lineno
= view
->offset
;
1357 draw_view_line(view
, 0);
1359 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
1360 if (view
->lineno
== view
->offset
+ view
->height
) {
1361 /* Clear the hidden line so it doesn't show if the view
1362 * is scrolled up. */
1363 wmove(view
->win
, view
->height
, 0);
1364 wclrtoeol(view
->win
);
1366 view
->lineno
= view
->offset
+ view
->height
- 1;
1367 draw_view_line(view
, view
->lineno
- view
->offset
);
1370 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
1375 redrawwin(view
->win
);
1376 wrefresh(view
->win
);
1380 /* Scroll frontend */
1382 scroll_view(struct view
*view
, enum request request
)
1387 case REQ_SCROLL_PAGE_DOWN
:
1388 lines
= view
->height
;
1389 case REQ_SCROLL_LINE_DOWN
:
1390 if (view
->offset
+ lines
> view
->lines
)
1391 lines
= view
->lines
- view
->offset
;
1393 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
1394 report("Cannot scroll beyond the last line");
1399 case REQ_SCROLL_PAGE_UP
:
1400 lines
= view
->height
;
1401 case REQ_SCROLL_LINE_UP
:
1402 if (lines
> view
->offset
)
1403 lines
= view
->offset
;
1406 report("Cannot scroll beyond the first line");
1414 die("request %d not handled in switch", request
);
1417 do_scroll_view(view
, lines
, TRUE
);
1422 move_view(struct view
*view
, enum request request
, bool redraw
)
1427 case REQ_MOVE_FIRST_LINE
:
1428 steps
= -view
->lineno
;
1431 case REQ_MOVE_LAST_LINE
:
1432 steps
= view
->lines
- view
->lineno
- 1;
1435 case REQ_MOVE_PAGE_UP
:
1436 steps
= view
->height
> view
->lineno
1437 ?
-view
->lineno
: -view
->height
;
1440 case REQ_MOVE_PAGE_DOWN
:
1441 steps
= view
->lineno
+ view
->height
>= view
->lines
1442 ? view
->lines
- view
->lineno
- 1 : view
->height
;
1454 die("request %d not handled in switch", request
);
1457 if (steps
<= 0 && view
->lineno
== 0) {
1458 report("Cannot move beyond the first line");
1461 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
1462 report("Cannot move beyond the last line");
1466 /* Move the current line */
1467 view
->lineno
+= steps
;
1468 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
1470 /* Repaint the old "current" line if we be scrolling */
1471 if (ABS(steps
) < view
->height
) {
1472 int prev_lineno
= view
->lineno
- steps
- view
->offset
;
1474 wmove(view
->win
, prev_lineno
, 0);
1475 wclrtoeol(view
->win
);
1476 draw_view_line(view
, prev_lineno
);
1479 /* Check whether the view needs to be scrolled */
1480 if (view
->lineno
< view
->offset
||
1481 view
->lineno
>= view
->offset
+ view
->height
) {
1482 if (steps
< 0 && -steps
> view
->offset
) {
1483 steps
= -view
->offset
;
1485 } else if (steps
> 0) {
1486 if (view
->lineno
== view
->lines
- 1 &&
1487 view
->lines
> view
->height
) {
1488 steps
= view
->lines
- view
->offset
- 1;
1489 if (steps
>= view
->height
)
1490 steps
-= view
->height
- 1;
1494 do_scroll_view(view
, steps
, redraw
);
1498 /* Draw the current line */
1499 draw_view_line(view
, view
->lineno
- view
->offset
);
1504 redrawwin(view
->win
);
1505 wrefresh(view
->win
);
1511 * Incremental updating
1515 end_update(struct view
*view
)
1519 set_nonblocking_input(FALSE
);
1520 if (view
->pipe
== stdin
)
1528 begin_update(struct view
*view
)
1530 const char *id
= view
->id
;
1536 string_copy(view
->cmd
, opt_cmd
);
1538 /* When running random commands, the view ref could have become
1539 * invalid so clear it. */
1542 const char *format
= view
->cmd_env ? view
->cmd_env
: view
->cmd_fmt
;
1544 if (!string_format(view
->cmd
, format
, id
, id
, id
, id
, id
))
1548 /* Special case for the pager view. */
1550 view
->pipe
= opt_pipe
;
1553 view
->pipe
= popen(view
->cmd
, "r");
1559 set_nonblocking_input(TRUE
);
1564 string_copy(view
->vid
, id
);
1569 for (i
= 0; i
< view
->lines
; i
++)
1570 if (view
->line
[i
].data
)
1571 free(view
->line
[i
].data
);
1577 view
->start_time
= time(NULL
);
1582 static struct line
*
1583 realloc_lines(struct view
*view
, size_t line_size
)
1585 struct line
*tmp
= realloc(view
->line
, sizeof(*view
->line
) * line_size
);
1591 view
->line_size
= line_size
;
1596 update_view(struct view
*view
)
1598 char buffer
[BUFSIZ
];
1600 /* The number of lines to read. If too low it will cause too much
1601 * redrawing (and possible flickering), if too high responsiveness
1603 unsigned long lines
= view
->height
;
1604 int redraw_from
= -1;
1609 /* Only redraw if lines are visible. */
1610 if (view
->offset
+ view
->height
>= view
->lines
)
1611 redraw_from
= view
->lines
- view
->offset
;
1613 if (!realloc_lines(view
, view
->lines
+ lines
))
1616 while ((line
= fgets(buffer
, sizeof(buffer
), view
->pipe
))) {
1617 int linelen
= strlen(line
);
1620 line
[linelen
- 1] = 0;
1622 if (!view
->ops
->read(view
, line
))
1632 lines
= view
->lines
;
1633 for (digits
= 0; lines
; digits
++)
1636 /* Keep the displayed view in sync with line number scaling. */
1637 if (digits
!= view
->digits
) {
1638 view
->digits
= digits
;
1643 if (redraw_from
>= 0) {
1644 /* If this is an incremental update, redraw the previous line
1645 * since for commits some members could have changed when
1646 * loading the main view. */
1647 if (redraw_from
> 0)
1650 /* Incrementally draw avoids flickering. */
1651 redraw_view_from(view
, redraw_from
);
1654 /* Update the title _after_ the redraw so that if the redraw picks up a
1655 * commit reference in view->ref it'll be available here. */
1656 update_view_title(view
);
1658 if (ferror(view
->pipe
)) {
1659 report("Failed to read: %s", strerror(errno
));
1662 } else if (feof(view
->pipe
)) {
1670 report("Allocation failure");
1682 static void open_help_view(struct view
*view
)
1685 int lines
= ARRAY_SIZE(req_info
) + 2;
1688 if (view
->lines
> 0)
1691 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1692 if (!req_info
[i
].request
)
1695 view
->line
= calloc(lines
, sizeof(*view
->line
));
1697 report("Allocation failure");
1701 view
->ops
->read(view
, "Quick reference for tig keybindings:");
1703 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
1706 if (!req_info
[i
].request
) {
1707 view
->ops
->read(view
, "");
1708 view
->ops
->read(view
, req_info
[i
].help
);
1712 key
= get_key(req_info
[i
].request
);
1713 if (!string_format(buf
, "%-25s %s", key
, req_info
[i
].help
))
1716 view
->ops
->read(view
, buf
);
1721 OPEN_DEFAULT
= 0, /* Use default view switching. */
1722 OPEN_SPLIT
= 1, /* Split current view. */
1723 OPEN_BACKGROUNDED
= 2, /* Backgrounded. */
1724 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
1728 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
1730 bool backgrounded
= !!(flags
& OPEN_BACKGROUNDED
);
1731 bool split
= !!(flags
& OPEN_SPLIT
);
1732 bool reload
= !!(flags
& OPEN_RELOAD
);
1733 struct view
*view
= VIEW(request
);
1734 int nviews
= displayed_views();
1735 struct view
*base_view
= display
[0];
1737 if (view
== prev
&& nviews
== 1 && !reload
) {
1738 report("Already in %s view", view
->name
);
1742 if (view
== VIEW(REQ_VIEW_HELP
)) {
1743 open_help_view(view
);
1745 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
1746 !begin_update(view
)) {
1747 report("Failed to load %s view", view
->name
);
1756 /* Maximize the current view. */
1757 memset(display
, 0, sizeof(display
));
1759 display
[current_view
] = view
;
1762 /* Resize the view when switching between split- and full-screen,
1763 * or when switching between two different full-screen views. */
1764 if (nviews
!= displayed_views() ||
1765 (nviews
== 1 && base_view
!= display
[0]))
1768 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
1769 /* Take the title line into account. */
1770 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
1772 /* Scroll the view that was split if the current line is
1773 * outside the new limited view. */
1774 do_scroll_view(prev
, lines
, TRUE
);
1777 if (prev
&& view
!= prev
) {
1778 if (split
&& !backgrounded
) {
1779 /* "Blur" the previous view. */
1780 update_view_title(prev
);
1783 view
->parent
= prev
;
1786 if (view
->pipe
&& view
->lines
== 0) {
1787 /* Clear the old view and let the incremental updating refill
1796 /* If the view is backgrounded the above calls to report()
1797 * won't redraw the view title. */
1799 update_view_title(view
);
1804 * User request switch noodle
1808 view_driver(struct view
*view
, enum request request
)
1815 case REQ_MOVE_PAGE_UP
:
1816 case REQ_MOVE_PAGE_DOWN
:
1817 case REQ_MOVE_FIRST_LINE
:
1818 case REQ_MOVE_LAST_LINE
:
1819 move_view(view
, request
, TRUE
);
1822 case REQ_SCROLL_LINE_DOWN
:
1823 case REQ_SCROLL_LINE_UP
:
1824 case REQ_SCROLL_PAGE_DOWN
:
1825 case REQ_SCROLL_PAGE_UP
:
1826 scroll_view(view
, request
);
1833 case REQ_VIEW_PAGER
:
1834 open_view(view
, request
, OPEN_DEFAULT
);
1839 request
= request
== REQ_NEXT ? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
1841 if (view
== VIEW(REQ_VIEW_DIFF
) &&
1842 view
->parent
== VIEW(REQ_VIEW_MAIN
)) {
1843 bool redraw
= display
[1] == view
;
1845 view
= view
->parent
;
1846 move_view(view
, request
, redraw
);
1848 update_view_title(view
);
1850 move_view(view
, request
, TRUE
);
1857 report("Nothing to enter");
1860 return view
->ops
->enter(view
, &view
->line
[view
->lineno
]);
1864 int nviews
= displayed_views();
1865 int next_view
= (current_view
+ 1) % nviews
;
1867 if (next_view
== current_view
) {
1868 report("Only one view is displayed");
1872 current_view
= next_view
;
1873 /* Blur out the title of the previous view. */
1874 update_view_title(view
);
1878 case REQ_TOGGLE_LINENO
:
1879 opt_line_number
= !opt_line_number
;
1883 case REQ_TOGGLE_REV_GRAPH
:
1884 opt_rev_graph
= !opt_rev_graph
;
1889 /* Always reload^Wrerun commands from the prompt. */
1890 open_view(view
, opt_request
, OPEN_RELOAD
);
1893 case REQ_STOP_LOADING
:
1894 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
1897 report("Stopped loading the %s view", view
->name
),
1902 case REQ_SHOW_VERSION
:
1903 report("%s (built %s)", VERSION
, __DATE__
);
1906 case REQ_SCREEN_RESIZE
:
1909 case REQ_SCREEN_REDRAW
:
1913 case REQ_SCREEN_UPDATE
:
1917 case REQ_VIEW_CLOSE
:
1918 /* XXX: Mark closed views by letting view->parent point to the
1919 * view itself. Parents to closed view should never be
1922 view
->parent
->parent
!= view
->parent
) {
1923 memset(display
, 0, sizeof(display
));
1925 display
[current_view
] = view
->parent
;
1926 view
->parent
= view
;
1936 /* An unknown key will show most commonly used commands. */
1937 report("Unknown key, press 'h' for help");
1950 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
1952 char *text
= line
->data
;
1953 enum line_type type
= line
->type
;
1954 int textlen
= strlen(text
);
1957 wmove(view
->win
, lineno
, 0);
1959 if (view
->offset
+ lineno
== view
->lineno
) {
1960 if (type
== LINE_COMMIT
) {
1961 string_copy(view
->ref
, text
+ 7);
1962 string_copy(ref_commit
, view
->ref
);
1966 wchgat(view
->win
, -1, 0, type
, NULL
);
1969 attr
= get_line_attr(type
);
1970 wattrset(view
->win
, attr
);
1972 if (opt_line_number
|| opt_tab_size
< TABSIZE
) {
1973 static char spaces
[] = " ";
1974 int col_offset
= 0, col
= 0;
1976 if (opt_line_number
) {
1977 unsigned long real_lineno
= view
->offset
+ lineno
+ 1;
1979 if (real_lineno
== 1 ||
1980 (real_lineno
% opt_num_interval
) == 0) {
1981 wprintw(view
->win
, "%.*d", view
->digits
, real_lineno
);
1984 waddnstr(view
->win
, spaces
,
1985 MIN(view
->digits
, STRING_SIZE(spaces
)));
1987 waddstr(view
->win
, ": ");
1988 col_offset
= view
->digits
+ 2;
1991 while (text
&& col_offset
+ col
< view
->width
) {
1992 int cols_max
= view
->width
- col_offset
- col
;
1996 if (*text
== '\t') {
1998 assert(sizeof(spaces
) > TABSIZE
);
2000 cols
= opt_tab_size
- (col
% opt_tab_size
);
2003 text
= strchr(text
, '\t');
2004 cols
= line ? text
- pos
: strlen(pos
);
2007 waddnstr(view
->win
, pos
, MIN(cols
, cols_max
));
2012 int col
= 0, pos
= 0;
2014 for (; pos
< textlen
&& col
< view
->width
; pos
++, col
++)
2015 if (text
[pos
] == '\t')
2016 col
+= TABSIZE
- (col
% TABSIZE
) - 1;
2018 waddnstr(view
->win
, text
, pos
);
2025 add_pager_refs(struct view
*view
, struct line
*line
)
2028 char *data
= line
->data
;
2030 int bufpos
= 0, refpos
= 0;
2031 const char *sep
= "Refs: ";
2033 assert(line
->type
== LINE_COMMIT
);
2035 refs
= get_refs(data
+ STRING_SIZE("commit "));
2040 struct ref
*ref
= refs
[refpos
];
2041 char *fmt
= ref
->tag ?
"%s[%s]" : "%s%s";
2043 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
2046 } while (refs
[refpos
++]->next
);
2048 if (!realloc_lines(view
, view
->line_size
+ 1))
2051 line
= &view
->line
[view
->lines
];
2052 line
->data
= strdup(buf
);
2056 line
->type
= LINE_PP_REFS
;
2061 pager_read(struct view
*view
, char *data
)
2063 struct line
*line
= &view
->line
[view
->lines
];
2065 line
->data
= strdup(data
);
2069 line
->type
= get_line_type(line
->data
);
2072 if (line
->type
== LINE_COMMIT
&&
2073 (view
== VIEW(REQ_VIEW_DIFF
) ||
2074 view
== VIEW(REQ_VIEW_LOG
)))
2075 add_pager_refs(view
, line
);
2081 pager_enter(struct view
*view
, struct line
*line
)
2085 if (line
->type
== LINE_COMMIT
&&
2086 (view
== VIEW(REQ_VIEW_LOG
) ||
2087 view
== VIEW(REQ_VIEW_PAGER
))) {
2088 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
2092 /* Always scroll the view even if it was split. That way
2093 * you can use Enter to scroll through the log view and
2094 * split open each commit diff. */
2095 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
2097 /* FIXME: A minor workaround. Scrolling the view will call report("")
2098 * but if we are scrolling a non-current view this won't properly
2099 * update the view title. */
2101 update_view_title(view
);
2106 static struct view_ops pager_ops
= {
2119 char id
[41]; /* SHA1 ID. */
2120 char title
[75]; /* First line of the commit message. */
2121 char author
[75]; /* Author of the commit. */
2122 struct tm time
; /* Date from the author ident. */
2123 struct ref
**refs
; /* Repository references. */
2124 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
2125 size_t graph_size
; /* The width of the graph array. */
2129 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
2131 char buf
[DATE_COLS
+ 1];
2132 struct commit
*commit
= line
->data
;
2133 enum line_type type
;
2139 if (!*commit
->author
)
2142 wmove(view
->win
, lineno
, col
);
2144 if (view
->offset
+ lineno
== view
->lineno
) {
2145 string_copy(view
->ref
, commit
->id
);
2146 string_copy(ref_commit
, view
->ref
);
2148 wattrset(view
->win
, get_line_attr(type
));
2149 wchgat(view
->win
, -1, 0, type
, NULL
);
2152 type
= LINE_MAIN_COMMIT
;
2153 wattrset(view
->win
, get_line_attr(LINE_MAIN_DATE
));
2156 timelen
= strftime(buf
, sizeof(buf
), DATE_FORMAT
, &commit
->time
);
2157 waddnstr(view
->win
, buf
, timelen
);
2158 waddstr(view
->win
, " ");
2161 wmove(view
->win
, lineno
, col
);
2162 if (type
!= LINE_CURSOR
)
2163 wattrset(view
->win
, get_line_attr(LINE_MAIN_AUTHOR
));
2166 authorlen
= utf8_length(commit
->author
, AUTHOR_COLS
- 2, &col
, &trimmed
);
2168 authorlen
= strlen(commit
->author
);
2169 if (authorlen
> AUTHOR_COLS
- 2) {
2170 authorlen
= AUTHOR_COLS
- 2;
2176 waddnstr(view
->win
, commit
->author
, authorlen
);
2177 if (type
!= LINE_CURSOR
)
2178 wattrset(view
->win
, get_line_attr(LINE_MAIN_DELIM
));
2179 waddch(view
->win
, '~');
2181 waddstr(view
->win
, commit
->author
);
2185 if (type
!= LINE_CURSOR
)
2186 wattrset(view
->win
, A_NORMAL
);
2188 if (opt_rev_graph
&& commit
->graph_size
) {
2191 wmove(view
->win
, lineno
, col
);
2192 /* Using waddch() instead of waddnstr() ensures that
2193 * they'll be rendered correctly for the cursor line. */
2194 for (i
= 0; i
< commit
->graph_size
; i
++)
2195 waddch(view
->win
, commit
->graph
[i
]);
2197 col
+= commit
->graph_size
+ 1;
2200 wmove(view
->win
, lineno
, col
);
2206 if (type
== LINE_CURSOR
)
2208 else if (commit
->refs
[i
]->tag
)
2209 wattrset(view
->win
, get_line_attr(LINE_MAIN_TAG
));
2211 wattrset(view
->win
, get_line_attr(LINE_MAIN_REF
));
2212 waddstr(view
->win
, "[");
2213 waddstr(view
->win
, commit
->refs
[i
]->name
);
2214 waddstr(view
->win
, "]");
2215 if (type
!= LINE_CURSOR
)
2216 wattrset(view
->win
, A_NORMAL
);
2217 waddstr(view
->win
, " ");
2218 col
+= strlen(commit
->refs
[i
]->name
) + STRING_SIZE("[] ");
2219 } while (commit
->refs
[i
++]->next
);
2222 if (type
!= LINE_CURSOR
)
2223 wattrset(view
->win
, get_line_attr(type
));
2226 int titlelen
= strlen(commit
->title
);
2228 if (col
+ titlelen
> view
->width
)
2229 titlelen
= view
->width
- col
;
2231 waddnstr(view
->win
, commit
->title
, titlelen
);
2237 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2239 main_read(struct view
*view
, char *line
)
2241 enum line_type type
= get_line_type(line
);
2242 struct commit
*commit
= view
->lines
2243 ? view
->line
[view
->lines
- 1].data
: NULL
;
2247 commit
= calloc(1, sizeof(struct commit
));
2251 line
+= STRING_SIZE("commit ");
2253 view
->line
[view
->lines
++].data
= commit
;
2254 string_copy(commit
->id
, line
);
2255 commit
->refs
= get_refs(commit
->id
);
2256 commit
->graph
[commit
->graph_size
++] = ACS_LTEE
;
2261 char *ident
= line
+ STRING_SIZE("author ");
2262 char *end
= strchr(ident
, '<');
2268 for (; end
> ident
&& isspace(end
[-1]); end
--) ;
2272 string_copy(commit
->author
, ident
);
2274 /* Parse epoch and timezone */
2276 char *secs
= strchr(end
+ 1, '>');
2280 if (!secs
|| secs
[1] != ' ')
2284 time
= (time_t) atol(secs
);
2285 zone
= strchr(secs
, ' ');
2286 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700")) {
2290 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
2291 tz
+= ('0' - zone
[2]) * 60 * 60;
2292 tz
+= ('0' - zone
[3]) * 60;
2293 tz
+= ('0' - zone
[4]) * 60;
2300 gmtime_r(&time
, &commit
->time
);
2308 /* Fill in the commit title if it has not already been set. */
2309 if (commit
->title
[0])
2312 /* Require titles to start with a non-space character at the
2313 * offset used by git log. */
2314 /* FIXME: More gracefull handling of titles; append "..." to
2315 * shortened titles, etc. */
2316 if (strncmp(line
, " ", 4) ||
2320 string_copy(commit
->title
, line
+ 4);
2327 main_enter(struct view
*view
, struct line
*line
)
2329 enum open_flags flags
= display
[0] == view ? OPEN_SPLIT
: OPEN_DEFAULT
;
2331 open_view(view
, REQ_VIEW_DIFF
, flags
);
2335 static struct view_ops main_ops
= {
2344 * Unicode / UTF-8 handling
2346 * NOTE: Much of the following code for dealing with unicode is derived from
2347 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2348 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2351 /* I've (over)annotated a lot of code snippets because I am not entirely
2352 * confident that the approach taken by this small UTF-8 interface is correct.
2356 unicode_width(unsigned long c
)
2359 (c
<= 0x115f /* Hangul Jamo */
2362 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
2364 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
2365 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
2366 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
2367 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
2368 || (c
>= 0xffe0 && c
<= 0xffe6)
2369 || (c
>= 0x20000 && c
<= 0x2fffd)
2370 || (c
>= 0x30000 && c
<= 0x3fffd)))
2376 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2377 * Illegal bytes are set one. */
2378 static const unsigned char utf8_bytes
[256] = {
2379 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2380 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2381 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2382 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2383 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2384 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2385 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
2386 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
2389 /* Decode UTF-8 multi-byte representation into a unicode character. */
2390 static inline unsigned long
2391 utf8_to_unicode(const char *string
, size_t length
)
2393 unsigned long unicode
;
2397 unicode
= string
[0];
2400 unicode
= (string
[0] & 0x1f) << 6;
2401 unicode
+= (string
[1] & 0x3f);
2404 unicode
= (string
[0] & 0x0f) << 12;
2405 unicode
+= ((string
[1] & 0x3f) << 6);
2406 unicode
+= (string
[2] & 0x3f);
2409 unicode
= (string
[0] & 0x0f) << 18;
2410 unicode
+= ((string
[1] & 0x3f) << 12);
2411 unicode
+= ((string
[2] & 0x3f) << 6);
2412 unicode
+= (string
[3] & 0x3f);
2415 unicode
= (string
[0] & 0x0f) << 24;
2416 unicode
+= ((string
[1] & 0x3f) << 18);
2417 unicode
+= ((string
[2] & 0x3f) << 12);
2418 unicode
+= ((string
[3] & 0x3f) << 6);
2419 unicode
+= (string
[4] & 0x3f);
2422 unicode
= (string
[0] & 0x01) << 30;
2423 unicode
+= ((string
[1] & 0x3f) << 24);
2424 unicode
+= ((string
[2] & 0x3f) << 18);
2425 unicode
+= ((string
[3] & 0x3f) << 12);
2426 unicode
+= ((string
[4] & 0x3f) << 6);
2427 unicode
+= (string
[5] & 0x3f);
2430 die("Invalid unicode length");
2433 /* Invalid characters could return the special 0xfffd value but NUL
2434 * should be just as good. */
2435 return unicode
> 0xffff ?
0 : unicode
;
2438 /* Calculates how much of string can be shown within the given maximum width
2439 * and sets trimmed parameter to non-zero value if all of string could not be
2442 * Additionally, adds to coloffset how many many columns to move to align with
2443 * the expected position. Takes into account how multi-byte and double-width
2444 * characters will effect the cursor position.
2446 * Returns the number of bytes to output from string to satisfy max_width. */
2448 utf8_length(const char *string
, size_t max_width
, int *coloffset
, int *trimmed
)
2450 const char *start
= string
;
2451 const char *end
= strchr(string
, '\0');
2457 while (string
< end
) {
2458 int c
= *(unsigned char *) string
;
2459 unsigned char bytes
= utf8_bytes
[c
];
2461 unsigned long unicode
;
2463 if (string
+ bytes
> end
)
2466 /* Change representation to figure out whether
2467 * it is a single- or double-width character. */
2469 unicode
= utf8_to_unicode(string
, bytes
);
2470 /* FIXME: Graceful handling of invalid unicode character. */
2474 ucwidth
= unicode_width(unicode
);
2476 if (width
> max_width
) {
2481 /* The column offset collects the differences between the
2482 * number of bytes encoding a character and the number of
2483 * columns will be used for rendering said character.
2485 * So if some character A is encoded in 2 bytes, but will be
2486 * represented on the screen using only 1 byte this will and up
2487 * adding 1 to the multi-byte column offset.
2489 * Assumes that no double-width character can be encoding in
2490 * less than two bytes. */
2491 if (bytes
> ucwidth
)
2492 mbwidth
+= bytes
- ucwidth
;
2497 *coloffset
+= mbwidth
;
2499 return string
- start
;
2507 /* Whether or not the curses interface has been initialized. */
2508 static bool cursed
= FALSE
;
2510 /* The status window is used for polling keystrokes. */
2511 static WINDOW
*status_win
;
2513 /* Update status and title window. */
2515 report(const char *msg
, ...)
2517 static bool empty
= TRUE
;
2518 struct view
*view
= display
[current_view
];
2520 if (!empty
|| *msg
) {
2523 va_start(args
, msg
);
2526 wmove(status_win
, 0, 0);
2528 vwprintw(status_win
, msg
, args
);
2533 wrefresh(status_win
);
2538 update_view_title(view
);
2539 update_display_cursor();
2542 /* Controls when nodelay should be in effect when polling user input. */
2544 set_nonblocking_input(bool loading
)
2546 static unsigned int loading_views
;
2548 if ((loading
== FALSE
&& loading_views
-- == 1) ||
2549 (loading
== TRUE
&& loading_views
++ == 0))
2550 nodelay(status_win
, loading
);
2558 /* Initialize the curses library */
2559 if (isatty(STDIN_FILENO
)) {
2560 cursed
= !!initscr();
2562 /* Leave stdin and stdout alone when acting as a pager. */
2563 FILE *io
= fopen("/dev/tty", "r+");
2565 cursed
= !!newterm(NULL
, io
, io
);
2569 die("Failed to initialize curses");
2571 nonl(); /* Tell curses not to do NL->CR/NL on output */
2572 cbreak(); /* Take input chars one at a time, no wait for \n */
2573 noecho(); /* Don't echo input */
2574 leaveok(stdscr
, TRUE
);
2579 getmaxyx(stdscr
, y
, x
);
2580 status_win
= newwin(1, 0, y
- 1, 0);
2582 die("Failed to create status window");
2584 /* Enable keyboard mapping */
2585 keypad(status_win
, TRUE
);
2586 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
2591 * Repository references
2594 static struct ref
*refs
;
2595 static size_t refs_size
;
2597 /* Id <-> ref store */
2598 static struct ref
***id_refs
;
2599 static size_t id_refs_size
;
2601 static struct ref
**
2604 struct ref
***tmp_id_refs
;
2605 struct ref
**ref_list
= NULL
;
2606 size_t ref_list_size
= 0;
2609 for (i
= 0; i
< id_refs_size
; i
++)
2610 if (!strcmp(id
, id_refs
[i
][0]->id
))
2613 tmp_id_refs
= realloc(id_refs
, (id_refs_size
+ 1) * sizeof(*id_refs
));
2617 id_refs
= tmp_id_refs
;
2619 for (i
= 0; i
< refs_size
; i
++) {
2622 if (strcmp(id
, refs
[i
].id
))
2625 tmp
= realloc(ref_list
, (ref_list_size
+ 1) * sizeof(*ref_list
));
2633 if (ref_list_size
> 0)
2634 ref_list
[ref_list_size
- 1]->next
= 1;
2635 ref_list
[ref_list_size
] = &refs
[i
];
2637 /* XXX: The properties of the commit chains ensures that we can
2638 * safely modify the shared ref. The repo references will
2639 * always be similar for the same id. */
2640 ref_list
[ref_list_size
]->next
= 0;
2645 id_refs
[id_refs_size
++] = ref_list
;
2651 read_ref(char *id
, int idlen
, char *name
, int namelen
)
2656 if (!strncmp(name
, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2657 /* Commits referenced by tags has "^{}" appended. */
2658 if (name
[namelen
- 1] != '}')
2661 while (namelen
> 0 && name
[namelen
] != '^')
2665 namelen
-= STRING_SIZE("refs/tags/");
2666 name
+= STRING_SIZE("refs/tags/");
2668 } else if (!strncmp(name
, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2669 namelen
-= STRING_SIZE("refs/heads/");
2670 name
+= STRING_SIZE("refs/heads/");
2672 } else if (!strcmp(name
, "HEAD")) {
2676 refs
= realloc(refs
, sizeof(*refs
) * (refs_size
+ 1));
2680 ref
= &refs
[refs_size
++];
2681 ref
->name
= malloc(namelen
+ 1);
2685 strncpy(ref
->name
, name
, namelen
);
2686 ref
->name
[namelen
] = 0;
2688 string_copy(ref
->id
, id
);
2696 const char *cmd_env
= getenv("TIG_LS_REMOTE");
2697 const char *cmd
= cmd_env
&& *cmd_env ? cmd_env
: TIG_LS_REMOTE
;
2699 return read_properties(popen(cmd
, "r"), "\t", read_ref
);
2703 read_repo_config_option(char *name
, int namelen
, char *value
, int valuelen
)
2705 if (!strcmp(name
, "i18n.commitencoding"))
2706 string_copy(opt_encoding
, value
);
2712 load_repo_config(void)
2714 return read_properties(popen("git repo-config --list", "r"),
2715 "=", read_repo_config_option
);
2719 read_properties(FILE *pipe
, const char *separators
,
2720 int (*read_property
)(char *, int, char *, int))
2722 char buffer
[BUFSIZ
];
2729 while (state
== OK
&& (name
= fgets(buffer
, sizeof(buffer
), pipe
))) {
2734 name
= chomp_string(name
);
2735 namelen
= strcspn(name
, separators
);
2737 if (name
[namelen
]) {
2739 value
= chomp_string(name
+ namelen
+ 1);
2740 valuelen
= strlen(value
);
2747 state
= read_property(name
, namelen
, value
, valuelen
);
2750 if (state
!= ERR
&& ferror(pipe
))
2763 static void __NORETURN
2766 /* XXX: Restore tty modes and let the OS cleanup the rest! */
2772 static void __NORETURN
2773 die(const char *err
, ...)
2779 va_start(args
, err
);
2780 fputs("tig: ", stderr
);
2781 vfprintf(stderr
, err
, args
);
2782 fputs("\n", stderr
);
2789 main(int argc
, char *argv
[])
2792 enum request request
;
2795 signal(SIGINT
, quit
);
2797 if (load_options() == ERR
)
2798 die("Failed to load user config.");
2800 /* Load the repo config file so options can be overwritten from
2801 * the command line. */
2802 if (load_repo_config() == ERR
)
2803 die("Failed to load repo config.");
2805 if (!parse_options(argc
, argv
))
2808 if (load_refs() == ERR
)
2809 die("Failed to load refs.");
2811 /* Require a git repository unless when running in pager mode. */
2812 if (refs_size
== 0 && opt_request
!= REQ_VIEW_PAGER
)
2813 die("Not a git repository");
2815 for (i
= 0; i
< ARRAY_SIZE(views
) && (view
= &views
[i
]); i
++)
2816 view
->cmd_env
= getenv(view
->cmd_env
);
2818 request
= opt_request
;
2822 while (view_driver(display
[current_view
], request
)) {
2826 foreach_view (view
, i
)
2829 /* Refresh, accept single keystroke of input */
2830 key
= wgetch(status_win
);
2832 request
= get_keybinding(display
[current_view
]->keymap
, key
);
2834 /* Some low-level request handling. This keeps access to
2835 * status_win restricted. */
2839 /* Temporarily switch to line-oriented and echoed
2844 if (wgetnstr(status_win
, opt_cmd
+ 4, sizeof(opt_cmd
) - 4) == OK
) {
2845 memcpy(opt_cmd
, "git ", 4);
2846 opt_request
= REQ_VIEW_PAGER
;
2848 report("Prompt interrupted by loading view, "
2849 "press 'z' to stop loading views");
2850 request
= REQ_SCREEN_UPDATE
;
2857 case REQ_SCREEN_RESIZE
:
2861 getmaxyx(stdscr
, height
, width
);
2863 /* Resize the status view and let the view driver take
2864 * care of resizing the displayed views. */
2865 wresize(status_win
, 1, width
);
2866 mvwin(status_win
, height
- 1, 0);
2867 wrefresh(status_win
);