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 "unknown-version"
30 #include <sys/types.h>
44 #define __NORETURN __attribute__((__noreturn__))
49 static void __NORETURN
die(const char *err
, ...);
50 static void report(const char *msg
, ...);
51 static int read_properties(FILE *pipe
, const char *separators
, int (*read
)(char *, size_t, char *, size_t));
52 static void set_nonblocking_input(bool loading
);
53 static size_t utf8_length(const char *string
, size_t max_width
, int *coloffset
, int *trimmed
);
55 #define ABS(x) ((x) >= 0 ? (x) : -(x))
56 #define MIN(x, y) ((x) < (y) ? (x) : (y))
58 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
59 #define STRING_SIZE(x) (sizeof(x) - 1)
61 #define SIZEOF_STR 1024 /* Default string size. */
62 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
63 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
67 #define REVGRAPH_INIT 'I'
68 #define REVGRAPH_MERGE 'M'
69 #define REVGRAPH_BRANCH '+'
70 #define REVGRAPH_COMMIT '*'
71 #define REVGRAPH_LINE '|'
73 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
75 /* This color name can be used to refer to the default term colors. */
76 #define COLOR_DEFAULT (-1)
78 #define ICONV_NONE ((iconv_t) -1)
80 /* The format and size of the date column in the main view. */
81 #define DATE_FORMAT "%Y-%m-%d %H:%M"
82 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
84 #define AUTHOR_COLS 20
86 /* The default interval between line numbers. */
87 #define NUMBER_INTERVAL 1
91 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
93 #define TIG_LS_REMOTE \
94 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
96 #define TIG_DIFF_CMD \
97 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
100 "git log --cc --stat -n100 %s 2>/dev/null"
102 #define TIG_MAIN_CMD \
103 "git log --topo-order --pretty=raw %s 2>/dev/null"
105 #define TIG_TREE_CMD \
108 #define TIG_BLOB_CMD \
109 "git cat-file blob %s"
111 /* XXX: Needs to be defined to the empty string. */
112 #define TIG_HELP_CMD ""
113 #define TIG_PAGER_CMD ""
114 #define TIG_STATUS_CMD ""
116 /* Some ascii-shorthands fitted into the ncurses namespace. */
118 #define KEY_RETURN '\r'
123 char *name
; /* Ref name; tag or head names are shortened. */
124 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
125 unsigned int tag
:1; /* Is it a tag? */
126 unsigned int remote
:1; /* Is it a remote ref? */
127 unsigned int next
:1; /* For ref lists: are there more refs? */
130 static struct ref
**get_refs(char *id
);
139 set_from_int_map(struct int_map
*map
, size_t map_size
,
140 int *value
, const char *name
, int namelen
)
145 for (i
= 0; i
< map_size
; i
++)
146 if (namelen
== map
[i
].namelen
&&
147 !strncasecmp(name
, map
[i
].name
, namelen
)) {
148 *value
= map
[i
].value
;
161 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
163 if (srclen
> dstlen
- 1)
166 strncpy(dst
, src
, srclen
);
170 /* Shorthands for safely copying into a fixed buffer. */
172 #define string_copy(dst, src) \
173 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
175 #define string_ncopy(dst, src, srclen) \
176 string_ncopy_do(dst, sizeof(dst), src, srclen)
178 #define string_copy_rev(dst, src) \
179 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
181 #define string_add(dst, from, src) \
182 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
185 chomp_string(char *name
)
189 while (isspace(*name
))
192 namelen
= strlen(name
) - 1;
193 while (namelen
> 0 && isspace(name
[namelen
]))
200 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
203 size_t pos
= bufpos ?
*bufpos
: 0;
206 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
212 return pos
>= bufsize ? FALSE
: TRUE
;
215 #define string_format(buf, fmt, args...) \
216 string_nformat(buf, sizeof(buf), NULL, fmt, args)
218 #define string_format_from(buf, from, fmt, args...) \
219 string_nformat(buf, sizeof(buf), from, fmt, args)
222 string_enum_compare(const char *str1
, const char *str2
, int len
)
226 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
228 /* Diff-Header == DIFF_HEADER */
229 for (i
= 0; i
< len
; i
++) {
230 if (toupper(str1
[i
]) == toupper(str2
[i
]))
233 if (string_enum_sep(str1
[i
]) &&
234 string_enum_sep(str2
[i
]))
237 return str1
[i
] - str2
[i
];
245 * NOTE: The following is a slightly modified copy of the git project's shell
246 * quoting routines found in the quote.c file.
248 * Help to copy the thing properly quoted for the shell safety. any single
249 * quote is replaced with '\'', any exclamation point is replaced with '\!',
250 * and the whole thing is enclosed in a
253 * original sq_quote result
254 * name ==> name ==> 'name'
255 * a b ==> a b ==> 'a b'
256 * a'b ==> a'\''b ==> 'a'\''b'
257 * a!b ==> a'\!'b ==> 'a'\!'b'
261 sq_quote(char buf
[SIZEOF_STR
], size_t bufsize
, const char *src
)
265 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
268 while ((c
= *src
++)) {
269 if (c
== '\'' || c
== '!') {
280 if (bufsize
< SIZEOF_STR
)
292 /* XXX: Keep the view request first and in sync with views[]. */ \
293 REQ_GROUP("View switching") \
294 REQ_(VIEW_MAIN, "Show main view"), \
295 REQ_(VIEW_DIFF, "Show diff view"), \
296 REQ_(VIEW_LOG, "Show log view"), \
297 REQ_(VIEW_TREE, "Show tree view"), \
298 REQ_(VIEW_BLOB, "Show blob view"), \
299 REQ_(VIEW_HELP, "Show help page"), \
300 REQ_(VIEW_PAGER, "Show pager view"), \
301 REQ_(VIEW_STATUS, "Show status view"), \
303 REQ_GROUP("View manipulation") \
304 REQ_(ENTER, "Enter current line and scroll"), \
305 REQ_(NEXT, "Move to next"), \
306 REQ_(PREVIOUS, "Move to previous"), \
307 REQ_(VIEW_NEXT, "Move focus to next view"), \
308 REQ_(VIEW_CLOSE, "Close the current view"), \
309 REQ_(QUIT, "Close all views and quit"), \
311 REQ_GROUP("Cursor navigation") \
312 REQ_(MOVE_UP, "Move cursor one line up"), \
313 REQ_(MOVE_DOWN, "Move cursor one line down"), \
314 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
315 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
316 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
317 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
319 REQ_GROUP("Scrolling") \
320 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
321 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
322 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
323 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
325 REQ_GROUP("Searching") \
326 REQ_(SEARCH, "Search the view"), \
327 REQ_(SEARCH_BACK, "Search backwards in the view"), \
328 REQ_(FIND_NEXT, "Find next search match"), \
329 REQ_(FIND_PREV, "Find previous search match"), \
332 REQ_(NONE, "Do nothing"), \
333 REQ_(PROMPT, "Bring up the prompt"), \
334 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
335 REQ_(SCREEN_RESIZE, "Resize the screen"), \
336 REQ_(SHOW_VERSION, "Show version information"), \
337 REQ_(STOP_LOADING, "Stop all loading views"), \
338 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
339 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
340 REQ_(STATUS_UPDATE, "Update file status"), \
341 REQ_(EDIT, "Open in editor")
344 /* User action requests. */
346 #define REQ_GROUP(help)
347 #define REQ_(req, help) REQ_##req
349 /* Offset all requests to avoid conflicts with ncurses getch values. */
350 REQ_OFFSET
= KEY_MAX
+ 1,
358 struct request_info
{
359 enum request request
;
365 static struct request_info req_info
[] = {
366 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
367 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
374 get_request(const char *name
)
376 int namelen
= strlen(name
);
379 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
380 if (req_info
[i
].namelen
== namelen
&&
381 !string_enum_compare(req_info
[i
].name
, name
, namelen
))
382 return req_info
[i
].request
;
392 static const char usage
[] =
393 "tig " VERSION
" (" __DATE__
")\n"
395 "Usage: tig [options]\n"
396 " or: tig [options] [--] [git log options]\n"
397 " or: tig [options] log [git log options]\n"
398 " or: tig [options] diff [git diff options]\n"
399 " or: tig [options] show [git show options]\n"
400 " or: tig [options] < [git command output]\n"
403 " -l Start up in log view\n"
404 " -d Start up in diff view\n"
405 " -S Start up in status view\n"
406 " -n[I], --line-number[=I] Show line numbers with given interval\n"
407 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
408 " -- Mark end of tig options\n"
409 " -v, --version Show version and exit\n"
410 " -h, --help Show help message and exit\n";
412 /* Option and state variables. */
413 static bool opt_line_number
= FALSE
;
414 static bool opt_rev_graph
= FALSE
;
415 static int opt_num_interval
= NUMBER_INTERVAL
;
416 static int opt_tab_size
= TABSIZE
;
417 static enum request opt_request
= REQ_VIEW_MAIN
;
418 static char opt_cmd
[SIZEOF_STR
] = "";
419 static char opt_path
[SIZEOF_STR
] = "";
420 static FILE *opt_pipe
= NULL
;
421 static char opt_encoding
[20] = "UTF-8";
422 static bool opt_utf8
= TRUE
;
423 static char opt_codeset
[20] = "UTF-8";
424 static iconv_t opt_iconv
= ICONV_NONE
;
425 static char opt_search
[SIZEOF_STR
] = "";
426 static char opt_cdup
[SIZEOF_STR
] = "";
427 static char opt_git_dir
[SIZEOF_STR
] = "";
428 static char opt_editor
[SIZEOF_STR
] = "";
436 check_option(char *opt
, char short_name
, char *name
, enum option_type type
, ...)
446 int namelen
= strlen(name
);
450 if (strncmp(opt
, name
, namelen
))
453 if (opt
[namelen
] == '=')
454 value
= opt
+ namelen
+ 1;
457 if (!short_name
|| opt
[1] != short_name
)
462 va_start(args
, type
);
463 if (type
== OPT_INT
) {
464 number
= va_arg(args
, int *);
466 *number
= atoi(value
);
473 /* Returns the index of log or diff command or -1 to exit. */
475 parse_options(int argc
, char *argv
[])
479 for (i
= 1; i
< argc
; i
++) {
482 if (!strcmp(opt
, "log") ||
483 !strcmp(opt
, "diff") ||
484 !strcmp(opt
, "show")) {
485 opt_request
= opt
[0] == 'l'
486 ? REQ_VIEW_LOG
: REQ_VIEW_DIFF
;
490 if (opt
[0] && opt
[0] != '-')
493 if (!strcmp(opt
, "-l")) {
494 opt_request
= REQ_VIEW_LOG
;
498 if (!strcmp(opt
, "-d")) {
499 opt_request
= REQ_VIEW_DIFF
;
503 if (!strcmp(opt
, "-S")) {
504 opt_request
= REQ_VIEW_STATUS
;
508 if (check_option(opt
, 'n', "line-number", OPT_INT
, &opt_num_interval
)) {
509 opt_line_number
= TRUE
;
513 if (check_option(opt
, 'b', "tab-size", OPT_INT
, &opt_tab_size
)) {
514 opt_tab_size
= MIN(opt_tab_size
, TABSIZE
);
518 if (check_option(opt
, 'v', "version", OPT_NONE
)) {
519 printf("tig version %s\n", VERSION
);
523 if (check_option(opt
, 'h', "help", OPT_NONE
)) {
528 if (!strcmp(opt
, "--")) {
533 die("unknown option '%s'\n\n%s", opt
, usage
);
536 if (!isatty(STDIN_FILENO
)) {
537 opt_request
= REQ_VIEW_PAGER
;
540 } else if (i
< argc
) {
543 if (opt_request
== REQ_VIEW_MAIN
)
544 /* XXX: This is vulnerable to the user overriding
545 * options required for the main view parser. */
546 string_copy(opt_cmd
, "git log --pretty=raw");
548 string_copy(opt_cmd
, "git");
549 buf_size
= strlen(opt_cmd
);
551 while (buf_size
< sizeof(opt_cmd
) && i
< argc
) {
552 opt_cmd
[buf_size
++] = ' ';
553 buf_size
= sq_quote(opt_cmd
, buf_size
, argv
[i
++]);
556 if (buf_size
>= sizeof(opt_cmd
))
557 die("command too long");
559 opt_cmd
[buf_size
] = 0;
562 if (*opt_encoding
&& strcasecmp(opt_encoding
, "UTF-8"))
570 * Line-oriented content detection.
574 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
575 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
576 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
577 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
578 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
579 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
580 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
581 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
582 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
583 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
584 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
585 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
586 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
587 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
588 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
589 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
590 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
591 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
592 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
593 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
594 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
595 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
596 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
597 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
598 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
599 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
600 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
601 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
602 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
603 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
604 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
605 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
606 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
607 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
608 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
609 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
610 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
611 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
612 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
613 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
614 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
615 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
616 LINE(STAT_SECTION, "", COLOR_DEFAULT, COLOR_BLUE, A_BOLD), \
617 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
618 LINE(STAT_STAGED, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
619 LINE(STAT_UNSTAGED,"", COLOR_YELLOW, COLOR_DEFAULT, 0), \
620 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
623 #define LINE(type, line, fg, bg, attr) \
630 const char *name
; /* Option name. */
631 int namelen
; /* Size of option name. */
632 const char *line
; /* The start of line to match. */
633 int linelen
; /* Size of string to match. */
634 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
637 static struct line_info line_info
[] = {
638 #define LINE(type, line, fg, bg, attr) \
639 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
644 static enum line_type
645 get_line_type(char *line
)
647 int linelen
= strlen(line
);
650 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
651 /* Case insensitive search matches Signed-off-by lines better. */
652 if (linelen
>= line_info
[type
].linelen
&&
653 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
660 get_line_attr(enum line_type type
)
662 assert(type
< ARRAY_SIZE(line_info
));
663 return COLOR_PAIR(type
) | line_info
[type
].attr
;
666 static struct line_info
*
667 get_line_info(char *name
, int namelen
)
671 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
672 if (namelen
== line_info
[type
].namelen
&&
673 !string_enum_compare(line_info
[type
].name
, name
, namelen
))
674 return &line_info
[type
];
682 int default_bg
= COLOR_BLACK
;
683 int default_fg
= COLOR_WHITE
;
688 if (use_default_colors() != ERR
) {
693 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
694 struct line_info
*info
= &line_info
[type
];
695 int bg
= info
->bg
== COLOR_DEFAULT ? default_bg
: info
->bg
;
696 int fg
= info
->fg
== COLOR_DEFAULT ? default_fg
: info
->fg
;
698 init_pair(type
, fg
, bg
);
706 unsigned int selected
:1;
708 void *data
; /* User data */
718 enum request request
;
719 struct keybinding
*next
;
722 static struct keybinding default_keybindings
[] = {
724 { 'm', REQ_VIEW_MAIN
},
725 { 'd', REQ_VIEW_DIFF
},
726 { 'l', REQ_VIEW_LOG
},
727 { 't', REQ_VIEW_TREE
},
728 { 'f', REQ_VIEW_BLOB
},
729 { 'p', REQ_VIEW_PAGER
},
730 { 'h', REQ_VIEW_HELP
},
731 { 'S', REQ_VIEW_STATUS
},
733 /* View manipulation */
734 { 'q', REQ_VIEW_CLOSE
},
735 { KEY_TAB
, REQ_VIEW_NEXT
},
736 { KEY_RETURN
, REQ_ENTER
},
737 { KEY_UP
, REQ_PREVIOUS
},
738 { KEY_DOWN
, REQ_NEXT
},
740 /* Cursor navigation */
741 { 'k', REQ_MOVE_UP
},
742 { 'j', REQ_MOVE_DOWN
},
743 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
744 { KEY_END
, REQ_MOVE_LAST_LINE
},
745 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
746 { ' ', REQ_MOVE_PAGE_DOWN
},
747 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
748 { 'b', REQ_MOVE_PAGE_UP
},
749 { '-', REQ_MOVE_PAGE_UP
},
752 { KEY_IC
, REQ_SCROLL_LINE_UP
},
753 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
754 { 'w', REQ_SCROLL_PAGE_UP
},
755 { 's', REQ_SCROLL_PAGE_DOWN
},
759 { '?', REQ_SEARCH_BACK
},
760 { 'n', REQ_FIND_NEXT
},
761 { 'N', REQ_FIND_PREV
},
765 { 'z', REQ_STOP_LOADING
},
766 { 'v', REQ_SHOW_VERSION
},
767 { 'r', REQ_SCREEN_REDRAW
},
768 { '.', REQ_TOGGLE_LINENO
},
769 { 'g', REQ_TOGGLE_REV_GRAPH
},
771 { 'u', REQ_STATUS_UPDATE
},
774 /* Using the ncurses SIGWINCH handler. */
775 { KEY_RESIZE
, REQ_SCREEN_RESIZE
},
778 #define KEYMAP_INFO \
790 #define KEYMAP_(name) KEYMAP_##name
795 static struct int_map keymap_table
[] = {
796 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
801 #define set_keymap(map, name) \
802 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
804 static struct keybinding
*keybindings
[ARRAY_SIZE(keymap_table
)];
807 add_keybinding(enum keymap keymap
, enum request request
, int key
)
809 struct keybinding
*keybinding
;
811 keybinding
= calloc(1, sizeof(*keybinding
));
813 die("Failed to allocate keybinding");
815 keybinding
->alias
= key
;
816 keybinding
->request
= request
;
817 keybinding
->next
= keybindings
[keymap
];
818 keybindings
[keymap
] = keybinding
;
821 /* Looks for a key binding first in the given map, then in the generic map, and
822 * lastly in the default keybindings. */
824 get_keybinding(enum keymap keymap
, int key
)
826 struct keybinding
*kbd
;
829 for (kbd
= keybindings
[keymap
]; kbd
; kbd
= kbd
->next
)
830 if (kbd
->alias
== key
)
833 for (kbd
= keybindings
[KEYMAP_GENERIC
]; kbd
; kbd
= kbd
->next
)
834 if (kbd
->alias
== key
)
837 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
838 if (default_keybindings
[i
].alias
== key
)
839 return default_keybindings
[i
].request
;
841 return (enum request
) key
;
850 static struct key key_table
[] = {
851 { "Enter", KEY_RETURN
},
853 { "Backspace", KEY_BACKSPACE
},
855 { "Escape", KEY_ESC
},
856 { "Left", KEY_LEFT
},
857 { "Right", KEY_RIGHT
},
859 { "Down", KEY_DOWN
},
860 { "Insert", KEY_IC
},
861 { "Delete", KEY_DC
},
863 { "Home", KEY_HOME
},
865 { "PageUp", KEY_PPAGE
},
866 { "PageDown", KEY_NPAGE
},
876 { "F10", KEY_F(10) },
877 { "F11", KEY_F(11) },
878 { "F12", KEY_F(12) },
882 get_key_value(const char *name
)
886 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
887 if (!strcasecmp(key_table
[i
].name
, name
))
888 return key_table
[i
].value
;
890 if (strlen(name
) == 1 && isprint(*name
))
897 get_key(enum request request
)
899 static char buf
[BUFSIZ
];
900 static char key_char
[] = "'X'";
907 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
908 struct keybinding
*keybinding
= &default_keybindings
[i
];
912 if (keybinding
->request
!= request
)
915 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
916 if (key_table
[key
].value
== keybinding
->alias
)
917 seq
= key_table
[key
].name
;
920 keybinding
->alias
< 127 &&
921 isprint(keybinding
->alias
)) {
922 key_char
[1] = (char) keybinding
->alias
;
929 if (!string_format_from(buf
, &pos
, "%s%s", sep
, seq
))
930 return "Too many keybindings!";
939 * User config file handling.
942 static struct int_map color_map
[] = {
943 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
955 #define set_color(color, name) \
956 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
958 static struct int_map attr_map
[] = {
959 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
969 #define set_attribute(attr, name) \
970 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
972 static int config_lineno
;
973 static bool config_errors
;
974 static char *config_msg
;
976 /* Wants: object fgcolor bgcolor [attr] */
978 option_color_command(int argc
, char *argv
[])
980 struct line_info
*info
;
982 if (argc
!= 3 && argc
!= 4) {
983 config_msg
= "Wrong number of arguments given to color command";
987 info
= get_line_info(argv
[0], strlen(argv
[0]));
989 config_msg
= "Unknown color name";
993 if (set_color(&info
->fg
, argv
[1]) == ERR
||
994 set_color(&info
->bg
, argv
[2]) == ERR
) {
995 config_msg
= "Unknown color";
999 if (argc
== 4 && set_attribute(&info
->attr
, argv
[3]) == ERR
) {
1000 config_msg
= "Unknown attribute";
1007 /* Wants: name = value */
1009 option_set_command(int argc
, char *argv
[])
1012 config_msg
= "Wrong number of arguments given to set command";
1016 if (strcmp(argv
[1], "=")) {
1017 config_msg
= "No value assigned";
1021 if (!strcmp(argv
[0], "show-rev-graph")) {
1022 opt_rev_graph
= (!strcmp(argv
[2], "1") ||
1023 !strcmp(argv
[2], "true") ||
1024 !strcmp(argv
[2], "yes"));
1028 if (!strcmp(argv
[0], "line-number-interval")) {
1029 opt_num_interval
= atoi(argv
[2]);
1033 if (!strcmp(argv
[0], "tab-size")) {
1034 opt_tab_size
= atoi(argv
[2]);
1038 if (!strcmp(argv
[0], "commit-encoding")) {
1039 char *arg
= argv
[2];
1040 int delimiter
= *arg
;
1043 switch (delimiter
) {
1046 for (arg
++, i
= 0; arg
[i
]; i
++)
1047 if (arg
[i
] == delimiter
) {
1052 string_ncopy(opt_encoding
, arg
, strlen(arg
));
1057 config_msg
= "Unknown variable name";
1061 /* Wants: mode request key */
1063 option_bind_command(int argc
, char *argv
[])
1065 enum request request
;
1070 config_msg
= "Wrong number of arguments given to bind command";
1074 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1075 config_msg
= "Unknown key map";
1079 key
= get_key_value(argv
[1]);
1081 config_msg
= "Unknown key";
1085 request
= get_request(argv
[2]);
1086 if (request
== REQ_UNKNOWN
) {
1087 config_msg
= "Unknown request name";
1091 add_keybinding(keymap
, request
, key
);
1097 set_option(char *opt
, char *value
)
1104 while (argc
< ARRAY_SIZE(argv
) && (valuelen
= strcspn(value
, " \t"))) {
1105 argv
[argc
++] = value
;
1112 while (isspace(*value
))
1116 if (!strcmp(opt
, "color"))
1117 return option_color_command(argc
, argv
);
1119 if (!strcmp(opt
, "set"))
1120 return option_set_command(argc
, argv
);
1122 if (!strcmp(opt
, "bind"))
1123 return option_bind_command(argc
, argv
);
1125 config_msg
= "Unknown option command";
1130 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1135 config_msg
= "Internal error";
1137 /* Check for comment markers, since read_properties() will
1138 * only ensure opt and value are split at first " \t". */
1139 optlen
= strcspn(opt
, "#");
1143 if (opt
[optlen
] != 0) {
1144 config_msg
= "No option value";
1148 /* Look for comment endings in the value. */
1149 size_t len
= strcspn(value
, "#");
1151 if (len
< valuelen
) {
1153 value
[valuelen
] = 0;
1156 status
= set_option(opt
, value
);
1159 if (status
== ERR
) {
1160 fprintf(stderr
, "Error on line %d, near '%.*s': %s\n",
1161 config_lineno
, (int) optlen
, opt
, config_msg
);
1162 config_errors
= TRUE
;
1165 /* Always keep going if errors are encountered. */
1172 char *home
= getenv("HOME");
1173 char buf
[SIZEOF_STR
];
1177 config_errors
= FALSE
;
1179 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1182 /* It's ok that the file doesn't exist. */
1183 file
= fopen(buf
, "r");
1187 if (read_properties(file
, " \t", read_option
) == ERR
||
1188 config_errors
== TRUE
)
1189 fprintf(stderr
, "Errors while loading %s.\n", buf
);
1202 /* The display array of active views and the index of the current view. */
1203 static struct view
*display
[2];
1204 static unsigned int current_view
;
1206 /* Reading from the prompt? */
1207 static bool input_mode
= FALSE
;
1209 #define foreach_displayed_view(view, i) \
1210 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1212 #define displayed_views() (display[1] != NULL ? 2 : 1)
1214 /* Current head and commit ID */
1215 static char ref_blob
[SIZEOF_REF
] = "";
1216 static char ref_commit
[SIZEOF_REF
] = "HEAD";
1217 static char ref_head
[SIZEOF_REF
] = "HEAD";
1220 const char *name
; /* View name */
1221 const char *cmd_fmt
; /* Default command line format */
1222 const char *cmd_env
; /* Command line set via environment */
1223 const char *id
; /* Points to either of ref_{head,commit,blob} */
1225 struct view_ops
*ops
; /* View operations */
1227 enum keymap keymap
; /* What keymap does this view have */
1229 char cmd
[SIZEOF_STR
]; /* Command buffer */
1230 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
1231 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
1233 int height
, width
; /* The width and height of the main window */
1234 WINDOW
*win
; /* The main window */
1235 WINDOW
*title
; /* The title window living below the main window */
1238 unsigned long offset
; /* Offset of the window top */
1239 unsigned long lineno
; /* Current line number */
1242 char grep
[SIZEOF_STR
]; /* Search string */
1243 regex_t
*regex
; /* Pre-compiled regex */
1245 /* If non-NULL, points to the view that opened this view. If this view
1246 * is closed tig will switch back to the parent view. */
1247 struct view
*parent
;
1250 unsigned long lines
; /* Total number of lines */
1251 struct line
*line
; /* Line index */
1252 unsigned long line_size
;/* Total number of allocated lines */
1253 unsigned int digits
; /* Number of digits in the lines member. */
1261 /* What type of content being displayed. Used in the title bar. */
1263 /* Open and reads in all view content. */
1264 bool (*open
)(struct view
*view
);
1265 /* Read one line; updates view->line. */
1266 bool (*read
)(struct view
*view
, char *data
);
1267 /* Draw one line; @lineno must be < view->height. */
1268 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
, bool selected
);
1269 /* Depending on view handle a special requests. */
1270 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
1271 /* Search for regex in a line. */
1272 bool (*grep
)(struct view
*view
, struct line
*line
);
1274 void (*select
)(struct view
*view
, struct line
*line
);
1277 static struct view_ops pager_ops
;
1278 static struct view_ops main_ops
;
1279 static struct view_ops tree_ops
;
1280 static struct view_ops blob_ops
;
1281 static struct view_ops help_ops
;
1282 static struct view_ops status_ops
;
1284 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1285 { name, cmd, #env, ref, ops, map}
1287 #define VIEW_(id, name, ops, ref) \
1288 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1291 static struct view views
[] = {
1292 VIEW_(MAIN
, "main", &main_ops
, ref_head
),
1293 VIEW_(DIFF
, "diff", &pager_ops
, ref_commit
),
1294 VIEW_(LOG
, "log", &pager_ops
, ref_head
),
1295 VIEW_(TREE
, "tree", &tree_ops
, ref_commit
),
1296 VIEW_(BLOB
, "blob", &blob_ops
, ref_blob
),
1297 VIEW_(HELP
, "help", &help_ops
, ""),
1298 VIEW_(PAGER
, "pager", &pager_ops
, "stdin"),
1299 VIEW_(STATUS
, "status", &status_ops
, ""),
1302 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1304 #define foreach_view(view, i) \
1305 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1307 #define view_is_displayed(view) \
1308 (view == display[0] || view == display[1])
1311 draw_view_line(struct view
*view
, unsigned int lineno
)
1314 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
1317 assert(view_is_displayed(view
));
1319 if (view
->offset
+ lineno
>= view
->lines
)
1322 line
= &view
->line
[view
->offset
+ lineno
];
1325 line
->selected
= TRUE
;
1326 view
->ops
->select(view
, line
);
1327 } else if (line
->selected
) {
1328 line
->selected
= FALSE
;
1329 wmove(view
->win
, lineno
, 0);
1330 wclrtoeol(view
->win
);
1333 scrollok(view
->win
, FALSE
);
1334 draw_ok
= view
->ops
->draw(view
, line
, lineno
, selected
);
1335 scrollok(view
->win
, TRUE
);
1341 redraw_view_from(struct view
*view
, int lineno
)
1343 assert(0 <= lineno
&& lineno
< view
->height
);
1345 for (; lineno
< view
->height
; lineno
++) {
1346 if (!draw_view_line(view
, lineno
))
1350 redrawwin(view
->win
);
1352 wnoutrefresh(view
->win
);
1354 wrefresh(view
->win
);
1358 redraw_view(struct view
*view
)
1361 redraw_view_from(view
, 0);
1366 update_view_title(struct view
*view
)
1368 char buf
[SIZEOF_STR
];
1369 char state
[SIZEOF_STR
];
1370 size_t bufpos
= 0, statelen
= 0;
1372 assert(view_is_displayed(view
));
1374 if (view
!= VIEW(REQ_VIEW_STATUS
) && (view
->lines
|| view
->pipe
)) {
1375 unsigned int view_lines
= view
->offset
+ view
->height
;
1376 unsigned int lines
= view
->lines
1377 ?
MIN(view_lines
, view
->lines
) * 100 / view
->lines
1380 string_format_from(state
, &statelen
, "- %s %d of %d (%d%%)",
1387 time_t secs
= time(NULL
) - view
->start_time
;
1389 /* Three git seconds are a long time ... */
1391 string_format_from(state
, &statelen
, " %lds", secs
);
1395 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
1396 if (*view
->ref
&& bufpos
< view
->width
) {
1397 size_t refsize
= strlen(view
->ref
);
1398 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
1400 if (minsize
< view
->width
)
1401 refsize
= view
->width
- minsize
+ 7;
1402 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
1405 if (statelen
&& bufpos
< view
->width
) {
1406 string_format_from(buf
, &bufpos
, " %s", state
);
1409 if (view
== display
[current_view
])
1410 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
1412 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
1414 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
1415 wclrtoeol(view
->title
);
1416 wmove(view
->title
, 0, view
->width
- 1);
1419 wnoutrefresh(view
->title
);
1421 wrefresh(view
->title
);
1425 resize_display(void)
1428 struct view
*base
= display
[0];
1429 struct view
*view
= display
[1] ? display
[1] : display
[0];
1431 /* Setup window dimensions */
1433 getmaxyx(stdscr
, base
->height
, base
->width
);
1435 /* Make room for the status window. */
1439 /* Horizontal split. */
1440 view
->width
= base
->width
;
1441 view
->height
= SCALE_SPLIT_VIEW(base
->height
);
1442 base
->height
-= view
->height
;
1444 /* Make room for the title bar. */
1448 /* Make room for the title bar. */
1453 foreach_displayed_view (view
, i
) {
1455 view
->win
= newwin(view
->height
, 0, offset
, 0);
1457 die("Failed to create %s view", view
->name
);
1459 scrollok(view
->win
, TRUE
);
1461 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
1463 die("Failed to create title window");
1466 wresize(view
->win
, view
->height
, view
->width
);
1467 mvwin(view
->win
, offset
, 0);
1468 mvwin(view
->title
, offset
+ view
->height
, 0);
1471 offset
+= view
->height
+ 1;
1476 redraw_display(void)
1481 foreach_displayed_view (view
, i
) {
1483 update_view_title(view
);
1488 update_display_cursor(struct view
*view
)
1490 /* Move the cursor to the right-most column of the cursor line.
1492 * XXX: This could turn out to be a bit expensive, but it ensures that
1493 * the cursor does not jump around. */
1495 wmove(view
->win
, view
->lineno
- view
->offset
, view
->width
- 1);
1496 wrefresh(view
->win
);
1504 /* Scrolling backend */
1506 do_scroll_view(struct view
*view
, int lines
)
1508 bool redraw_current_line
= FALSE
;
1510 /* The rendering expects the new offset. */
1511 view
->offset
+= lines
;
1513 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
1516 /* Move current line into the view. */
1517 if (view
->lineno
< view
->offset
) {
1518 view
->lineno
= view
->offset
;
1519 redraw_current_line
= TRUE
;
1520 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
1521 view
->lineno
= view
->offset
+ view
->height
- 1;
1522 redraw_current_line
= TRUE
;
1525 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
1527 /* Redraw the whole screen if scrolling is pointless. */
1528 if (view
->height
< ABS(lines
)) {
1532 int line
= lines
> 0 ? view
->height
- lines
: 0;
1533 int end
= line
+ ABS(lines
);
1535 wscrl(view
->win
, lines
);
1537 for (; line
< end
; line
++) {
1538 if (!draw_view_line(view
, line
))
1542 if (redraw_current_line
)
1543 draw_view_line(view
, view
->lineno
- view
->offset
);
1546 redrawwin(view
->win
);
1547 wrefresh(view
->win
);
1551 /* Scroll frontend */
1553 scroll_view(struct view
*view
, enum request request
)
1557 assert(view_is_displayed(view
));
1560 case REQ_SCROLL_PAGE_DOWN
:
1561 lines
= view
->height
;
1562 case REQ_SCROLL_LINE_DOWN
:
1563 if (view
->offset
+ lines
> view
->lines
)
1564 lines
= view
->lines
- view
->offset
;
1566 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
1567 report("Cannot scroll beyond the last line");
1572 case REQ_SCROLL_PAGE_UP
:
1573 lines
= view
->height
;
1574 case REQ_SCROLL_LINE_UP
:
1575 if (lines
> view
->offset
)
1576 lines
= view
->offset
;
1579 report("Cannot scroll beyond the first line");
1587 die("request %d not handled in switch", request
);
1590 do_scroll_view(view
, lines
);
1595 move_view(struct view
*view
, enum request request
)
1597 int scroll_steps
= 0;
1601 case REQ_MOVE_FIRST_LINE
:
1602 steps
= -view
->lineno
;
1605 case REQ_MOVE_LAST_LINE
:
1606 steps
= view
->lines
- view
->lineno
- 1;
1609 case REQ_MOVE_PAGE_UP
:
1610 steps
= view
->height
> view
->lineno
1611 ?
-view
->lineno
: -view
->height
;
1614 case REQ_MOVE_PAGE_DOWN
:
1615 steps
= view
->lineno
+ view
->height
>= view
->lines
1616 ? view
->lines
- view
->lineno
- 1 : view
->height
;
1628 die("request %d not handled in switch", request
);
1631 if (steps
<= 0 && view
->lineno
== 0) {
1632 report("Cannot move beyond the first line");
1635 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
1636 report("Cannot move beyond the last line");
1640 /* Move the current line */
1641 view
->lineno
+= steps
;
1642 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
1644 /* Check whether the view needs to be scrolled */
1645 if (view
->lineno
< view
->offset
||
1646 view
->lineno
>= view
->offset
+ view
->height
) {
1647 scroll_steps
= steps
;
1648 if (steps
< 0 && -steps
> view
->offset
) {
1649 scroll_steps
= -view
->offset
;
1651 } else if (steps
> 0) {
1652 if (view
->lineno
== view
->lines
- 1 &&
1653 view
->lines
> view
->height
) {
1654 scroll_steps
= view
->lines
- view
->offset
- 1;
1655 if (scroll_steps
>= view
->height
)
1656 scroll_steps
-= view
->height
- 1;
1661 if (!view_is_displayed(view
)) {
1662 view
->offset
+= scroll_steps
;
1663 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
1664 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
1668 /* Repaint the old "current" line if we be scrolling */
1669 if (ABS(steps
) < view
->height
)
1670 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
1673 do_scroll_view(view
, scroll_steps
);
1677 /* Draw the current line */
1678 draw_view_line(view
, view
->lineno
- view
->offset
);
1680 redrawwin(view
->win
);
1681 wrefresh(view
->win
);
1690 static void search_view(struct view
*view
, enum request request
);
1693 find_next_line(struct view
*view
, unsigned long lineno
, struct line
*line
)
1695 assert(view_is_displayed(view
));
1697 if (!view
->ops
->grep(view
, line
))
1700 if (lineno
- view
->offset
>= view
->height
) {
1701 view
->offset
= lineno
;
1702 view
->lineno
= lineno
;
1706 unsigned long old_lineno
= view
->lineno
- view
->offset
;
1708 view
->lineno
= lineno
;
1709 draw_view_line(view
, old_lineno
);
1711 draw_view_line(view
, view
->lineno
- view
->offset
);
1712 redrawwin(view
->win
);
1713 wrefresh(view
->win
);
1716 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
1721 find_next(struct view
*view
, enum request request
)
1723 unsigned long lineno
= view
->lineno
;
1728 report("No previous search");
1730 search_view(view
, request
);
1740 case REQ_SEARCH_BACK
:
1749 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
1750 lineno
+= direction
;
1752 /* Note, lineno is unsigned long so will wrap around in which case it
1753 * will become bigger than view->lines. */
1754 for (; lineno
< view
->lines
; lineno
+= direction
) {
1755 struct line
*line
= &view
->line
[lineno
];
1757 if (find_next_line(view
, lineno
, line
))
1761 report("No match found for '%s'", view
->grep
);
1765 search_view(struct view
*view
, enum request request
)
1770 regfree(view
->regex
);
1773 view
->regex
= calloc(1, sizeof(*view
->regex
));
1778 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
1779 if (regex_err
!= 0) {
1780 char buf
[SIZEOF_STR
] = "unknown error";
1782 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
1783 report("Search failed: %s", buf
);
1787 string_copy(view
->grep
, opt_search
);
1789 find_next(view
, request
);
1793 * Incremental updating
1797 end_update(struct view
*view
)
1801 set_nonblocking_input(FALSE
);
1802 if (view
->pipe
== stdin
)
1810 begin_update(struct view
*view
)
1816 string_copy(view
->cmd
, opt_cmd
);
1818 /* When running random commands, initially show the
1819 * command in the title. However, it maybe later be
1820 * overwritten if a commit line is selected. */
1821 string_copy(view
->ref
, view
->cmd
);
1823 } else if (view
== VIEW(REQ_VIEW_TREE
)) {
1824 const char *format
= view
->cmd_env ? view
->cmd_env
: view
->cmd_fmt
;
1825 char path
[SIZEOF_STR
];
1827 if (strcmp(view
->vid
, view
->id
))
1828 opt_path
[0] = path
[0] = 0;
1829 else if (sq_quote(path
, 0, opt_path
) >= sizeof(path
))
1832 if (!string_format(view
->cmd
, format
, view
->id
, path
))
1836 const char *format
= view
->cmd_env ? view
->cmd_env
: view
->cmd_fmt
;
1837 const char *id
= view
->id
;
1839 if (!string_format(view
->cmd
, format
, id
, id
, id
, id
, id
))
1842 /* Put the current ref_* value to the view title ref
1843 * member. This is needed by the blob view. Most other
1844 * views sets it automatically after loading because the
1845 * first line is a commit line. */
1846 string_copy_rev(view
->ref
, view
->id
);
1849 /* Special case for the pager view. */
1851 view
->pipe
= opt_pipe
;
1854 view
->pipe
= popen(view
->cmd
, "r");
1860 set_nonblocking_input(TRUE
);
1865 string_copy_rev(view
->vid
, view
->id
);
1870 for (i
= 0; i
< view
->lines
; i
++)
1871 if (view
->line
[i
].data
)
1872 free(view
->line
[i
].data
);
1878 view
->start_time
= time(NULL
);
1883 static struct line
*
1884 realloc_lines(struct view
*view
, size_t line_size
)
1886 struct line
*tmp
= realloc(view
->line
, sizeof(*view
->line
) * line_size
);
1892 view
->line_size
= line_size
;
1897 update_view(struct view
*view
)
1899 char in_buffer
[BUFSIZ
];
1900 char out_buffer
[BUFSIZ
* 2];
1902 /* The number of lines to read. If too low it will cause too much
1903 * redrawing (and possible flickering), if too high responsiveness
1905 unsigned long lines
= view
->height
;
1906 int redraw_from
= -1;
1911 /* Only redraw if lines are visible. */
1912 if (view
->offset
+ view
->height
>= view
->lines
)
1913 redraw_from
= view
->lines
- view
->offset
;
1915 /* FIXME: This is probably not perfect for backgrounded views. */
1916 if (!realloc_lines(view
, view
->lines
+ lines
))
1919 while ((line
= fgets(in_buffer
, sizeof(in_buffer
), view
->pipe
))) {
1920 size_t linelen
= strlen(line
);
1923 line
[linelen
- 1] = 0;
1925 if (opt_iconv
!= ICONV_NONE
) {
1927 size_t inlen
= linelen
;
1929 char *outbuf
= out_buffer
;
1930 size_t outlen
= sizeof(out_buffer
);
1934 ret
= iconv(opt_iconv
, &inbuf
, &inlen
, &outbuf
, &outlen
);
1935 if (ret
!= (size_t) -1) {
1937 linelen
= strlen(out_buffer
);
1941 if (!view
->ops
->read(view
, line
))
1951 lines
= view
->lines
;
1952 for (digits
= 0; lines
; digits
++)
1955 /* Keep the displayed view in sync with line number scaling. */
1956 if (digits
!= view
->digits
) {
1957 view
->digits
= digits
;
1962 if (!view_is_displayed(view
))
1965 if (view
== VIEW(REQ_VIEW_TREE
)) {
1966 /* Clear the view and redraw everything since the tree sorting
1967 * might have rearranged things. */
1970 } else if (redraw_from
>= 0) {
1971 /* If this is an incremental update, redraw the previous line
1972 * since for commits some members could have changed when
1973 * loading the main view. */
1974 if (redraw_from
> 0)
1977 /* Since revision graph visualization requires knowledge
1978 * about the parent commit, it causes a further one-off
1979 * needed to be redrawn for incremental updates. */
1980 if (redraw_from
> 0 && opt_rev_graph
)
1983 /* Incrementally draw avoids flickering. */
1984 redraw_view_from(view
, redraw_from
);
1987 /* Update the title _after_ the redraw so that if the redraw picks up a
1988 * commit reference in view->ref it'll be available here. */
1989 update_view_title(view
);
1992 if (ferror(view
->pipe
)) {
1993 report("Failed to read: %s", strerror(errno
));
1996 } else if (feof(view
->pipe
)) {
2004 report("Allocation failure");
2007 view
->ops
->read(view
, NULL
);
2012 static struct line
*
2013 add_line_data(struct view
*view
, void *data
, enum line_type type
)
2015 struct line
*line
= &view
->line
[view
->lines
++];
2017 memset(line
, 0, sizeof(*line
));
2024 static struct line
*
2025 add_line_text(struct view
*view
, char *data
, enum line_type type
)
2028 data
= strdup(data
);
2030 return data ?
add_line_data(view
, data
, type
) : NULL
;
2039 OPEN_DEFAULT
= 0, /* Use default view switching. */
2040 OPEN_SPLIT
= 1, /* Split current view. */
2041 OPEN_BACKGROUNDED
= 2, /* Backgrounded. */
2042 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
2046 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
2048 bool backgrounded
= !!(flags
& OPEN_BACKGROUNDED
);
2049 bool split
= !!(flags
& OPEN_SPLIT
);
2050 bool reload
= !!(flags
& OPEN_RELOAD
);
2051 struct view
*view
= VIEW(request
);
2052 int nviews
= displayed_views();
2053 struct view
*base_view
= display
[0];
2055 if (view
== prev
&& nviews
== 1 && !reload
) {
2056 report("Already in %s view", view
->name
);
2060 if (view
->ops
->open
) {
2061 if (!view
->ops
->open(view
)) {
2062 report("Failed to load %s view", view
->name
);
2066 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
2067 !begin_update(view
)) {
2068 report("Failed to load %s view", view
->name
);
2077 /* Maximize the current view. */
2078 memset(display
, 0, sizeof(display
));
2080 display
[current_view
] = view
;
2083 /* Resize the view when switching between split- and full-screen,
2084 * or when switching between two different full-screen views. */
2085 if (nviews
!= displayed_views() ||
2086 (nviews
== 1 && base_view
!= display
[0]))
2089 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
2090 /* Take the title line into account. */
2091 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
2093 /* Scroll the view that was split if the current line is
2094 * outside the new limited view. */
2095 do_scroll_view(prev
, lines
);
2098 if (prev
&& view
!= prev
) {
2099 if (split
&& !backgrounded
) {
2100 /* "Blur" the previous view. */
2101 update_view_title(prev
);
2104 view
->parent
= prev
;
2107 if (view
->pipe
&& view
->lines
== 0) {
2108 /* Clear the old view and let the incremental updating refill
2117 /* If the view is backgrounded the above calls to report()
2118 * won't redraw the view title. */
2120 update_view_title(view
);
2124 open_editor(struct view
*view
, char *file
)
2126 char cmd
[SIZEOF_STR
];
2127 char file_sq
[SIZEOF_STR
];
2130 editor
= getenv("GIT_EDITOR");
2131 if (!editor
&& *opt_editor
)
2132 editor
= opt_editor
;
2134 editor
= getenv("VISUAL");
2136 editor
= getenv("EDITOR");
2140 if (sq_quote(file_sq
, 0, file
) < sizeof(file_sq
) &&
2141 string_format(cmd
, "%s %s", editor
, file_sq
)) {
2142 def_prog_mode(); /* save current tty modes */
2143 endwin(); /* restore original tty modes */
2151 * User request switch noodle
2155 view_driver(struct view
*view
, enum request request
)
2159 if (view
&& view
->lines
) {
2160 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
2161 if (request
== REQ_NONE
)
2168 case REQ_MOVE_PAGE_UP
:
2169 case REQ_MOVE_PAGE_DOWN
:
2170 case REQ_MOVE_FIRST_LINE
:
2171 case REQ_MOVE_LAST_LINE
:
2172 move_view(view
, request
);
2175 case REQ_SCROLL_LINE_DOWN
:
2176 case REQ_SCROLL_LINE_UP
:
2177 case REQ_SCROLL_PAGE_DOWN
:
2178 case REQ_SCROLL_PAGE_UP
:
2179 scroll_view(view
, request
);
2184 report("No file chosen, press %s to open tree view",
2185 get_key(REQ_VIEW_TREE
));
2188 open_view(view
, request
, OPEN_DEFAULT
);
2191 case REQ_VIEW_PAGER
:
2192 if (!opt_pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
2193 report("No pager content, press %s to run command from prompt",
2194 get_key(REQ_PROMPT
));
2197 open_view(view
, request
, OPEN_DEFAULT
);
2205 case REQ_VIEW_STATUS
:
2206 open_view(view
, request
, OPEN_DEFAULT
);
2211 request
= request
== REQ_NEXT ? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
2213 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
2214 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
2215 (view
== VIEW(REQ_VIEW_BLOB
) &&
2216 view
->parent
== VIEW(REQ_VIEW_TREE
))) {
2219 view
= view
->parent
;
2220 line
= view
->lineno
;
2221 move_view(view
, request
);
2222 if (view_is_displayed(view
))
2223 update_view_title(view
);
2224 if (line
!= view
->lineno
)
2225 view
->ops
->request(view
, REQ_ENTER
,
2226 &view
->line
[view
->lineno
]);
2229 move_view(view
, request
);
2235 int nviews
= displayed_views();
2236 int next_view
= (current_view
+ 1) % nviews
;
2238 if (next_view
== current_view
) {
2239 report("Only one view is displayed");
2243 current_view
= next_view
;
2244 /* Blur out the title of the previous view. */
2245 update_view_title(view
);
2249 case REQ_TOGGLE_LINENO
:
2250 opt_line_number
= !opt_line_number
;
2254 case REQ_TOGGLE_REV_GRAPH
:
2255 opt_rev_graph
= !opt_rev_graph
;
2260 /* Always reload^Wrerun commands from the prompt. */
2261 open_view(view
, opt_request
, OPEN_RELOAD
);
2265 case REQ_SEARCH_BACK
:
2266 search_view(view
, request
);
2271 find_next(view
, request
);
2274 case REQ_STOP_LOADING
:
2275 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
2278 report("Stopped loading the %s view", view
->name
),
2283 case REQ_SHOW_VERSION
:
2284 report("tig-%s (built %s)", VERSION
, __DATE__
);
2287 case REQ_SCREEN_RESIZE
:
2290 case REQ_SCREEN_REDRAW
:
2295 report("Nothing to edit");
2299 report("Nothing to enter");
2306 case REQ_VIEW_CLOSE
:
2307 /* XXX: Mark closed views by letting view->parent point to the
2308 * view itself. Parents to closed view should never be
2311 view
->parent
->parent
!= view
->parent
) {
2312 memset(display
, 0, sizeof(display
));
2314 display
[current_view
] = view
->parent
;
2315 view
->parent
= view
;
2325 /* An unknown key will show most commonly used commands. */
2326 report("Unknown key, press 'h' for help");
2339 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
, bool selected
)
2341 char *text
= line
->data
;
2342 enum line_type type
= line
->type
;
2343 int textlen
= strlen(text
);
2346 wmove(view
->win
, lineno
, 0);
2350 wchgat(view
->win
, -1, 0, type
, NULL
);
2353 attr
= get_line_attr(type
);
2354 wattrset(view
->win
, attr
);
2356 if (opt_line_number
|| opt_tab_size
< TABSIZE
) {
2357 static char spaces
[] = " ";
2358 int col_offset
= 0, col
= 0;
2360 if (opt_line_number
) {
2361 unsigned long real_lineno
= view
->offset
+ lineno
+ 1;
2363 if (real_lineno
== 1 ||
2364 (real_lineno
% opt_num_interval
) == 0) {
2365 wprintw(view
->win
, "%.*d", view
->digits
, real_lineno
);
2368 waddnstr(view
->win
, spaces
,
2369 MIN(view
->digits
, STRING_SIZE(spaces
)));
2371 waddstr(view
->win
, ": ");
2372 col_offset
= view
->digits
+ 2;
2375 while (text
&& col_offset
+ col
< view
->width
) {
2376 int cols_max
= view
->width
- col_offset
- col
;
2380 if (*text
== '\t') {
2382 assert(sizeof(spaces
) > TABSIZE
);
2384 cols
= opt_tab_size
- (col
% opt_tab_size
);
2387 text
= strchr(text
, '\t');
2388 cols
= line ? text
- pos
: strlen(pos
);
2391 waddnstr(view
->win
, pos
, MIN(cols
, cols_max
));
2396 int col
= 0, pos
= 0;
2398 for (; pos
< textlen
&& col
< view
->width
; pos
++, col
++)
2399 if (text
[pos
] == '\t')
2400 col
+= TABSIZE
- (col
% TABSIZE
) - 1;
2402 waddnstr(view
->win
, text
, pos
);
2409 add_describe_ref(char *buf
, size_t *bufpos
, char *commit_id
, const char *sep
)
2411 char refbuf
[SIZEOF_STR
];
2415 if (!string_format(refbuf
, "git describe %s 2>/dev/null", commit_id
))
2418 pipe
= popen(refbuf
, "r");
2422 if ((ref
= fgets(refbuf
, sizeof(refbuf
), pipe
)))
2423 ref
= chomp_string(ref
);
2429 /* This is the only fatal call, since it can "corrupt" the buffer. */
2430 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
2437 add_pager_refs(struct view
*view
, struct line
*line
)
2439 char buf
[SIZEOF_STR
];
2440 char *commit_id
= line
->data
+ STRING_SIZE("commit ");
2442 size_t bufpos
= 0, refpos
= 0;
2443 const char *sep
= "Refs: ";
2444 bool is_tag
= FALSE
;
2446 assert(line
->type
== LINE_COMMIT
);
2448 refs
= get_refs(commit_id
);
2450 if (view
== VIEW(REQ_VIEW_DIFF
))
2451 goto try_add_describe_ref
;
2456 struct ref
*ref
= refs
[refpos
];
2457 char *fmt
= ref
->tag ?
"%s[%s]" :
2458 ref
->remote ?
"%s<%s>" : "%s%s";
2460 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
2465 } while (refs
[refpos
++]->next
);
2467 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
2468 try_add_describe_ref
:
2469 /* Add <tag>-g<commit_id> "fake" reference. */
2470 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
2477 if (!realloc_lines(view
, view
->line_size
+ 1))
2480 add_line_text(view
, buf
, LINE_PP_REFS
);
2484 pager_read(struct view
*view
, char *data
)
2491 line
= add_line_text(view
, data
, get_line_type(data
));
2495 if (line
->type
== LINE_COMMIT
&&
2496 (view
== VIEW(REQ_VIEW_DIFF
) ||
2497 view
== VIEW(REQ_VIEW_LOG
)))
2498 add_pager_refs(view
, line
);
2504 pager_request(struct view
*view
, enum request request
, struct line
*line
)
2508 if (request
!= REQ_ENTER
)
2511 if (line
->type
== LINE_COMMIT
&&
2512 (view
== VIEW(REQ_VIEW_LOG
) ||
2513 view
== VIEW(REQ_VIEW_PAGER
))) {
2514 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
2518 /* Always scroll the view even if it was split. That way
2519 * you can use Enter to scroll through the log view and
2520 * split open each commit diff. */
2521 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
2523 /* FIXME: A minor workaround. Scrolling the view will call report("")
2524 * but if we are scrolling a non-current view this won't properly
2525 * update the view title. */
2527 update_view_title(view
);
2533 pager_grep(struct view
*view
, struct line
*line
)
2536 char *text
= line
->data
;
2541 if (regexec(view
->regex
, text
, 1, &pmatch
, 0) == REG_NOMATCH
)
2548 pager_select(struct view
*view
, struct line
*line
)
2550 if (line
->type
== LINE_COMMIT
) {
2551 char *text
= line
->data
+ STRING_SIZE("commit ");
2553 if (view
!= VIEW(REQ_VIEW_PAGER
))
2554 string_copy_rev(view
->ref
, text
);
2555 string_copy_rev(ref_commit
, text
);
2559 static struct view_ops pager_ops
= {
2575 help_open(struct view
*view
)
2578 int lines
= ARRAY_SIZE(req_info
) + 2;
2581 if (view
->lines
> 0)
2584 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
2585 if (!req_info
[i
].request
)
2588 view
->line
= calloc(lines
, sizeof(*view
->line
));
2592 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
2594 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
2597 if (!req_info
[i
].request
) {
2598 add_line_text(view
, "", LINE_DEFAULT
);
2599 add_line_text(view
, req_info
[i
].help
, LINE_DEFAULT
);
2603 key
= get_key(req_info
[i
].request
);
2604 if (!string_format(buf
, " %-25s %s", key
, req_info
[i
].help
))
2607 add_line_text(view
, buf
, LINE_DEFAULT
);
2613 static struct view_ops help_ops
= {
2628 /* Parse output from git-ls-tree(1):
2630 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2631 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2632 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2633 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2636 #define SIZEOF_TREE_ATTR \
2637 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2639 #define TREE_UP_FORMAT "040000 tree %s\t.."
2642 tree_compare_entry(enum line_type type1
, char *name1
,
2643 enum line_type type2
, char *name2
)
2645 if (type1
!= type2
) {
2646 if (type1
== LINE_TREE_DIR
)
2651 return strcmp(name1
, name2
);
2655 tree_read(struct view
*view
, char *text
)
2657 size_t textlen
= text ?
strlen(text
) : 0;
2658 char buf
[SIZEOF_STR
];
2660 enum line_type type
;
2661 bool first_read
= view
->lines
== 0;
2663 if (textlen
<= SIZEOF_TREE_ATTR
)
2666 type
= text
[STRING_SIZE("100644 ")] == 't'
2667 ? LINE_TREE_DIR
: LINE_TREE_FILE
;
2670 /* Add path info line */
2671 if (!string_format(buf
, "Directory path /%s", opt_path
) ||
2672 !realloc_lines(view
, view
->line_size
+ 1) ||
2673 !add_line_text(view
, buf
, LINE_DEFAULT
))
2676 /* Insert "link" to parent directory. */
2678 if (!string_format(buf
, TREE_UP_FORMAT
, view
->ref
) ||
2679 !realloc_lines(view
, view
->line_size
+ 1) ||
2680 !add_line_text(view
, buf
, LINE_TREE_DIR
))
2685 /* Strip the path part ... */
2687 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
2688 size_t striplen
= strlen(opt_path
);
2689 char *path
= text
+ SIZEOF_TREE_ATTR
;
2691 if (pathlen
> striplen
)
2692 memmove(path
, path
+ striplen
,
2693 pathlen
- striplen
+ 1);
2696 /* Skip "Directory ..." and ".." line. */
2697 for (pos
= 1 + !!*opt_path
; pos
< view
->lines
; pos
++) {
2698 struct line
*line
= &view
->line
[pos
];
2699 char *path1
= ((char *) line
->data
) + SIZEOF_TREE_ATTR
;
2700 char *path2
= text
+ SIZEOF_TREE_ATTR
;
2701 int cmp
= tree_compare_entry(line
->type
, path1
, type
, path2
);
2706 text
= strdup(text
);
2710 if (view
->lines
> pos
)
2711 memmove(&view
->line
[pos
+ 1], &view
->line
[pos
],
2712 (view
->lines
- pos
) * sizeof(*line
));
2714 line
= &view
->line
[pos
];
2721 if (!add_line_text(view
, text
, type
))
2724 /* Move the current line to the first tree entry. */
2732 tree_request(struct view
*view
, enum request request
, struct line
*line
)
2734 enum open_flags flags
;
2736 if (request
!= REQ_ENTER
)
2739 switch (line
->type
) {
2741 /* Depending on whether it is a subdir or parent (updir?) link
2742 * mangle the path buffer. */
2743 if (line
== &view
->line
[1] && *opt_path
) {
2744 size_t path_len
= strlen(opt_path
);
2745 char *dirsep
= opt_path
+ path_len
- 1;
2747 while (dirsep
> opt_path
&& dirsep
[-1] != '/')
2753 size_t pathlen
= strlen(opt_path
);
2754 size_t origlen
= pathlen
;
2755 char *data
= line
->data
;
2756 char *basename
= data
+ SIZEOF_TREE_ATTR
;
2758 if (!string_format_from(opt_path
, &pathlen
, "%s/", basename
)) {
2759 opt_path
[origlen
] = 0;
2764 /* Trees and subtrees share the same ID, so they are not not
2765 * unique like blobs. */
2766 flags
= OPEN_RELOAD
;
2767 request
= REQ_VIEW_TREE
;
2770 case LINE_TREE_FILE
:
2771 flags
= display
[0] == view ? OPEN_SPLIT
: OPEN_DEFAULT
;
2772 request
= REQ_VIEW_BLOB
;
2779 open_view(view
, request
, flags
);
2785 tree_select(struct view
*view
, struct line
*line
)
2787 char *text
= line
->data
+ STRING_SIZE("100644 blob ");
2789 if (line
->type
== LINE_TREE_FILE
) {
2790 string_copy_rev(ref_blob
, text
);
2792 } else if (line
->type
!= LINE_TREE_DIR
) {
2796 string_copy_rev(view
->ref
, text
);
2799 static struct view_ops tree_ops
= {
2810 blob_read(struct view
*view
, char *line
)
2812 return add_line_text(view
, line
, LINE_DEFAULT
);
2815 static struct view_ops blob_ops
= {
2834 char rev
[SIZEOF_REV
];
2838 char rev
[SIZEOF_REV
];
2840 char name
[SIZEOF_STR
];
2843 /* Get fields from the diff line:
2844 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2847 status_get_diff(struct status
*file
, char *buf
, size_t bufsize
)
2849 char *old_mode
= buf
+ 1;
2850 char *new_mode
= buf
+ 8;
2851 char *old_rev
= buf
+ 15;
2852 char *new_rev
= buf
+ 56;
2853 char *status
= buf
+ 97;
2855 if (bufsize
!= 99 ||
2856 old_mode
[-1] != ':' ||
2857 new_mode
[-1] != ' ' ||
2858 old_rev
[-1] != ' ' ||
2859 new_rev
[-1] != ' ' ||
2863 file
->status
= *status
;
2865 string_copy_rev(file
->old
.rev
, old_rev
);
2866 string_copy_rev(file
->new.rev
, new_rev
);
2868 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
2869 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
2877 status_run(struct view
*view
, const char cmd
[], bool diff
, enum line_type type
)
2879 struct status
*file
= NULL
;
2880 char buf
[SIZEOF_STR
* 4];
2884 pipe
= popen(cmd
, "r");
2888 add_line_data(view
, NULL
, type
);
2890 while (!feof(pipe
) && !ferror(pipe
)) {
2894 readsize
= fread(buf
+ bufsize
, 1, sizeof(buf
) - bufsize
, pipe
);
2897 bufsize
+= readsize
;
2899 /* Process while we have NUL chars. */
2900 while ((sep
= memchr(buf
, 0, bufsize
))) {
2901 size_t sepsize
= sep
- buf
+ 1;
2904 if (!realloc_lines(view
, view
->line_size
+ 1))
2907 file
= calloc(1, sizeof(*file
));
2911 add_line_data(view
, file
, type
);
2914 /* Parse diff info part. */
2918 } else if (!file
->status
) {
2919 if (!status_get_diff(file
, buf
, sepsize
))
2923 memmove(buf
, sep
+ 1, bufsize
);
2925 sep
= memchr(buf
, 0, bufsize
);
2928 sepsize
= sep
- buf
+ 1;
2931 /* git-ls-files just delivers a NUL separated
2932 * list of file names similar to the second half
2933 * of the git-diff-* output. */
2934 string_ncopy(file
->name
, buf
, sepsize
);
2936 memmove(buf
, sep
+ 1, bufsize
);
2947 if (!view
->line
[view
->lines
- 1].data
)
2948 add_line_data(view
, NULL
, LINE_STAT_NONE
);
2954 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
2955 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
2956 #define STATUS_LIST_OTHER_CMD \
2957 "git ls-files -z --others --exclude-per-directory=.gitignore"
2959 #define STATUS_DIFF_SHOW_CMD \
2960 "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
2962 /* First parse staged info using git-diff-index(1), then parse unstaged
2963 * info using git-diff-files(1), and finally untracked files using
2964 * git-ls-files(1). */
2966 status_open(struct view
*view
)
2968 struct stat statbuf
;
2969 char exclude
[SIZEOF_STR
];
2970 char cmd
[SIZEOF_STR
];
2973 for (i
= 0; i
< view
->lines
; i
++)
2974 free(view
->line
[i
].data
);
2976 view
->lines
= view
->line_size
= 0;
2979 if (!realloc_lines(view
, view
->line_size
+ 6))
2982 if (!string_format(exclude
, "%s/info/exclude", opt_git_dir
))
2985 string_copy(cmd
, STATUS_LIST_OTHER_CMD
);
2987 if (stat(exclude
, &statbuf
) >= 0) {
2988 size_t cmdsize
= strlen(cmd
);
2990 if (!string_format_from(cmd
, &cmdsize
, " %s", "--exclude-from=") ||
2991 sq_quote(cmd
, cmdsize
, exclude
) >= sizeof(cmd
))
2995 if (!status_run(view
, STATUS_DIFF_INDEX_CMD
, TRUE
, LINE_STAT_STAGED
) ||
2996 !status_run(view
, STATUS_DIFF_FILES_CMD
, TRUE
, LINE_STAT_UNSTAGED
) ||
2997 !status_run(view
, cmd
, FALSE
, LINE_STAT_UNTRACKED
))
3004 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
, bool selected
)
3006 struct status
*status
= line
->data
;
3008 wmove(view
->win
, lineno
, 0);
3011 wattrset(view
->win
, get_line_attr(LINE_CURSOR
));
3012 wchgat(view
->win
, -1, 0, LINE_CURSOR
, NULL
);
3014 } else if (!status
&& line
->type
!= LINE_STAT_NONE
) {
3015 wattrset(view
->win
, get_line_attr(LINE_STAT_SECTION
));
3016 wchgat(view
->win
, -1, 0, LINE_STAT_SECTION
, NULL
);
3019 wattrset(view
->win
, get_line_attr(line
->type
));
3025 switch (line
->type
) {
3026 case LINE_STAT_STAGED
:
3027 text
= "Changes to be committed:";
3030 case LINE_STAT_UNSTAGED
:
3031 text
= "Changed but not updated:";
3034 case LINE_STAT_UNTRACKED
:
3035 text
= "Untracked files:";
3038 case LINE_STAT_NONE
:
3039 text
= " (no files)";
3046 waddstr(view
->win
, text
);
3050 waddch(view
->win
, status
->status
);
3052 wattrset(view
->win
, A_NORMAL
);
3053 wmove(view
->win
, lineno
, 4);
3054 waddstr(view
->win
, status
->name
);
3060 status_enter(struct view
*view
, struct line
*line
)
3062 struct status
*status
= line
->data
;
3063 char path
[SIZEOF_STR
] = "";
3067 if (line
->type
== LINE_STAT_NONE
||
3068 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
3069 report("No file to diff");
3073 if (status
&& sq_quote(path
, 0, status
->name
) >= sizeof(path
))
3077 line
->type
!= LINE_STAT_UNTRACKED
&&
3078 !string_format_from(opt_cmd
, &cmdsize
, "cd %s;", opt_cdup
))
3081 switch (line
->type
) {
3082 case LINE_STAT_STAGED
:
3083 if (!string_format_from(opt_cmd
, &cmdsize
, STATUS_DIFF_SHOW_CMD
,
3087 info
= "Staged changes to %s";
3089 info
= "Staged changes";
3092 case LINE_STAT_UNSTAGED
:
3093 if (!string_format_from(opt_cmd
, &cmdsize
, STATUS_DIFF_SHOW_CMD
,
3097 info
= "Unstaged changes to %s";
3099 info
= "Unstaged changes";
3102 case LINE_STAT_UNTRACKED
:
3108 report("No file to show");
3112 opt_pipe
= fopen(status
->name
, "r");
3113 info
= "Untracked file %s";
3120 open_view(view
, REQ_VIEW_DIFF
, OPEN_RELOAD
| OPEN_SPLIT
);
3121 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
))) {
3122 string_format(VIEW(REQ_VIEW_DIFF
)->ref
, info
, status
->name
);
3130 status_update_file(struct view
*view
, struct status
*status
, enum line_type type
)
3132 char cmd
[SIZEOF_STR
];
3133 char buf
[SIZEOF_STR
];
3140 type
!= LINE_STAT_UNTRACKED
&&
3141 !string_format_from(cmd
, &cmdsize
, "cd %s;", opt_cdup
))
3145 case LINE_STAT_STAGED
:
3146 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
3152 string_add(cmd
, cmdsize
, "git update-index -z --index-info");
3155 case LINE_STAT_UNSTAGED
:
3156 case LINE_STAT_UNTRACKED
:
3157 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->name
, 0))
3160 string_add(cmd
, cmdsize
, "git update-index -z --add --remove --stdin");
3167 pipe
= popen(cmd
, "w");
3171 while (!ferror(pipe
) && written
< bufsize
) {
3172 written
+= fwrite(buf
+ written
, 1, bufsize
- written
, pipe
);
3177 if (written
!= bufsize
)
3184 status_update(struct view
*view
)
3186 struct line
*line
= &view
->line
[view
->lineno
];
3188 assert(view
->lines
);
3191 while (++line
< view
->line
+ view
->lines
&& line
->data
) {
3192 if (!status_update_file(view
, line
->data
, line
->type
))
3193 report("Failed to update file status");
3196 if (!line
[-1].data
) {
3197 report("Nothing to update");
3201 } else if (!status_update_file(view
, line
->data
, line
->type
)) {
3202 report("Failed to update file status");
3205 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
3209 status_request(struct view
*view
, enum request request
, struct line
*line
)
3211 struct status
*status
= line
->data
;
3214 case REQ_STATUS_UPDATE
:
3215 status_update(view
);
3222 open_editor(view
, status
->name
);
3226 status_enter(view
, line
);
3237 status_select(struct view
*view
, struct line
*line
)
3239 struct status
*status
= line
->data
;
3240 char file
[SIZEOF_STR
] = "all files";
3243 if (status
&& !string_format(file
, "'%s'", status
->name
))
3246 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
3249 switch (line
->type
) {
3250 case LINE_STAT_STAGED
:
3251 text
= "Press %s to unstage %s for commit";
3254 case LINE_STAT_UNSTAGED
:
3255 text
= "Press %s to stage %s for commit";
3258 case LINE_STAT_UNTRACKED
:
3259 text
= "Press %s to stage %s for addition";
3262 case LINE_STAT_NONE
:
3263 text
= "Nothing to update";
3270 string_format(view
->ref
, text
, get_key(REQ_STATUS_UPDATE
), file
);
3274 status_grep(struct view
*view
, struct line
*line
)
3276 struct status
*status
= line
->data
;
3277 enum { S_STATUS
, S_NAME
, S_END
} state
;
3284 for (state
= S_STATUS
; state
< S_END
; state
++) {
3288 case S_NAME
: text
= status
->name
; break;
3290 buf
[0] = status
->status
;
3298 if (regexec(view
->regex
, text
, 1, &pmatch
, 0) != REG_NOMATCH
)
3305 static struct view_ops status_ops
= {
3321 char id
[SIZEOF_REV
]; /* SHA1 ID. */
3322 char title
[128]; /* First line of the commit message. */
3323 char author
[75]; /* Author of the commit. */
3324 struct tm time
; /* Date from the author ident. */
3325 struct ref
**refs
; /* Repository references. */
3326 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
3327 size_t graph_size
; /* The width of the graph array. */
3330 /* Size of rev graph with no "padding" columns */
3331 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3334 struct rev_graph
*prev
, *next
, *parents
;
3335 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
3337 struct commit
*commit
;
3341 /* Parents of the commit being visualized. */
3342 static struct rev_graph graph_parents
[4];
3344 /* The current stack of revisions on the graph. */
3345 static struct rev_graph graph_stacks
[4] = {
3346 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
3347 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
3348 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
3349 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
3353 graph_parent_is_merge(struct rev_graph
*graph
)
3355 return graph
->parents
->size
> 1;
3359 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
3361 struct commit
*commit
= graph
->commit
;
3363 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
3364 commit
->graph
[commit
->graph_size
++] = symbol
;
3368 done_rev_graph(struct rev_graph
*graph
)
3370 if (graph_parent_is_merge(graph
) &&
3371 graph
->pos
< graph
->size
- 1 &&
3372 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
3373 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
3375 graph
->commit
->graph_size
= i
* 2;
3376 while (i
< graph
->next
->size
- 1) {
3377 append_to_rev_graph(graph
, ' ');
3378 append_to_rev_graph(graph
, '\\');
3383 graph
->size
= graph
->pos
= 0;
3384 graph
->commit
= NULL
;
3385 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
3389 push_rev_graph(struct rev_graph
*graph
, char *parent
)
3393 /* "Collapse" duplicate parents lines.
3395 * FIXME: This needs to also update update the drawn graph but
3396 * for now it just serves as a method for pruning graph lines. */
3397 for (i
= 0; i
< graph
->size
; i
++)
3398 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
3401 if (graph
->size
< SIZEOF_REVITEMS
) {
3402 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
3407 get_rev_graph_symbol(struct rev_graph
*graph
)
3411 if (graph
->parents
->size
== 0)
3412 symbol
= REVGRAPH_INIT
;
3413 else if (graph_parent_is_merge(graph
))
3414 symbol
= REVGRAPH_MERGE
;
3415 else if (graph
->pos
>= graph
->size
)
3416 symbol
= REVGRAPH_BRANCH
;
3418 symbol
= REVGRAPH_COMMIT
;
3424 draw_rev_graph(struct rev_graph
*graph
)
3427 chtype separator
, line
;
3429 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
3430 static struct rev_filler fillers
[] = {
3431 { ' ', REVGRAPH_LINE
},
3436 chtype symbol
= get_rev_graph_symbol(graph
);
3437 struct rev_filler
*filler
;
3440 filler
= &fillers
[DEFAULT
];
3442 for (i
= 0; i
< graph
->pos
; i
++) {
3443 append_to_rev_graph(graph
, filler
->line
);
3444 if (graph_parent_is_merge(graph
->prev
) &&
3445 graph
->prev
->pos
== i
)
3446 filler
= &fillers
[RSHARP
];
3448 append_to_rev_graph(graph
, filler
->separator
);
3451 /* Place the symbol for this revision. */
3452 append_to_rev_graph(graph
, symbol
);
3454 if (graph
->prev
->size
> graph
->size
)
3455 filler
= &fillers
[RDIAG
];
3457 filler
= &fillers
[DEFAULT
];
3461 for (; i
< graph
->size
; i
++) {
3462 append_to_rev_graph(graph
, filler
->separator
);
3463 append_to_rev_graph(graph
, filler
->line
);
3464 if (graph_parent_is_merge(graph
->prev
) &&
3465 i
< graph
->prev
->pos
+ graph
->parents
->size
)
3466 filler
= &fillers
[RSHARP
];
3467 if (graph
->prev
->size
> graph
->size
)
3468 filler
= &fillers
[LDIAG
];
3471 if (graph
->prev
->size
> graph
->size
) {
3472 append_to_rev_graph(graph
, filler
->separator
);
3473 if (filler
->line
!= ' ')
3474 append_to_rev_graph(graph
, filler
->line
);
3478 /* Prepare the next rev graph */
3480 prepare_rev_graph(struct rev_graph
*graph
)
3484 /* First, traverse all lines of revisions up to the active one. */
3485 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
3486 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
3489 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
3492 /* Interleave the new revision parent(s). */
3493 for (i
= 0; i
< graph
->parents
->size
; i
++)
3494 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
3496 /* Lastly, put any remaining revisions. */
3497 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
3498 push_rev_graph(graph
->next
, graph
->rev
[i
]);
3502 update_rev_graph(struct rev_graph
*graph
)
3504 /* If this is the finalizing update ... */
3506 prepare_rev_graph(graph
);
3508 /* Graph visualization needs a one rev look-ahead,
3509 * so the first update doesn't visualize anything. */
3510 if (!graph
->prev
->commit
)
3513 draw_rev_graph(graph
->prev
);
3514 done_rev_graph(graph
->prev
->prev
);
3523 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
, bool selected
)
3525 char buf
[DATE_COLS
+ 1];
3526 struct commit
*commit
= line
->data
;
3527 enum line_type type
;
3533 if (!*commit
->author
)
3536 wmove(view
->win
, lineno
, col
);
3540 wattrset(view
->win
, get_line_attr(type
));
3541 wchgat(view
->win
, -1, 0, type
, NULL
);
3544 type
= LINE_MAIN_COMMIT
;
3545 wattrset(view
->win
, get_line_attr(LINE_MAIN_DATE
));
3548 timelen
= strftime(buf
, sizeof(buf
), DATE_FORMAT
, &commit
->time
);
3549 waddnstr(view
->win
, buf
, timelen
);
3550 waddstr(view
->win
, " ");
3553 wmove(view
->win
, lineno
, col
);
3554 if (type
!= LINE_CURSOR
)
3555 wattrset(view
->win
, get_line_attr(LINE_MAIN_AUTHOR
));
3558 authorlen
= utf8_length(commit
->author
, AUTHOR_COLS
- 2, &col
, &trimmed
);
3560 authorlen
= strlen(commit
->author
);
3561 if (authorlen
> AUTHOR_COLS
- 2) {
3562 authorlen
= AUTHOR_COLS
- 2;
3568 waddnstr(view
->win
, commit
->author
, authorlen
);
3569 if (type
!= LINE_CURSOR
)
3570 wattrset(view
->win
, get_line_attr(LINE_MAIN_DELIM
));
3571 waddch(view
->win
, '~');
3573 waddstr(view
->win
, commit
->author
);
3577 if (type
!= LINE_CURSOR
)
3578 wattrset(view
->win
, A_NORMAL
);
3580 if (opt_rev_graph
&& commit
->graph_size
) {
3583 wmove(view
->win
, lineno
, col
);
3584 /* Using waddch() instead of waddnstr() ensures that
3585 * they'll be rendered correctly for the cursor line. */
3586 for (i
= 0; i
< commit
->graph_size
; i
++)
3587 waddch(view
->win
, commit
->graph
[i
]);
3589 waddch(view
->win
, ' ');
3590 col
+= commit
->graph_size
+ 1;
3593 wmove(view
->win
, lineno
, col
);
3599 if (type
== LINE_CURSOR
)
3601 else if (commit
->refs
[i
]->tag
)
3602 wattrset(view
->win
, get_line_attr(LINE_MAIN_TAG
));
3603 else if (commit
->refs
[i
]->remote
)
3604 wattrset(view
->win
, get_line_attr(LINE_MAIN_REMOTE
));
3606 wattrset(view
->win
, get_line_attr(LINE_MAIN_REF
));
3607 waddstr(view
->win
, "[");
3608 waddstr(view
->win
, commit
->refs
[i
]->name
);
3609 waddstr(view
->win
, "]");
3610 if (type
!= LINE_CURSOR
)
3611 wattrset(view
->win
, A_NORMAL
);
3612 waddstr(view
->win
, " ");
3613 col
+= strlen(commit
->refs
[i
]->name
) + STRING_SIZE("[] ");
3614 } while (commit
->refs
[i
++]->next
);
3617 if (type
!= LINE_CURSOR
)
3618 wattrset(view
->win
, get_line_attr(type
));
3621 int titlelen
= strlen(commit
->title
);
3623 if (col
+ titlelen
> view
->width
)
3624 titlelen
= view
->width
- col
;
3626 waddnstr(view
->win
, commit
->title
, titlelen
);
3632 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3634 main_read(struct view
*view
, char *line
)
3636 static struct rev_graph
*graph
= graph_stacks
;
3637 enum line_type type
;
3638 struct commit
*commit
;
3641 update_rev_graph(graph
);
3645 type
= get_line_type(line
);
3646 if (type
== LINE_COMMIT
) {
3647 commit
= calloc(1, sizeof(struct commit
));
3651 string_copy_rev(commit
->id
, line
+ STRING_SIZE("commit "));
3652 commit
->refs
= get_refs(commit
->id
);
3653 graph
->commit
= commit
;
3654 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
3660 commit
= view
->line
[view
->lines
- 1].data
;
3664 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
3669 /* Parse author lines where the name may be empty:
3670 * author <email@address.tld> 1138474660 +0100
3672 char *ident
= line
+ STRING_SIZE("author ");
3673 char *nameend
= strchr(ident
, '<');
3674 char *emailend
= strchr(ident
, '>');
3676 if (!nameend
|| !emailend
)
3679 update_rev_graph(graph
);
3680 graph
= graph
->next
;
3682 *nameend
= *emailend
= 0;
3683 ident
= chomp_string(ident
);
3685 ident
= chomp_string(nameend
+ 1);
3690 string_ncopy(commit
->author
, ident
, strlen(ident
));
3692 /* Parse epoch and timezone */
3693 if (emailend
[1] == ' ') {
3694 char *secs
= emailend
+ 2;
3695 char *zone
= strchr(secs
, ' ');
3696 time_t time
= (time_t) atol(secs
);
3698 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700")) {
3702 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3703 tz
+= ('0' - zone
[2]) * 60 * 60;
3704 tz
+= ('0' - zone
[3]) * 60;
3705 tz
+= ('0' - zone
[4]) * 60;
3713 gmtime_r(&time
, &commit
->time
);
3718 /* Fill in the commit title if it has not already been set. */
3719 if (commit
->title
[0])
3722 /* Require titles to start with a non-space character at the
3723 * offset used by git log. */
3724 if (strncmp(line
, " ", 4))
3727 /* Well, if the title starts with a whitespace character,
3728 * try to be forgiving. Otherwise we end up with no title. */
3729 while (isspace(*line
))
3733 /* FIXME: More graceful handling of titles; append "..." to
3734 * shortened titles, etc. */
3736 string_ncopy(commit
->title
, line
, strlen(line
));
3743 main_request(struct view
*view
, enum request request
, struct line
*line
)
3745 enum open_flags flags
= display
[0] == view ? OPEN_SPLIT
: OPEN_DEFAULT
;
3747 if (request
== REQ_ENTER
)
3748 open_view(view
, REQ_VIEW_DIFF
, flags
);
3756 main_grep(struct view
*view
, struct line
*line
)
3758 struct commit
*commit
= line
->data
;
3759 enum { S_TITLE
, S_AUTHOR
, S_DATE
, S_END
} state
;
3760 char buf
[DATE_COLS
+ 1];
3763 for (state
= S_TITLE
; state
< S_END
; state
++) {
3767 case S_TITLE
: text
= commit
->title
; break;
3768 case S_AUTHOR
: text
= commit
->author
; break;
3770 if (!strftime(buf
, sizeof(buf
), DATE_FORMAT
, &commit
->time
))
3779 if (regexec(view
->regex
, text
, 1, &pmatch
, 0) != REG_NOMATCH
)
3787 main_select(struct view
*view
, struct line
*line
)
3789 struct commit
*commit
= line
->data
;
3791 string_copy_rev(view
->ref
, commit
->id
);
3792 string_copy_rev(ref_commit
, view
->ref
);
3795 static struct view_ops main_ops
= {
3807 * Unicode / UTF-8 handling
3809 * NOTE: Much of the following code for dealing with unicode is derived from
3810 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3811 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3814 /* I've (over)annotated a lot of code snippets because I am not entirely
3815 * confident that the approach taken by this small UTF-8 interface is correct.
3819 unicode_width(unsigned long c
)
3822 (c
<= 0x115f /* Hangul Jamo */
3825 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
3827 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
3828 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
3829 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
3830 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
3831 || (c
>= 0xffe0 && c
<= 0xffe6)
3832 || (c
>= 0x20000 && c
<= 0x2fffd)
3833 || (c
>= 0x30000 && c
<= 0x3fffd)))
3839 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3840 * Illegal bytes are set one. */
3841 static const unsigned char utf8_bytes
[256] = {
3842 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,
3843 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,
3844 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,
3845 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,
3846 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,
3847 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,
3848 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,
3849 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,
3852 /* Decode UTF-8 multi-byte representation into a unicode character. */
3853 static inline unsigned long
3854 utf8_to_unicode(const char *string
, size_t length
)
3856 unsigned long unicode
;
3860 unicode
= string
[0];
3863 unicode
= (string
[0] & 0x1f) << 6;
3864 unicode
+= (string
[1] & 0x3f);
3867 unicode
= (string
[0] & 0x0f) << 12;
3868 unicode
+= ((string
[1] & 0x3f) << 6);
3869 unicode
+= (string
[2] & 0x3f);
3872 unicode
= (string
[0] & 0x0f) << 18;
3873 unicode
+= ((string
[1] & 0x3f) << 12);
3874 unicode
+= ((string
[2] & 0x3f) << 6);
3875 unicode
+= (string
[3] & 0x3f);
3878 unicode
= (string
[0] & 0x0f) << 24;
3879 unicode
+= ((string
[1] & 0x3f) << 18);
3880 unicode
+= ((string
[2] & 0x3f) << 12);
3881 unicode
+= ((string
[3] & 0x3f) << 6);
3882 unicode
+= (string
[4] & 0x3f);
3885 unicode
= (string
[0] & 0x01) << 30;
3886 unicode
+= ((string
[1] & 0x3f) << 24);
3887 unicode
+= ((string
[2] & 0x3f) << 18);
3888 unicode
+= ((string
[3] & 0x3f) << 12);
3889 unicode
+= ((string
[4] & 0x3f) << 6);
3890 unicode
+= (string
[5] & 0x3f);
3893 die("Invalid unicode length");
3896 /* Invalid characters could return the special 0xfffd value but NUL
3897 * should be just as good. */
3898 return unicode
> 0xffff ?
0 : unicode
;
3901 /* Calculates how much of string can be shown within the given maximum width
3902 * and sets trimmed parameter to non-zero value if all of string could not be
3905 * Additionally, adds to coloffset how many many columns to move to align with
3906 * the expected position. Takes into account how multi-byte and double-width
3907 * characters will effect the cursor position.
3909 * Returns the number of bytes to output from string to satisfy max_width. */
3911 utf8_length(const char *string
, size_t max_width
, int *coloffset
, int *trimmed
)
3913 const char *start
= string
;
3914 const char *end
= strchr(string
, '\0');
3920 while (string
< end
) {
3921 int c
= *(unsigned char *) string
;
3922 unsigned char bytes
= utf8_bytes
[c
];
3924 unsigned long unicode
;
3926 if (string
+ bytes
> end
)
3929 /* Change representation to figure out whether
3930 * it is a single- or double-width character. */
3932 unicode
= utf8_to_unicode(string
, bytes
);
3933 /* FIXME: Graceful handling of invalid unicode character. */
3937 ucwidth
= unicode_width(unicode
);
3939 if (width
> max_width
) {
3944 /* The column offset collects the differences between the
3945 * number of bytes encoding a character and the number of
3946 * columns will be used for rendering said character.
3948 * So if some character A is encoded in 2 bytes, but will be
3949 * represented on the screen using only 1 byte this will and up
3950 * adding 1 to the multi-byte column offset.
3952 * Assumes that no double-width character can be encoding in
3953 * less than two bytes. */
3954 if (bytes
> ucwidth
)
3955 mbwidth
+= bytes
- ucwidth
;
3960 *coloffset
+= mbwidth
;
3962 return string
- start
;
3970 /* Whether or not the curses interface has been initialized. */
3971 static bool cursed
= FALSE
;
3973 /* The status window is used for polling keystrokes. */
3974 static WINDOW
*status_win
;
3976 static bool status_empty
= TRUE
;
3978 /* Update status and title window. */
3980 report(const char *msg
, ...)
3982 struct view
*view
= display
[current_view
];
3987 if (!status_empty
|| *msg
) {
3990 va_start(args
, msg
);
3992 wmove(status_win
, 0, 0);
3994 vwprintw(status_win
, msg
, args
);
3995 status_empty
= FALSE
;
3997 status_empty
= TRUE
;
3999 wclrtoeol(status_win
);
4000 wrefresh(status_win
);
4005 update_view_title(view
);
4006 update_display_cursor(view
);
4009 /* Controls when nodelay should be in effect when polling user input. */
4011 set_nonblocking_input(bool loading
)
4013 static unsigned int loading_views
;
4015 if ((loading
== FALSE
&& loading_views
-- == 1) ||
4016 (loading
== TRUE
&& loading_views
++ == 0))
4017 nodelay(status_win
, loading
);
4025 /* Initialize the curses library */
4026 if (isatty(STDIN_FILENO
)) {
4027 cursed
= !!initscr();
4029 /* Leave stdin and stdout alone when acting as a pager. */
4030 FILE *io
= fopen("/dev/tty", "r+");
4033 die("Failed to open /dev/tty");
4034 cursed
= !!newterm(NULL
, io
, io
);
4038 die("Failed to initialize curses");
4040 nonl(); /* Tell curses not to do NL->CR/NL on output */
4041 cbreak(); /* Take input chars one at a time, no wait for \n */
4042 noecho(); /* Don't echo input */
4043 leaveok(stdscr
, TRUE
);
4048 getmaxyx(stdscr
, y
, x
);
4049 status_win
= newwin(1, 0, y
- 1, 0);
4051 die("Failed to create status window");
4053 /* Enable keyboard mapping */
4054 keypad(status_win
, TRUE
);
4055 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
4059 read_prompt(const char *prompt
)
4061 enum { READING
, STOP
, CANCEL
} status
= READING
;
4062 static char buf
[sizeof(opt_cmd
) - STRING_SIZE("git \0")];
4065 while (status
== READING
) {
4071 foreach_view (view
, i
)
4076 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
4077 wclrtoeol(status_win
);
4079 /* Refresh, accept single keystroke of input */
4080 key
= wgetch(status_win
);
4085 status
= pos ? STOP
: CANCEL
;
4103 if (pos
>= sizeof(buf
)) {
4104 report("Input string too long");
4109 buf
[pos
++] = (char) key
;
4113 /* Clear the status window */
4114 status_empty
= FALSE
;
4117 if (status
== CANCEL
)
4126 * Repository references
4129 static struct ref
*refs
;
4130 static size_t refs_size
;
4132 /* Id <-> ref store */
4133 static struct ref
***id_refs
;
4134 static size_t id_refs_size
;
4136 static struct ref
**
4139 struct ref
***tmp_id_refs
;
4140 struct ref
**ref_list
= NULL
;
4141 size_t ref_list_size
= 0;
4144 for (i
= 0; i
< id_refs_size
; i
++)
4145 if (!strcmp(id
, id_refs
[i
][0]->id
))
4148 tmp_id_refs
= realloc(id_refs
, (id_refs_size
+ 1) * sizeof(*id_refs
));
4152 id_refs
= tmp_id_refs
;
4154 for (i
= 0; i
< refs_size
; i
++) {
4157 if (strcmp(id
, refs
[i
].id
))
4160 tmp
= realloc(ref_list
, (ref_list_size
+ 1) * sizeof(*ref_list
));
4168 if (ref_list_size
> 0)
4169 ref_list
[ref_list_size
- 1]->next
= 1;
4170 ref_list
[ref_list_size
] = &refs
[i
];
4172 /* XXX: The properties of the commit chains ensures that we can
4173 * safely modify the shared ref. The repo references will
4174 * always be similar for the same id. */
4175 ref_list
[ref_list_size
]->next
= 0;
4180 id_refs
[id_refs_size
++] = ref_list
;
4186 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
4190 bool remote
= FALSE
;
4192 if (!strncmp(name
, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4193 /* Commits referenced by tags has "^{}" appended. */
4194 if (name
[namelen
- 1] != '}')
4197 while (namelen
> 0 && name
[namelen
] != '^')
4201 namelen
-= STRING_SIZE("refs/tags/");
4202 name
+= STRING_SIZE("refs/tags/");
4204 } else if (!strncmp(name
, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4206 namelen
-= STRING_SIZE("refs/remotes/");
4207 name
+= STRING_SIZE("refs/remotes/");
4209 } else if (!strncmp(name
, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4210 namelen
-= STRING_SIZE("refs/heads/");
4211 name
+= STRING_SIZE("refs/heads/");
4213 } else if (!strcmp(name
, "HEAD")) {
4217 refs
= realloc(refs
, sizeof(*refs
) * (refs_size
+ 1));
4221 ref
= &refs
[refs_size
++];
4222 ref
->name
= malloc(namelen
+ 1);
4226 strncpy(ref
->name
, name
, namelen
);
4227 ref
->name
[namelen
] = 0;
4229 ref
->remote
= remote
;
4230 string_copy_rev(ref
->id
, id
);
4238 const char *cmd_env
= getenv("TIG_LS_REMOTE");
4239 const char *cmd
= cmd_env
&& *cmd_env ? cmd_env
: TIG_LS_REMOTE
;
4241 return read_properties(popen(cmd
, "r"), "\t", read_ref
);
4245 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
4247 if (!strcmp(name
, "i18n.commitencoding"))
4248 string_ncopy(opt_encoding
, value
, valuelen
);
4250 if (!strcmp(name
, "core.editor"))
4251 string_ncopy(opt_editor
, value
, valuelen
);
4257 load_repo_config(void)
4259 return read_properties(popen("git repo-config --list", "r"),
4260 "=", read_repo_config_option
);
4264 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
4266 if (!opt_git_dir
[0])
4267 string_ncopy(opt_git_dir
, name
, namelen
);
4269 string_ncopy(opt_cdup
, name
, namelen
);
4273 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4274 * must be the last one! */
4276 load_repo_info(void)
4278 return read_properties(popen("git rev-parse --git-dir --show-cdup 2>/dev/null", "r"),
4279 "=", read_repo_info
);
4283 read_properties(FILE *pipe
, const char *separators
,
4284 int (*read_property
)(char *, size_t, char *, size_t))
4286 char buffer
[BUFSIZ
];
4293 while (state
== OK
&& (name
= fgets(buffer
, sizeof(buffer
), pipe
))) {
4298 name
= chomp_string(name
);
4299 namelen
= strcspn(name
, separators
);
4301 if (name
[namelen
]) {
4303 value
= chomp_string(name
+ namelen
+ 1);
4304 valuelen
= strlen(value
);
4311 state
= read_property(name
, namelen
, value
, valuelen
);
4314 if (state
!= ERR
&& ferror(pipe
))
4327 static void __NORETURN
4330 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4336 static void __NORETURN
4337 die(const char *err
, ...)
4343 va_start(args
, err
);
4344 fputs("tig: ", stderr
);
4345 vfprintf(stderr
, err
, args
);
4346 fputs("\n", stderr
);
4353 main(int argc
, char *argv
[])
4356 enum request request
;
4359 signal(SIGINT
, quit
);
4361 if (setlocale(LC_ALL
, "")) {
4362 char *codeset
= nl_langinfo(CODESET
);
4364 string_ncopy(opt_codeset
, codeset
, strlen(codeset
));
4367 if (load_repo_info() == ERR
)
4368 die("Failed to load repo info.");
4370 /* Require a git repository unless when running in pager mode. */
4371 if (!opt_git_dir
[0])
4372 die("Not a git repository");
4374 if (load_options() == ERR
)
4375 die("Failed to load user config.");
4377 /* Load the repo config file so options can be overwritten from
4378 * the command line. */
4379 if (load_repo_config() == ERR
)
4380 die("Failed to load repo config.");
4382 if (!parse_options(argc
, argv
))
4385 if (*opt_codeset
&& strcmp(opt_codeset
, opt_encoding
)) {
4386 opt_iconv
= iconv_open(opt_codeset
, opt_encoding
);
4387 if (opt_iconv
== ICONV_NONE
)
4388 die("Failed to initialize character set conversion");
4391 if (load_refs() == ERR
)
4392 die("Failed to load refs.");
4394 for (i
= 0; i
< ARRAY_SIZE(views
) && (view
= &views
[i
]); i
++)
4395 view
->cmd_env
= getenv(view
->cmd_env
);
4397 request
= opt_request
;
4401 while (view_driver(display
[current_view
], request
)) {
4405 foreach_view (view
, i
)
4408 /* Refresh, accept single keystroke of input */
4409 key
= wgetch(status_win
);
4411 /* wgetch() with nodelay() enabled returns ERR when there's no
4418 request
= get_keybinding(display
[current_view
]->keymap
, key
);
4420 /* Some low-level request handling. This keeps access to
4421 * status_win restricted. */
4425 char *cmd
= read_prompt(":");
4427 if (cmd
&& string_format(opt_cmd
, "git %s", cmd
)) {
4428 if (strncmp(cmd
, "show", 4) && isspace(cmd
[4])) {
4429 opt_request
= REQ_VIEW_DIFF
;
4431 opt_request
= REQ_VIEW_PAGER
;
4440 case REQ_SEARCH_BACK
:
4442 const char *prompt
= request
== REQ_SEARCH
4444 char *search
= read_prompt(prompt
);
4447 string_ncopy(opt_search
, search
, strlen(search
));
4452 case REQ_SCREEN_RESIZE
:
4456 getmaxyx(stdscr
, height
, width
);
4458 /* Resize the status view and let the view driver take
4459 * care of resizing the displayed views. */
4460 wresize(status_win
, 1, width
);
4461 mvwin(status_win
, height
- 1, 0);
4462 wrefresh(status_win
);