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
},
774 { "Home", KEY_HOME
},
776 { "PageUp", KEY_PPAGE
},
777 { "PageDown", KEY_NPAGE
},
787 { "F10", KEY_F(10) },
788 { "F11", KEY_F(11) },
789 { "F12", KEY_F(12) },
793 get_key_value(const char *name
)
797 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
798 if (!strcasecmp(key_table
[i
].name
, name
))
799 return key_table
[i
].value
;
801 if (strlen(name
) == 1 && isprint(*name
))
808 get_key(enum request request
)
810 static char buf
[BUFSIZ
];
811 static char key_char
[] = "'X'";
818 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
819 struct keybinding
*keybinding
= &default_keybindings
[i
];
823 if (keybinding
->request
!= request
)
826 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
827 if (key_table
[key
].value
== keybinding
->alias
)
828 seq
= key_table
[key
].name
;
831 keybinding
->alias
< 127 &&
832 isprint(keybinding
->alias
)) {
833 key_char
[1] = (char) keybinding
->alias
;
840 if (!string_format_from(buf
, &pos
, "%s%s", sep
, seq
))
841 return "Too many keybindings!";
850 * User config file handling.
853 static struct int_map color_map
[] = {
854 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
866 #define set_color(color, name) \
867 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
869 static struct int_map attr_map
[] = {
870 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
880 #define set_attribute(attr, name) \
881 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
883 static int config_lineno
;
884 static bool config_errors
;
885 static char *config_msg
;
887 /* Wants: object fgcolor bgcolor [attr] */
889 option_color_command(int argc
, char *argv
[])
891 struct line_info
*info
;
893 if (argc
!= 3 && argc
!= 4) {
894 config_msg
= "Wrong number of arguments given to color command";
898 info
= get_line_info(argv
[0], strlen(argv
[0]));
900 config_msg
= "Unknown color name";
904 if (set_color(&info
->fg
, argv
[1]) == ERR
||
905 set_color(&info
->bg
, argv
[2]) == ERR
) {
906 config_msg
= "Unknown color";
910 if (argc
== 4 && set_attribute(&info
->attr
, argv
[3]) == ERR
) {
911 config_msg
= "Unknown attribute";
918 /* Wants: name = value */
920 option_set_command(int argc
, char *argv
[])
923 config_msg
= "Wrong number of arguments given to set command";
927 if (strcmp(argv
[1], "=")) {
928 config_msg
= "No value assigned";
932 if (!strcmp(argv
[0], "show-rev-graph")) {
933 opt_rev_graph
= (!strcmp(argv
[2], "1") ||
934 !strcmp(argv
[2], "true") ||
935 !strcmp(argv
[2], "yes"));
939 if (!strcmp(argv
[0], "line-number-interval")) {
940 opt_num_interval
= atoi(argv
[2]);
944 if (!strcmp(argv
[0], "tab-size")) {
945 opt_tab_size
= atoi(argv
[2]);
949 if (!strcmp(argv
[0], "commit-encoding")) {
951 int delimiter
= *arg
;
957 for (arg
++, i
= 0; arg
[i
]; i
++)
958 if (arg
[i
] == delimiter
) {
963 string_copy(opt_encoding
, arg
);
968 config_msg
= "Unknown variable name";
972 /* Wants: mode request key */
974 option_bind_command(int argc
, char *argv
[])
976 enum request request
;
981 config_msg
= "Wrong number of arguments given to bind command";
985 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
986 config_msg
= "Unknown key map";
990 key
= get_key_value(argv
[1]);
992 config_msg
= "Unknown key";
996 request
= get_request(argv
[2]);
997 if (request
== REQ_UNKNOWN
) {
998 config_msg
= "Unknown request name";
1002 add_keybinding(keymap
, request
, key
);
1008 set_option(char *opt
, char *value
)
1015 while (argc
< ARRAY_SIZE(argv
) && (valuelen
= strcspn(value
, " \t"))) {
1016 argv
[argc
++] = value
;
1023 while (isspace(*value
))
1027 if (!strcmp(opt
, "color"))
1028 return option_color_command(argc
, argv
);
1030 if (!strcmp(opt
, "set"))
1031 return option_set_command(argc
, argv
);
1033 if (!strcmp(opt
, "bind"))
1034 return option_bind_command(argc
, argv
);
1036 config_msg
= "Unknown option command";
1041 read_option(char *opt
, int optlen
, char *value
, int valuelen
)
1046 config_msg
= "Internal error";
1048 /* Check for comment markers, since read_properties() will
1049 * only ensure opt and value are split at first " \t". */
1050 optlen
= strcspn(opt
, "#");
1054 if (opt
[optlen
] != 0) {
1055 config_msg
= "No option value";
1059 /* Look for comment endings in the value. */
1060 int len
= strcspn(value
, "#");
1062 if (len
< valuelen
) {
1064 value
[valuelen
] = 0;
1067 status
= set_option(opt
, value
);
1070 if (status
== ERR
) {
1071 fprintf(stderr
, "Error on line %d, near '%.*s': %s\n",
1072 config_lineno
, optlen
, opt
, config_msg
);
1073 config_errors
= TRUE
;
1076 /* Always keep going if errors are encountered. */
1083 char *home
= getenv("HOME");
1088 config_errors
= FALSE
;
1090 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1093 /* It's ok that the file doesn't exist. */
1094 file
= fopen(buf
, "r");
1098 if (read_properties(file
, " \t", read_option
) == ERR
||
1099 config_errors
== TRUE
)
1100 fprintf(stderr
, "Errors while loading %s.\n", buf
);
1113 /* The display array of active views and the index of the current view. */
1114 static struct view
*display
[2];
1115 static unsigned int current_view
;
1117 #define foreach_view(view, i) \
1118 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1120 #define displayed_views() (display[1] != NULL ? 2 : 1)
1122 /* Current head and commit ID */
1123 static char ref_commit
[SIZEOF_REF
] = "HEAD";
1124 static char ref_head
[SIZEOF_REF
] = "HEAD";
1127 const char *name
; /* View name */
1128 const char *cmd_fmt
; /* Default command line format */
1129 const char *cmd_env
; /* Command line set via environment */
1130 const char *id
; /* Points to either of ref_{head,commit} */
1132 struct view_ops
*ops
; /* View operations */
1134 enum keymap keymap
; /* What keymap does this view have */
1136 char cmd
[SIZEOF_CMD
]; /* Command buffer */
1137 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
1138 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
1140 int height
, width
; /* The width and height of the main window */
1141 WINDOW
*win
; /* The main window */
1142 WINDOW
*title
; /* The title window living below the main window */
1145 unsigned long offset
; /* Offset of the window top */
1146 unsigned long lineno
; /* Current line number */
1148 /* If non-NULL, points to the view that opened this view. If this view
1149 * is closed tig will switch back to the parent view. */
1150 struct view
*parent
;
1153 unsigned long lines
; /* Total number of lines */
1154 struct line
*line
; /* Line index */
1155 unsigned long line_size
;/* Total number of allocated lines */
1156 unsigned int digits
; /* Number of digits in the lines member. */
1164 /* What type of content being displayed. Used in the title bar. */
1166 /* Draw one line; @lineno must be < view->height. */
1167 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
1168 /* Read one line; updates view->line. */
1169 bool (*read
)(struct view
*view
, char *data
);
1170 /* Depending on view, change display based on current line. */
1171 bool (*enter
)(struct view
*view
, struct line
*line
);
1174 static struct view_ops pager_ops
;
1175 static struct view_ops main_ops
;
1177 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1178 { name, cmd, #env, ref, ops, map}
1180 #define VIEW_(id, name, ops, ref) \
1181 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1184 static struct view views
[] = {
1185 VIEW_(MAIN
, "main", &main_ops
, ref_head
),
1186 VIEW_(DIFF
, "diff", &pager_ops
, ref_commit
),
1187 VIEW_(LOG
, "log", &pager_ops
, ref_head
),
1188 VIEW_(HELP
, "help", &pager_ops
, "static"),
1189 VIEW_(PAGER
, "pager", &pager_ops
, "static"),
1192 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1196 draw_view_line(struct view
*view
, unsigned int lineno
)
1198 if (view
->offset
+ lineno
>= view
->lines
)
1201 return view
->ops
->draw(view
, &view
->line
[view
->offset
+ lineno
], lineno
);
1205 redraw_view_from(struct view
*view
, int lineno
)
1207 assert(0 <= lineno
&& lineno
< view
->height
);
1209 for (; lineno
< view
->height
; lineno
++) {
1210 if (!draw_view_line(view
, lineno
))
1214 redrawwin(view
->win
);
1215 wrefresh(view
->win
);
1219 redraw_view(struct view
*view
)
1222 redraw_view_from(view
, 0);
1227 update_view_title(struct view
*view
)
1229 if (view
== display
[current_view
])
1230 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
1232 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
1234 werase(view
->title
);
1235 wmove(view
->title
, 0, 0);
1238 wprintw(view
->title
, "[%s] %s", view
->name
, view
->ref
);
1240 wprintw(view
->title
, "[%s]", view
->name
);
1242 if (view
->lines
|| view
->pipe
) {
1243 unsigned int view_lines
= view
->offset
+ view
->height
;
1244 unsigned int lines
= view
->lines
1245 ?
MIN(view_lines
, view
->lines
) * 100 / view
->lines
1248 wprintw(view
->title
, " - %s %d of %d (%d%%)",
1256 time_t secs
= time(NULL
) - view
->start_time
;
1258 /* Three git seconds are a long time ... */
1260 wprintw(view
->title
, " %lds", secs
);
1263 wmove(view
->title
, 0, view
->width
- 1);
1264 wrefresh(view
->title
);
1268 resize_display(void)
1271 struct view
*base
= display
[0];
1272 struct view
*view
= display
[1] ? display
[1] : display
[0];
1274 /* Setup window dimensions */
1276 getmaxyx(stdscr
, base
->height
, base
->width
);
1278 /* Make room for the status window. */
1282 /* Horizontal split. */
1283 view
->width
= base
->width
;
1284 view
->height
= SCALE_SPLIT_VIEW(base
->height
);
1285 base
->height
-= view
->height
;
1287 /* Make room for the title bar. */
1291 /* Make room for the title bar. */
1296 foreach_view (view
, i
) {
1298 view
->win
= newwin(view
->height
, 0, offset
, 0);
1300 die("Failed to create %s view", view
->name
);
1302 scrollok(view
->win
, TRUE
);
1304 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
1306 die("Failed to create title window");
1309 wresize(view
->win
, view
->height
, view
->width
);
1310 mvwin(view
->win
, offset
, 0);
1311 mvwin(view
->title
, offset
+ view
->height
, 0);
1314 offset
+= view
->height
+ 1;
1319 redraw_display(void)
1324 foreach_view (view
, i
) {
1326 update_view_title(view
);
1331 update_display_cursor(void)
1333 struct view
*view
= display
[current_view
];
1335 /* Move the cursor to the right-most column of the cursor line.
1337 * XXX: This could turn out to be a bit expensive, but it ensures that
1338 * the cursor does not jump around. */
1340 wmove(view
->win
, view
->lineno
- view
->offset
, view
->width
- 1);
1341 wrefresh(view
->win
);
1349 /* Scrolling backend */
1351 do_scroll_view(struct view
*view
, int lines
, bool redraw
)
1353 /* The rendering expects the new offset. */
1354 view
->offset
+= lines
;
1356 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
1359 /* Redraw the whole screen if scrolling is pointless. */
1360 if (view
->height
< ABS(lines
)) {
1364 int line
= lines
> 0 ? view
->height
- lines
: 0;
1365 int end
= line
+ ABS(lines
);
1367 wscrl(view
->win
, lines
);
1369 for (; line
< end
; line
++) {
1370 if (!draw_view_line(view
, line
))
1375 /* Move current line into the view. */
1376 if (view
->lineno
< view
->offset
) {
1377 view
->lineno
= view
->offset
;
1378 draw_view_line(view
, 0);
1380 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
1381 if (view
->lineno
== view
->offset
+ view
->height
) {
1382 /* Clear the hidden line so it doesn't show if the view
1383 * is scrolled up. */
1384 wmove(view
->win
, view
->height
, 0);
1385 wclrtoeol(view
->win
);
1387 view
->lineno
= view
->offset
+ view
->height
- 1;
1388 draw_view_line(view
, view
->lineno
- view
->offset
);
1391 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
1396 redrawwin(view
->win
);
1397 wrefresh(view
->win
);
1401 /* Scroll frontend */
1403 scroll_view(struct view
*view
, enum request request
)
1408 case REQ_SCROLL_PAGE_DOWN
:
1409 lines
= view
->height
;
1410 case REQ_SCROLL_LINE_DOWN
:
1411 if (view
->offset
+ lines
> view
->lines
)
1412 lines
= view
->lines
- view
->offset
;
1414 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
1415 report("Cannot scroll beyond the last line");
1420 case REQ_SCROLL_PAGE_UP
:
1421 lines
= view
->height
;
1422 case REQ_SCROLL_LINE_UP
:
1423 if (lines
> view
->offset
)
1424 lines
= view
->offset
;
1427 report("Cannot scroll beyond the first line");
1435 die("request %d not handled in switch", request
);
1438 do_scroll_view(view
, lines
, TRUE
);
1443 move_view(struct view
*view
, enum request request
, bool redraw
)
1448 case REQ_MOVE_FIRST_LINE
:
1449 steps
= -view
->lineno
;
1452 case REQ_MOVE_LAST_LINE
:
1453 steps
= view
->lines
- view
->lineno
- 1;
1456 case REQ_MOVE_PAGE_UP
:
1457 steps
= view
->height
> view
->lineno
1458 ?
-view
->lineno
: -view
->height
;
1461 case REQ_MOVE_PAGE_DOWN
:
1462 steps
= view
->lineno
+ view
->height
>= view
->lines
1463 ? view
->lines
- view
->lineno
- 1 : view
->height
;
1475 die("request %d not handled in switch", request
);
1478 if (steps
<= 0 && view
->lineno
== 0) {
1479 report("Cannot move beyond the first line");
1482 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
1483 report("Cannot move beyond the last line");
1487 /* Move the current line */
1488 view
->lineno
+= steps
;
1489 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
1491 /* Repaint the old "current" line if we be scrolling */
1492 if (ABS(steps
) < view
->height
) {
1493 int prev_lineno
= view
->lineno
- steps
- view
->offset
;
1495 wmove(view
->win
, prev_lineno
, 0);
1496 wclrtoeol(view
->win
);
1497 draw_view_line(view
, prev_lineno
);
1500 /* Check whether the view needs to be scrolled */
1501 if (view
->lineno
< view
->offset
||
1502 view
->lineno
>= view
->offset
+ view
->height
) {
1503 if (steps
< 0 && -steps
> view
->offset
) {
1504 steps
= -view
->offset
;
1506 } else if (steps
> 0) {
1507 if (view
->lineno
== view
->lines
- 1 &&
1508 view
->lines
> view
->height
) {
1509 steps
= view
->lines
- view
->offset
- 1;
1510 if (steps
>= view
->height
)
1511 steps
-= view
->height
- 1;
1515 do_scroll_view(view
, steps
, redraw
);
1519 /* Draw the current line */
1520 draw_view_line(view
, view
->lineno
- view
->offset
);
1525 redrawwin(view
->win
);
1526 wrefresh(view
->win
);
1532 * Incremental updating
1536 end_update(struct view
*view
)
1540 set_nonblocking_input(FALSE
);
1541 if (view
->pipe
== stdin
)
1549 begin_update(struct view
*view
)
1551 const char *id
= view
->id
;
1557 string_copy(view
->cmd
, opt_cmd
);
1559 /* When running random commands, the view ref could have become
1560 * invalid so clear it. */
1563 const char *format
= view
->cmd_env ? view
->cmd_env
: view
->cmd_fmt
;
1565 if (!string_format(view
->cmd
, format
, id
, id
, id
, id
, id
))
1569 /* Special case for the pager view. */
1571 view
->pipe
= opt_pipe
;
1574 view
->pipe
= popen(view
->cmd
, "r");
1580 set_nonblocking_input(TRUE
);
1585 string_copy(view
->vid
, id
);
1590 for (i
= 0; i
< view
->lines
; i
++)
1591 if (view
->line
[i
].data
)
1592 free(view
->line
[i
].data
);
1598 view
->start_time
= time(NULL
);
1603 static struct line
*
1604 realloc_lines(struct view
*view
, size_t line_size
)
1606 struct line
*tmp
= realloc(view
->line
, sizeof(*view
->line
) * line_size
);
1612 view
->line_size
= line_size
;
1617 update_view(struct view
*view
)
1619 char buffer
[BUFSIZ
];
1621 /* The number of lines to read. If too low it will cause too much
1622 * redrawing (and possible flickering), if too high responsiveness
1624 unsigned long lines
= view
->height
;
1625 int redraw_from
= -1;
1630 /* Only redraw if lines are visible. */
1631 if (view
->offset
+ view
->height
>= view
->lines
)
1632 redraw_from
= view
->lines
- view
->offset
;
1634 if (!realloc_lines(view
, view
->lines
+ lines
))
1637 while ((line
= fgets(buffer
, sizeof(buffer
), view
->pipe
))) {
1638 int linelen
= strlen(line
);
1641 line
[linelen
- 1] = 0;
1643 if (!view
->ops
->read(view
, line
))
1653 lines
= view
->lines
;
1654 for (digits
= 0; lines
; digits
++)
1657 /* Keep the displayed view in sync with line number scaling. */
1658 if (digits
!= view
->digits
) {
1659 view
->digits
= digits
;
1664 if (redraw_from
>= 0) {
1665 /* If this is an incremental update, redraw the previous line
1666 * since for commits some members could have changed when
1667 * loading the main view. */
1668 if (redraw_from
> 0)
1671 /* Incrementally draw avoids flickering. */
1672 redraw_view_from(view
, redraw_from
);
1675 /* Update the title _after_ the redraw so that if the redraw picks up a
1676 * commit reference in view->ref it'll be available here. */
1677 update_view_title(view
);
1679 if (ferror(view
->pipe
)) {
1680 report("Failed to read: %s", strerror(errno
));
1683 } else if (feof(view
->pipe
)) {
1691 report("Allocation failure");
1703 static void open_help_view(struct view
*view
)
1706 int lines
= ARRAY_SIZE(req_info
) + 2;
1709 if (view
->lines
> 0)
1712 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1713 if (!req_info
[i
].request
)
1716 view
->line
= calloc(lines
, sizeof(*view
->line
));
1718 report("Allocation failure");
1722 view
->ops
->read(view
, "Quick reference for tig keybindings:");
1724 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
1727 if (!req_info
[i
].request
) {
1728 view
->ops
->read(view
, "");
1729 view
->ops
->read(view
, req_info
[i
].help
);
1733 key
= get_key(req_info
[i
].request
);
1734 if (!string_format(buf
, "%-25s %s", key
, req_info
[i
].help
))
1737 view
->ops
->read(view
, buf
);
1742 OPEN_DEFAULT
= 0, /* Use default view switching. */
1743 OPEN_SPLIT
= 1, /* Split current view. */
1744 OPEN_BACKGROUNDED
= 2, /* Backgrounded. */
1745 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
1749 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
1751 bool backgrounded
= !!(flags
& OPEN_BACKGROUNDED
);
1752 bool split
= !!(flags
& OPEN_SPLIT
);
1753 bool reload
= !!(flags
& OPEN_RELOAD
);
1754 struct view
*view
= VIEW(request
);
1755 int nviews
= displayed_views();
1756 struct view
*base_view
= display
[0];
1758 if (view
== prev
&& nviews
== 1 && !reload
) {
1759 report("Already in %s view", view
->name
);
1763 if (view
== VIEW(REQ_VIEW_HELP
)) {
1764 open_help_view(view
);
1766 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
1767 !begin_update(view
)) {
1768 report("Failed to load %s view", view
->name
);
1777 /* Maximize the current view. */
1778 memset(display
, 0, sizeof(display
));
1780 display
[current_view
] = view
;
1783 /* Resize the view when switching between split- and full-screen,
1784 * or when switching between two different full-screen views. */
1785 if (nviews
!= displayed_views() ||
1786 (nviews
== 1 && base_view
!= display
[0]))
1789 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
1790 /* Take the title line into account. */
1791 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
1793 /* Scroll the view that was split if the current line is
1794 * outside the new limited view. */
1795 do_scroll_view(prev
, lines
, TRUE
);
1798 if (prev
&& view
!= prev
) {
1799 if (split
&& !backgrounded
) {
1800 /* "Blur" the previous view. */
1801 update_view_title(prev
);
1804 view
->parent
= prev
;
1807 if (view
->pipe
&& view
->lines
== 0) {
1808 /* Clear the old view and let the incremental updating refill
1817 /* If the view is backgrounded the above calls to report()
1818 * won't redraw the view title. */
1820 update_view_title(view
);
1825 * User request switch noodle
1829 view_driver(struct view
*view
, enum request request
)
1836 case REQ_MOVE_PAGE_UP
:
1837 case REQ_MOVE_PAGE_DOWN
:
1838 case REQ_MOVE_FIRST_LINE
:
1839 case REQ_MOVE_LAST_LINE
:
1840 move_view(view
, request
, TRUE
);
1843 case REQ_SCROLL_LINE_DOWN
:
1844 case REQ_SCROLL_LINE_UP
:
1845 case REQ_SCROLL_PAGE_DOWN
:
1846 case REQ_SCROLL_PAGE_UP
:
1847 scroll_view(view
, request
);
1854 case REQ_VIEW_PAGER
:
1855 open_view(view
, request
, OPEN_DEFAULT
);
1860 request
= request
== REQ_NEXT ? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
1862 if (view
== VIEW(REQ_VIEW_DIFF
) &&
1863 view
->parent
== VIEW(REQ_VIEW_MAIN
)) {
1864 bool redraw
= display
[1] == view
;
1866 view
= view
->parent
;
1867 move_view(view
, request
, redraw
);
1869 update_view_title(view
);
1871 move_view(view
, request
, TRUE
);
1878 report("Nothing to enter");
1881 return view
->ops
->enter(view
, &view
->line
[view
->lineno
]);
1885 int nviews
= displayed_views();
1886 int next_view
= (current_view
+ 1) % nviews
;
1888 if (next_view
== current_view
) {
1889 report("Only one view is displayed");
1893 current_view
= next_view
;
1894 /* Blur out the title of the previous view. */
1895 update_view_title(view
);
1899 case REQ_TOGGLE_LINENO
:
1900 opt_line_number
= !opt_line_number
;
1904 case REQ_TOGGLE_REV_GRAPH
:
1905 opt_rev_graph
= !opt_rev_graph
;
1910 /* Always reload^Wrerun commands from the prompt. */
1911 open_view(view
, opt_request
, OPEN_RELOAD
);
1914 case REQ_STOP_LOADING
:
1915 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
1918 report("Stopped loading the %s view", view
->name
),
1923 case REQ_SHOW_VERSION
:
1924 report("%s (built %s)", VERSION
, __DATE__
);
1927 case REQ_SCREEN_RESIZE
:
1930 case REQ_SCREEN_REDRAW
:
1934 case REQ_SCREEN_UPDATE
:
1938 case REQ_VIEW_CLOSE
:
1939 /* XXX: Mark closed views by letting view->parent point to the
1940 * view itself. Parents to closed view should never be
1943 view
->parent
->parent
!= view
->parent
) {
1944 memset(display
, 0, sizeof(display
));
1946 display
[current_view
] = view
->parent
;
1947 view
->parent
= view
;
1957 /* An unknown key will show most commonly used commands. */
1958 report("Unknown key, press 'h' for help");
1971 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
1973 char *text
= line
->data
;
1974 enum line_type type
= line
->type
;
1975 int textlen
= strlen(text
);
1978 wmove(view
->win
, lineno
, 0);
1980 if (view
->offset
+ lineno
== view
->lineno
) {
1981 if (type
== LINE_COMMIT
) {
1982 string_copy(view
->ref
, text
+ 7);
1983 string_copy(ref_commit
, view
->ref
);
1987 wchgat(view
->win
, -1, 0, type
, NULL
);
1990 attr
= get_line_attr(type
);
1991 wattrset(view
->win
, attr
);
1993 if (opt_line_number
|| opt_tab_size
< TABSIZE
) {
1994 static char spaces
[] = " ";
1995 int col_offset
= 0, col
= 0;
1997 if (opt_line_number
) {
1998 unsigned long real_lineno
= view
->offset
+ lineno
+ 1;
2000 if (real_lineno
== 1 ||
2001 (real_lineno
% opt_num_interval
) == 0) {
2002 wprintw(view
->win
, "%.*d", view
->digits
, real_lineno
);
2005 waddnstr(view
->win
, spaces
,
2006 MIN(view
->digits
, STRING_SIZE(spaces
)));
2008 waddstr(view
->win
, ": ");
2009 col_offset
= view
->digits
+ 2;
2012 while (text
&& col_offset
+ col
< view
->width
) {
2013 int cols_max
= view
->width
- col_offset
- col
;
2017 if (*text
== '\t') {
2019 assert(sizeof(spaces
) > TABSIZE
);
2021 cols
= opt_tab_size
- (col
% opt_tab_size
);
2024 text
= strchr(text
, '\t');
2025 cols
= line ? text
- pos
: strlen(pos
);
2028 waddnstr(view
->win
, pos
, MIN(cols
, cols_max
));
2033 int col
= 0, pos
= 0;
2035 for (; pos
< textlen
&& col
< view
->width
; pos
++, col
++)
2036 if (text
[pos
] == '\t')
2037 col
+= TABSIZE
- (col
% TABSIZE
) - 1;
2039 waddnstr(view
->win
, text
, pos
);
2046 add_pager_refs(struct view
*view
, struct line
*line
)
2049 char *data
= line
->data
;
2051 int bufpos
= 0, refpos
= 0;
2052 const char *sep
= "Refs: ";
2054 assert(line
->type
== LINE_COMMIT
);
2056 refs
= get_refs(data
+ STRING_SIZE("commit "));
2061 struct ref
*ref
= refs
[refpos
];
2062 char *fmt
= ref
->tag ?
"%s[%s]" : "%s%s";
2064 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
2067 } while (refs
[refpos
++]->next
);
2069 if (!realloc_lines(view
, view
->line_size
+ 1))
2072 line
= &view
->line
[view
->lines
];
2073 line
->data
= strdup(buf
);
2077 line
->type
= LINE_PP_REFS
;
2082 pager_read(struct view
*view
, char *data
)
2084 struct line
*line
= &view
->line
[view
->lines
];
2086 line
->data
= strdup(data
);
2090 line
->type
= get_line_type(line
->data
);
2093 if (line
->type
== LINE_COMMIT
&&
2094 (view
== VIEW(REQ_VIEW_DIFF
) ||
2095 view
== VIEW(REQ_VIEW_LOG
)))
2096 add_pager_refs(view
, line
);
2102 pager_enter(struct view
*view
, struct line
*line
)
2106 if (line
->type
== LINE_COMMIT
&&
2107 (view
== VIEW(REQ_VIEW_LOG
) ||
2108 view
== VIEW(REQ_VIEW_PAGER
))) {
2109 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
2113 /* Always scroll the view even if it was split. That way
2114 * you can use Enter to scroll through the log view and
2115 * split open each commit diff. */
2116 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
2118 /* FIXME: A minor workaround. Scrolling the view will call report("")
2119 * but if we are scrolling a non-current view this won't properly
2120 * update the view title. */
2122 update_view_title(view
);
2127 static struct view_ops pager_ops
= {
2140 char id
[41]; /* SHA1 ID. */
2141 char title
[75]; /* First line of the commit message. */
2142 char author
[75]; /* Author of the commit. */
2143 struct tm time
; /* Date from the author ident. */
2144 struct ref
**refs
; /* Repository references. */
2145 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
2146 size_t graph_size
; /* The width of the graph array. */
2150 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
2152 char buf
[DATE_COLS
+ 1];
2153 struct commit
*commit
= line
->data
;
2154 enum line_type type
;
2160 if (!*commit
->author
)
2163 wmove(view
->win
, lineno
, col
);
2165 if (view
->offset
+ lineno
== view
->lineno
) {
2166 string_copy(view
->ref
, commit
->id
);
2167 string_copy(ref_commit
, view
->ref
);
2169 wattrset(view
->win
, get_line_attr(type
));
2170 wchgat(view
->win
, -1, 0, type
, NULL
);
2173 type
= LINE_MAIN_COMMIT
;
2174 wattrset(view
->win
, get_line_attr(LINE_MAIN_DATE
));
2177 timelen
= strftime(buf
, sizeof(buf
), DATE_FORMAT
, &commit
->time
);
2178 waddnstr(view
->win
, buf
, timelen
);
2179 waddstr(view
->win
, " ");
2182 wmove(view
->win
, lineno
, col
);
2183 if (type
!= LINE_CURSOR
)
2184 wattrset(view
->win
, get_line_attr(LINE_MAIN_AUTHOR
));
2187 authorlen
= utf8_length(commit
->author
, AUTHOR_COLS
- 2, &col
, &trimmed
);
2189 authorlen
= strlen(commit
->author
);
2190 if (authorlen
> AUTHOR_COLS
- 2) {
2191 authorlen
= AUTHOR_COLS
- 2;
2197 waddnstr(view
->win
, commit
->author
, authorlen
);
2198 if (type
!= LINE_CURSOR
)
2199 wattrset(view
->win
, get_line_attr(LINE_MAIN_DELIM
));
2200 waddch(view
->win
, '~');
2202 waddstr(view
->win
, commit
->author
);
2206 if (type
!= LINE_CURSOR
)
2207 wattrset(view
->win
, A_NORMAL
);
2209 if (opt_rev_graph
&& commit
->graph_size
) {
2212 wmove(view
->win
, lineno
, col
);
2213 /* Using waddch() instead of waddnstr() ensures that
2214 * they'll be rendered correctly for the cursor line. */
2215 for (i
= 0; i
< commit
->graph_size
; i
++)
2216 waddch(view
->win
, commit
->graph
[i
]);
2218 col
+= commit
->graph_size
+ 1;
2221 wmove(view
->win
, lineno
, col
);
2227 if (type
== LINE_CURSOR
)
2229 else if (commit
->refs
[i
]->tag
)
2230 wattrset(view
->win
, get_line_attr(LINE_MAIN_TAG
));
2232 wattrset(view
->win
, get_line_attr(LINE_MAIN_REF
));
2233 waddstr(view
->win
, "[");
2234 waddstr(view
->win
, commit
->refs
[i
]->name
);
2235 waddstr(view
->win
, "]");
2236 if (type
!= LINE_CURSOR
)
2237 wattrset(view
->win
, A_NORMAL
);
2238 waddstr(view
->win
, " ");
2239 col
+= strlen(commit
->refs
[i
]->name
) + STRING_SIZE("[] ");
2240 } while (commit
->refs
[i
++]->next
);
2243 if (type
!= LINE_CURSOR
)
2244 wattrset(view
->win
, get_line_attr(type
));
2247 int titlelen
= strlen(commit
->title
);
2249 if (col
+ titlelen
> view
->width
)
2250 titlelen
= view
->width
- col
;
2252 waddnstr(view
->win
, commit
->title
, titlelen
);
2258 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2260 main_read(struct view
*view
, char *line
)
2262 enum line_type type
= get_line_type(line
);
2263 struct commit
*commit
= view
->lines
2264 ? view
->line
[view
->lines
- 1].data
: NULL
;
2268 commit
= calloc(1, sizeof(struct commit
));
2272 line
+= STRING_SIZE("commit ");
2274 view
->line
[view
->lines
++].data
= commit
;
2275 string_copy(commit
->id
, line
);
2276 commit
->refs
= get_refs(commit
->id
);
2277 commit
->graph
[commit
->graph_size
++] = ACS_LTEE
;
2282 char *ident
= line
+ STRING_SIZE("author ");
2283 char *end
= strchr(ident
, '<');
2289 for (; end
> ident
&& isspace(end
[-1]); end
--) ;
2293 string_copy(commit
->author
, ident
);
2295 /* Parse epoch and timezone */
2297 char *secs
= strchr(end
+ 1, '>');
2301 if (!secs
|| secs
[1] != ' ')
2305 time
= (time_t) atol(secs
);
2306 zone
= strchr(secs
, ' ');
2307 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700")) {
2311 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
2312 tz
+= ('0' - zone
[2]) * 60 * 60;
2313 tz
+= ('0' - zone
[3]) * 60;
2314 tz
+= ('0' - zone
[4]) * 60;
2321 gmtime_r(&time
, &commit
->time
);
2329 /* Fill in the commit title if it has not already been set. */
2330 if (commit
->title
[0])
2333 /* Require titles to start with a non-space character at the
2334 * offset used by git log. */
2335 /* FIXME: More gracefull handling of titles; append "..." to
2336 * shortened titles, etc. */
2337 if (strncmp(line
, " ", 4) ||
2341 string_copy(commit
->title
, line
+ 4);
2348 main_enter(struct view
*view
, struct line
*line
)
2350 enum open_flags flags
= display
[0] == view ? OPEN_SPLIT
: OPEN_DEFAULT
;
2352 open_view(view
, REQ_VIEW_DIFF
, flags
);
2356 static struct view_ops main_ops
= {
2365 * Unicode / UTF-8 handling
2367 * NOTE: Much of the following code for dealing with unicode is derived from
2368 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2369 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2372 /* I've (over)annotated a lot of code snippets because I am not entirely
2373 * confident that the approach taken by this small UTF-8 interface is correct.
2377 unicode_width(unsigned long c
)
2380 (c
<= 0x115f /* Hangul Jamo */
2383 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
2385 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
2386 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
2387 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
2388 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
2389 || (c
>= 0xffe0 && c
<= 0xffe6)
2390 || (c
>= 0x20000 && c
<= 0x2fffd)
2391 || (c
>= 0x30000 && c
<= 0x3fffd)))
2397 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2398 * Illegal bytes are set one. */
2399 static const unsigned char utf8_bytes
[256] = {
2400 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,
2401 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,
2402 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,
2403 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,
2404 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,
2405 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,
2406 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,
2407 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,
2410 /* Decode UTF-8 multi-byte representation into a unicode character. */
2411 static inline unsigned long
2412 utf8_to_unicode(const char *string
, size_t length
)
2414 unsigned long unicode
;
2418 unicode
= string
[0];
2421 unicode
= (string
[0] & 0x1f) << 6;
2422 unicode
+= (string
[1] & 0x3f);
2425 unicode
= (string
[0] & 0x0f) << 12;
2426 unicode
+= ((string
[1] & 0x3f) << 6);
2427 unicode
+= (string
[2] & 0x3f);
2430 unicode
= (string
[0] & 0x0f) << 18;
2431 unicode
+= ((string
[1] & 0x3f) << 12);
2432 unicode
+= ((string
[2] & 0x3f) << 6);
2433 unicode
+= (string
[3] & 0x3f);
2436 unicode
= (string
[0] & 0x0f) << 24;
2437 unicode
+= ((string
[1] & 0x3f) << 18);
2438 unicode
+= ((string
[2] & 0x3f) << 12);
2439 unicode
+= ((string
[3] & 0x3f) << 6);
2440 unicode
+= (string
[4] & 0x3f);
2443 unicode
= (string
[0] & 0x01) << 30;
2444 unicode
+= ((string
[1] & 0x3f) << 24);
2445 unicode
+= ((string
[2] & 0x3f) << 18);
2446 unicode
+= ((string
[3] & 0x3f) << 12);
2447 unicode
+= ((string
[4] & 0x3f) << 6);
2448 unicode
+= (string
[5] & 0x3f);
2451 die("Invalid unicode length");
2454 /* Invalid characters could return the special 0xfffd value but NUL
2455 * should be just as good. */
2456 return unicode
> 0xffff ?
0 : unicode
;
2459 /* Calculates how much of string can be shown within the given maximum width
2460 * and sets trimmed parameter to non-zero value if all of string could not be
2463 * Additionally, adds to coloffset how many many columns to move to align with
2464 * the expected position. Takes into account how multi-byte and double-width
2465 * characters will effect the cursor position.
2467 * Returns the number of bytes to output from string to satisfy max_width. */
2469 utf8_length(const char *string
, size_t max_width
, int *coloffset
, int *trimmed
)
2471 const char *start
= string
;
2472 const char *end
= strchr(string
, '\0');
2478 while (string
< end
) {
2479 int c
= *(unsigned char *) string
;
2480 unsigned char bytes
= utf8_bytes
[c
];
2482 unsigned long unicode
;
2484 if (string
+ bytes
> end
)
2487 /* Change representation to figure out whether
2488 * it is a single- or double-width character. */
2490 unicode
= utf8_to_unicode(string
, bytes
);
2491 /* FIXME: Graceful handling of invalid unicode character. */
2495 ucwidth
= unicode_width(unicode
);
2497 if (width
> max_width
) {
2502 /* The column offset collects the differences between the
2503 * number of bytes encoding a character and the number of
2504 * columns will be used for rendering said character.
2506 * So if some character A is encoded in 2 bytes, but will be
2507 * represented on the screen using only 1 byte this will and up
2508 * adding 1 to the multi-byte column offset.
2510 * Assumes that no double-width character can be encoding in
2511 * less than two bytes. */
2512 if (bytes
> ucwidth
)
2513 mbwidth
+= bytes
- ucwidth
;
2518 *coloffset
+= mbwidth
;
2520 return string
- start
;
2528 /* Whether or not the curses interface has been initialized. */
2529 static bool cursed
= FALSE
;
2531 /* The status window is used for polling keystrokes. */
2532 static WINDOW
*status_win
;
2534 /* Update status and title window. */
2536 report(const char *msg
, ...)
2538 static bool empty
= TRUE
;
2539 struct view
*view
= display
[current_view
];
2541 if (!empty
|| *msg
) {
2544 va_start(args
, msg
);
2547 wmove(status_win
, 0, 0);
2549 vwprintw(status_win
, msg
, args
);
2554 wrefresh(status_win
);
2559 update_view_title(view
);
2560 update_display_cursor();
2563 /* Controls when nodelay should be in effect when polling user input. */
2565 set_nonblocking_input(bool loading
)
2567 static unsigned int loading_views
;
2569 if ((loading
== FALSE
&& loading_views
-- == 1) ||
2570 (loading
== TRUE
&& loading_views
++ == 0))
2571 nodelay(status_win
, loading
);
2579 /* Initialize the curses library */
2580 if (isatty(STDIN_FILENO
)) {
2581 cursed
= !!initscr();
2583 /* Leave stdin and stdout alone when acting as a pager. */
2584 FILE *io
= fopen("/dev/tty", "r+");
2586 cursed
= !!newterm(NULL
, io
, io
);
2590 die("Failed to initialize curses");
2592 nonl(); /* Tell curses not to do NL->CR/NL on output */
2593 cbreak(); /* Take input chars one at a time, no wait for \n */
2594 noecho(); /* Don't echo input */
2595 leaveok(stdscr
, TRUE
);
2600 getmaxyx(stdscr
, y
, x
);
2601 status_win
= newwin(1, 0, y
- 1, 0);
2603 die("Failed to create status window");
2605 /* Enable keyboard mapping */
2606 keypad(status_win
, TRUE
);
2607 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
2613 enum { READING
, STOP
, CANCEL
} status
= READING
;
2614 char buf
[sizeof(opt_cmd
) - STRING_SIZE("git \0")];
2617 while (status
== READING
) {
2621 foreach_view (view
, i
)
2624 report(":%.*s", pos
, buf
);
2625 /* Refresh, accept single keystroke of input */
2626 key
= wgetch(status_win
);
2631 status
= pos ? STOP
: CANCEL
;
2649 if (pos
>= sizeof(buf
)) {
2650 report("Input string too long");
2655 buf
[pos
++] = (char) key
;
2659 if (status
== CANCEL
) {
2660 /* Clear the status window */
2666 if (!string_format(opt_cmd
, "git %s", buf
))
2668 opt_request
= REQ_VIEW_PAGER
;
2674 * Repository references
2677 static struct ref
*refs
;
2678 static size_t refs_size
;
2680 /* Id <-> ref store */
2681 static struct ref
***id_refs
;
2682 static size_t id_refs_size
;
2684 static struct ref
**
2687 struct ref
***tmp_id_refs
;
2688 struct ref
**ref_list
= NULL
;
2689 size_t ref_list_size
= 0;
2692 for (i
= 0; i
< id_refs_size
; i
++)
2693 if (!strcmp(id
, id_refs
[i
][0]->id
))
2696 tmp_id_refs
= realloc(id_refs
, (id_refs_size
+ 1) * sizeof(*id_refs
));
2700 id_refs
= tmp_id_refs
;
2702 for (i
= 0; i
< refs_size
; i
++) {
2705 if (strcmp(id
, refs
[i
].id
))
2708 tmp
= realloc(ref_list
, (ref_list_size
+ 1) * sizeof(*ref_list
));
2716 if (ref_list_size
> 0)
2717 ref_list
[ref_list_size
- 1]->next
= 1;
2718 ref_list
[ref_list_size
] = &refs
[i
];
2720 /* XXX: The properties of the commit chains ensures that we can
2721 * safely modify the shared ref. The repo references will
2722 * always be similar for the same id. */
2723 ref_list
[ref_list_size
]->next
= 0;
2728 id_refs
[id_refs_size
++] = ref_list
;
2734 read_ref(char *id
, int idlen
, char *name
, int namelen
)
2739 if (!strncmp(name
, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2740 /* Commits referenced by tags has "^{}" appended. */
2741 if (name
[namelen
- 1] != '}')
2744 while (namelen
> 0 && name
[namelen
] != '^')
2748 namelen
-= STRING_SIZE("refs/tags/");
2749 name
+= STRING_SIZE("refs/tags/");
2751 } else if (!strncmp(name
, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2752 namelen
-= STRING_SIZE("refs/heads/");
2753 name
+= STRING_SIZE("refs/heads/");
2755 } else if (!strcmp(name
, "HEAD")) {
2759 refs
= realloc(refs
, sizeof(*refs
) * (refs_size
+ 1));
2763 ref
= &refs
[refs_size
++];
2764 ref
->name
= malloc(namelen
+ 1);
2768 strncpy(ref
->name
, name
, namelen
);
2769 ref
->name
[namelen
] = 0;
2771 string_copy(ref
->id
, id
);
2779 const char *cmd_env
= getenv("TIG_LS_REMOTE");
2780 const char *cmd
= cmd_env
&& *cmd_env ? cmd_env
: TIG_LS_REMOTE
;
2782 return read_properties(popen(cmd
, "r"), "\t", read_ref
);
2786 read_repo_config_option(char *name
, int namelen
, char *value
, int valuelen
)
2788 if (!strcmp(name
, "i18n.commitencoding"))
2789 string_copy(opt_encoding
, value
);
2795 load_repo_config(void)
2797 return read_properties(popen("git repo-config --list", "r"),
2798 "=", read_repo_config_option
);
2802 read_properties(FILE *pipe
, const char *separators
,
2803 int (*read_property
)(char *, int, char *, int))
2805 char buffer
[BUFSIZ
];
2812 while (state
== OK
&& (name
= fgets(buffer
, sizeof(buffer
), pipe
))) {
2817 name
= chomp_string(name
);
2818 namelen
= strcspn(name
, separators
);
2820 if (name
[namelen
]) {
2822 value
= chomp_string(name
+ namelen
+ 1);
2823 valuelen
= strlen(value
);
2830 state
= read_property(name
, namelen
, value
, valuelen
);
2833 if (state
!= ERR
&& ferror(pipe
))
2846 static void __NORETURN
2849 /* XXX: Restore tty modes and let the OS cleanup the rest! */
2855 static void __NORETURN
2856 die(const char *err
, ...)
2862 va_start(args
, err
);
2863 fputs("tig: ", stderr
);
2864 vfprintf(stderr
, err
, args
);
2865 fputs("\n", stderr
);
2872 main(int argc
, char *argv
[])
2875 enum request request
;
2878 signal(SIGINT
, quit
);
2880 if (load_options() == ERR
)
2881 die("Failed to load user config.");
2883 /* Load the repo config file so options can be overwritten from
2884 * the command line. */
2885 if (load_repo_config() == ERR
)
2886 die("Failed to load repo config.");
2888 if (!parse_options(argc
, argv
))
2891 if (load_refs() == ERR
)
2892 die("Failed to load refs.");
2894 /* Require a git repository unless when running in pager mode. */
2895 if (refs_size
== 0 && opt_request
!= REQ_VIEW_PAGER
)
2896 die("Not a git repository");
2898 for (i
= 0; i
< ARRAY_SIZE(views
) && (view
= &views
[i
]); i
++)
2899 view
->cmd_env
= getenv(view
->cmd_env
);
2901 request
= opt_request
;
2905 while (view_driver(display
[current_view
], request
)) {
2909 foreach_view (view
, i
)
2912 /* Refresh, accept single keystroke of input */
2913 key
= wgetch(status_win
);
2915 request
= get_keybinding(display
[current_view
]->keymap
, key
);
2917 /* Some low-level request handling. This keeps access to
2918 * status_win restricted. */
2921 if (read_prompt() == ERR
)
2922 request
= REQ_SCREEN_UPDATE
;
2925 case REQ_SCREEN_RESIZE
:
2929 getmaxyx(stdscr
, height
, width
);
2931 /* Resize the status view and let the view driver take
2932 * care of resizing the displayed views. */
2933 wresize(status_win
, 1, width
);
2934 mvwin(status_win
, height
- 1, 0);
2935 wrefresh(status_win
);