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.4.git"
33 #include <sys/types.h>
43 #define __NORETURN __attribute__((__noreturn__))
48 static void __NORETURN
die(const char *err
, ...);
49 static void report(const char *msg
, ...);
50 static int read_properties(FILE *pipe
, const char *separators
, int (*read
)(char *, int, char *, int));
51 static void set_nonblocking_input(bool loading
);
52 static size_t utf8_length(const char *string
, size_t max_width
, int *coloffset
, int *trimmed
);
54 #define ABS(x) ((x) >= 0 ? (x) : -(x))
55 #define MIN(x, y) ((x) < (y) ? (x) : (y))
57 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
58 #define STRING_SIZE(x) (sizeof(x) - 1)
60 #define SIZEOF_STR 1024 /* Default string size. */
61 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
62 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
64 /* This color name can be used to refer to the default term colors. */
65 #define COLOR_DEFAULT (-1)
67 #define ICONV_NONE ((iconv_t) -1)
69 /* The format and size of the date column in the main view. */
70 #define DATE_FORMAT "%Y-%m-%d %H:%M"
71 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
73 #define AUTHOR_COLS 20
75 /* The default interval between line numbers. */
76 #define NUMBER_INTERVAL 1
80 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
82 #define TIG_LS_REMOTE \
83 "git ls-remote . 2>/dev/null"
85 #define TIG_DIFF_CMD \
86 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
89 "git log --cc --stat -n100 %s 2>/dev/null"
91 #define TIG_MAIN_CMD \
92 "git log --topo-order --pretty=raw %s 2>/dev/null"
94 #define TIG_TREE_CMD \
97 #define TIG_BLOB_CMD \
98 "git cat-file blob %s"
100 /* XXX: Needs to be defined to the empty string. */
101 #define TIG_HELP_CMD ""
102 #define TIG_PAGER_CMD ""
104 /* Some ascii-shorthands fitted into the ncurses namespace. */
106 #define KEY_RETURN '\r'
111 char *name
; /* Ref name; tag or head names are shortened. */
112 char id
[41]; /* Commit SHA1 ID */
113 unsigned int tag
:1; /* Is it a tag? */
114 unsigned int next
:1; /* For ref lists: are there more refs? */
117 static struct ref
**get_refs(char *id
);
126 set_from_int_map(struct int_map
*map
, size_t map_size
,
127 int *value
, const char *name
, int namelen
)
132 for (i
= 0; i
< map_size
; i
++)
133 if (namelen
== map
[i
].namelen
&&
134 !strncasecmp(name
, map
[i
].name
, namelen
)) {
135 *value
= map
[i
].value
;
148 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
150 if (srclen
> dstlen
- 1)
153 strncpy(dst
, src
, srclen
);
157 /* Shorthands for safely copying into a fixed buffer. */
159 #define string_copy(dst, src) \
160 string_ncopy_do(dst, sizeof(dst), src, sizeof(dst))
162 #define string_ncopy(dst, src, srclen) \
163 string_ncopy_do(dst, sizeof(dst), src, srclen)
166 chomp_string(char *name
)
170 while (isspace(*name
))
173 namelen
= strlen(name
) - 1;
174 while (namelen
> 0 && isspace(name
[namelen
]))
181 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
184 size_t pos
= bufpos ?
*bufpos
: 0;
187 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
193 return pos
>= bufsize ? FALSE
: TRUE
;
196 #define string_format(buf, fmt, args...) \
197 string_nformat(buf, sizeof(buf), NULL, fmt, args)
199 #define string_format_from(buf, from, fmt, args...) \
200 string_nformat(buf, sizeof(buf), from, fmt, args)
203 string_enum_compare(const char *str1
, const char *str2
, int len
)
207 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
209 /* Diff-Header == DIFF_HEADER */
210 for (i
= 0; i
< len
; i
++) {
211 if (toupper(str1
[i
]) == toupper(str2
[i
]))
214 if (string_enum_sep(str1
[i
]) &&
215 string_enum_sep(str2
[i
]))
218 return str1
[i
] - str2
[i
];
226 * NOTE: The following is a slightly modified copy of the git project's shell
227 * quoting routines found in the quote.c file.
229 * Help to copy the thing properly quoted for the shell safety. any single
230 * quote is replaced with '\'', any exclamation point is replaced with '\!',
231 * and the whole thing is enclosed in a
234 * original sq_quote result
235 * name ==> name ==> 'name'
236 * a b ==> a b ==> 'a b'
237 * a'b ==> a'\''b ==> 'a'\''b'
238 * a!b ==> a'\!'b ==> 'a'\!'b'
242 sq_quote(char buf
[SIZEOF_STR
], size_t bufsize
, const char *src
)
246 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
249 while ((c
= *src
++)) {
250 if (c
== '\'' || c
== '!') {
270 /* XXX: Keep the view request first and in sync with views[]. */ \
271 REQ_GROUP("View switching") \
272 REQ_(VIEW_MAIN, "Show main view"), \
273 REQ_(VIEW_DIFF, "Show diff view"), \
274 REQ_(VIEW_LOG, "Show log view"), \
275 REQ_(VIEW_TREE, "Show tree view"), \
276 REQ_(VIEW_BLOB, "Show blob view"), \
277 REQ_(VIEW_HELP, "Show help page"), \
278 REQ_(VIEW_PAGER, "Show pager view"), \
280 REQ_GROUP("View manipulation") \
281 REQ_(ENTER, "Enter current line and scroll"), \
282 REQ_(NEXT, "Move to next"), \
283 REQ_(PREVIOUS, "Move to previous"), \
284 REQ_(VIEW_NEXT, "Move focus to next view"), \
285 REQ_(VIEW_CLOSE, "Close the current view"), \
286 REQ_(QUIT, "Close all views and quit"), \
288 REQ_GROUP("Cursor navigation") \
289 REQ_(MOVE_UP, "Move cursor one line up"), \
290 REQ_(MOVE_DOWN, "Move cursor one line down"), \
291 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
292 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
293 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
294 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
296 REQ_GROUP("Scrolling") \
297 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
298 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
299 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
300 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
302 REQ_GROUP("Searching") \
303 REQ_(SEARCH, "Search the view"), \
304 REQ_(SEARCH_BACK, "Search backwards in the view"), \
305 REQ_(FIND_NEXT, "Find next search match"), \
306 REQ_(FIND_PREV, "Find previous search match"), \
309 REQ_(NONE, "Do nothing"), \
310 REQ_(PROMPT, "Bring up the prompt"), \
311 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
312 REQ_(SCREEN_RESIZE, "Resize the screen"), \
313 REQ_(SHOW_VERSION, "Show version information"), \
314 REQ_(STOP_LOADING, "Stop all loading views"), \
315 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
316 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization")
319 /* User action requests. */
321 #define REQ_GROUP(help)
322 #define REQ_(req, help) REQ_##req
324 /* Offset all requests to avoid conflicts with ncurses getch values. */
325 REQ_OFFSET
= KEY_MAX
+ 1,
333 struct request_info
{
334 enum request request
;
340 static struct request_info req_info
[] = {
341 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
342 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
349 get_request(const char *name
)
351 int namelen
= strlen(name
);
354 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
355 if (req_info
[i
].namelen
== namelen
&&
356 !string_enum_compare(req_info
[i
].name
, name
, namelen
))
357 return req_info
[i
].request
;
367 static const char usage
[] =
368 VERSION
" (" __DATE__
")\n"
370 "Usage: tig [options]\n"
371 " or: tig [options] [--] [git log options]\n"
372 " or: tig [options] log [git log options]\n"
373 " or: tig [options] diff [git diff options]\n"
374 " or: tig [options] show [git show options]\n"
375 " or: tig [options] < [git command output]\n"
378 " -l Start up in log view\n"
379 " -d Start up in diff view\n"
380 " -n[I], --line-number[=I] Show line numbers with given interval\n"
381 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
382 " -- Mark end of tig options\n"
383 " -v, --version Show version and exit\n"
384 " -h, --help Show help message and exit\n";
386 /* Option and state variables. */
387 static bool opt_line_number
= FALSE
;
388 static bool opt_rev_graph
= TRUE
;
389 static int opt_num_interval
= NUMBER_INTERVAL
;
390 static int opt_tab_size
= TABSIZE
;
391 static enum request opt_request
= REQ_VIEW_MAIN
;
392 static char opt_cmd
[SIZEOF_STR
] = "";
393 static char opt_path
[SIZEOF_STR
] = "";
394 static FILE *opt_pipe
= NULL
;
395 static char opt_encoding
[20] = "UTF-8";
396 static bool opt_utf8
= TRUE
;
397 static char opt_codeset
[20] = "UTF-8";
398 static iconv_t opt_iconv
= ICONV_NONE
;
399 static char opt_search
[SIZEOF_STR
] = "";
407 check_option(char *opt
, char short_name
, char *name
, enum option_type type
, ...)
417 int namelen
= strlen(name
);
421 if (strncmp(opt
, name
, namelen
))
424 if (opt
[namelen
] == '=')
425 value
= opt
+ namelen
+ 1;
428 if (!short_name
|| opt
[1] != short_name
)
433 va_start(args
, type
);
434 if (type
== OPT_INT
) {
435 number
= va_arg(args
, int *);
437 *number
= atoi(value
);
444 /* Returns the index of log or diff command or -1 to exit. */
446 parse_options(int argc
, char *argv
[])
450 for (i
= 1; i
< argc
; i
++) {
453 if (!strcmp(opt
, "-l")) {
454 opt_request
= REQ_VIEW_LOG
;
458 if (!strcmp(opt
, "-d")) {
459 opt_request
= REQ_VIEW_DIFF
;
463 if (check_option(opt
, 'n', "line-number", OPT_INT
, &opt_num_interval
)) {
464 opt_line_number
= TRUE
;
468 if (check_option(opt
, 'b', "tab-size", OPT_INT
, &opt_tab_size
)) {
469 opt_tab_size
= MIN(opt_tab_size
, TABSIZE
);
473 if (check_option(opt
, 'v', "version", OPT_NONE
)) {
474 printf("tig version %s\n", VERSION
);
478 if (check_option(opt
, 'h', "help", OPT_NONE
)) {
483 if (!strcmp(opt
, "--")) {
488 if (!strcmp(opt
, "log") ||
489 !strcmp(opt
, "diff") ||
490 !strcmp(opt
, "show")) {
491 opt_request
= opt
[0] == 'l'
492 ? REQ_VIEW_LOG
: REQ_VIEW_DIFF
;
496 if (opt
[0] && opt
[0] != '-')
499 die("unknown option '%s'\n\n%s", opt
, usage
);
502 if (!isatty(STDIN_FILENO
)) {
503 opt_request
= REQ_VIEW_PAGER
;
506 } else if (i
< argc
) {
509 if (opt_request
== REQ_VIEW_MAIN
)
510 /* XXX: This is vulnerable to the user overriding
511 * options required for the main view parser. */
512 string_copy(opt_cmd
, "git log --stat --pretty=raw");
514 string_copy(opt_cmd
, "git");
515 buf_size
= strlen(opt_cmd
);
517 while (buf_size
< sizeof(opt_cmd
) && i
< argc
) {
518 opt_cmd
[buf_size
++] = ' ';
519 buf_size
= sq_quote(opt_cmd
, buf_size
, argv
[i
++]);
522 if (buf_size
>= sizeof(opt_cmd
))
523 die("command too long");
525 opt_cmd
[buf_size
] = 0;
529 if (*opt_encoding
&& strcasecmp(opt_encoding
, "UTF-8"))
537 * Line-oriented content detection.
541 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
542 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
543 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
544 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
545 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
546 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
547 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
548 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
549 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
550 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
551 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
552 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
553 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
554 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
555 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
556 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
557 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
558 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
559 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
560 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
561 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
562 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
563 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
564 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
565 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
566 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
567 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
568 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
569 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
570 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
571 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
572 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
573 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
574 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
575 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
576 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
577 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
578 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
579 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
580 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL)
583 #define LINE(type, line, fg, bg, attr) \
590 const char *name
; /* Option name. */
591 int namelen
; /* Size of option name. */
592 const char *line
; /* The start of line to match. */
593 int linelen
; /* Size of string to match. */
594 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
597 static struct line_info line_info
[] = {
598 #define LINE(type, line, fg, bg, attr) \
599 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
604 static enum line_type
605 get_line_type(char *line
)
607 int linelen
= strlen(line
);
610 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
611 /* Case insensitive search matches Signed-off-by lines better. */
612 if (linelen
>= line_info
[type
].linelen
&&
613 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
620 get_line_attr(enum line_type type
)
622 assert(type
< ARRAY_SIZE(line_info
));
623 return COLOR_PAIR(type
) | line_info
[type
].attr
;
626 static struct line_info
*
627 get_line_info(char *name
, int namelen
)
631 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
632 if (namelen
== line_info
[type
].namelen
&&
633 !string_enum_compare(line_info
[type
].name
, name
, namelen
))
634 return &line_info
[type
];
642 int default_bg
= COLOR_BLACK
;
643 int default_fg
= COLOR_WHITE
;
648 if (use_default_colors() != ERR
) {
653 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
654 struct line_info
*info
= &line_info
[type
];
655 int bg
= info
->bg
== COLOR_DEFAULT ? default_bg
: info
->bg
;
656 int fg
= info
->fg
== COLOR_DEFAULT ? default_fg
: info
->fg
;
658 init_pair(type
, fg
, bg
);
666 unsigned int selected
:1;
668 void *data
; /* User data */
678 enum request request
;
679 struct keybinding
*next
;
682 static struct keybinding default_keybindings
[] = {
684 { 'm', REQ_VIEW_MAIN
},
685 { 'd', REQ_VIEW_DIFF
},
686 { 'l', REQ_VIEW_LOG
},
687 { 't', REQ_VIEW_TREE
},
688 { 'f', REQ_VIEW_BLOB
},
689 { 'p', REQ_VIEW_PAGER
},
690 { 'h', REQ_VIEW_HELP
},
692 /* View manipulation */
693 { 'q', REQ_VIEW_CLOSE
},
694 { KEY_TAB
, REQ_VIEW_NEXT
},
695 { KEY_RETURN
, REQ_ENTER
},
696 { KEY_UP
, REQ_PREVIOUS
},
697 { KEY_DOWN
, REQ_NEXT
},
699 /* Cursor navigation */
700 { 'k', REQ_MOVE_UP
},
701 { 'j', REQ_MOVE_DOWN
},
702 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
703 { KEY_END
, REQ_MOVE_LAST_LINE
},
704 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
705 { ' ', REQ_MOVE_PAGE_DOWN
},
706 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
707 { 'b', REQ_MOVE_PAGE_UP
},
708 { '-', REQ_MOVE_PAGE_UP
},
711 { KEY_IC
, REQ_SCROLL_LINE_UP
},
712 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
713 { 'w', REQ_SCROLL_PAGE_UP
},
714 { 's', REQ_SCROLL_PAGE_DOWN
},
718 { '?', REQ_SEARCH_BACK
},
719 { 'n', REQ_FIND_NEXT
},
720 { 'N', REQ_FIND_PREV
},
724 { 'z', REQ_STOP_LOADING
},
725 { 'v', REQ_SHOW_VERSION
},
726 { 'r', REQ_SCREEN_REDRAW
},
727 { '.', REQ_TOGGLE_LINENO
},
728 { 'g', REQ_TOGGLE_REV_GRAPH
},
731 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
734 /* Using the ncurses SIGWINCH handler. */
735 { KEY_RESIZE
, REQ_SCREEN_RESIZE
},
738 #define KEYMAP_INFO \
749 #define KEYMAP_(name) KEYMAP_##name
754 static struct int_map keymap_table
[] = {
755 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
760 #define set_keymap(map, name) \
761 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
763 static struct keybinding
*keybindings
[ARRAY_SIZE(keymap_table
)];
766 add_keybinding(enum keymap keymap
, enum request request
, int key
)
768 struct keybinding
*keybinding
;
770 keybinding
= calloc(1, sizeof(*keybinding
));
772 die("Failed to allocate keybinding");
774 keybinding
->alias
= key
;
775 keybinding
->request
= request
;
776 keybinding
->next
= keybindings
[keymap
];
777 keybindings
[keymap
] = keybinding
;
780 /* Looks for a key binding first in the given map, then in the generic map, and
781 * lastly in the default keybindings. */
783 get_keybinding(enum keymap keymap
, int key
)
785 struct keybinding
*kbd
;
788 for (kbd
= keybindings
[keymap
]; kbd
; kbd
= kbd
->next
)
789 if (kbd
->alias
== key
)
792 for (kbd
= keybindings
[KEYMAP_GENERIC
]; kbd
; kbd
= kbd
->next
)
793 if (kbd
->alias
== key
)
796 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
797 if (default_keybindings
[i
].alias
== key
)
798 return default_keybindings
[i
].request
;
800 return (enum request
) key
;
809 static struct key key_table
[] = {
810 { "Enter", KEY_RETURN
},
812 { "Backspace", KEY_BACKSPACE
},
814 { "Escape", KEY_ESC
},
815 { "Left", KEY_LEFT
},
816 { "Right", KEY_RIGHT
},
818 { "Down", KEY_DOWN
},
819 { "Insert", KEY_IC
},
820 { "Delete", KEY_DC
},
822 { "Home", KEY_HOME
},
824 { "PageUp", KEY_PPAGE
},
825 { "PageDown", KEY_NPAGE
},
835 { "F10", KEY_F(10) },
836 { "F11", KEY_F(11) },
837 { "F12", KEY_F(12) },
841 get_key_value(const char *name
)
845 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
846 if (!strcasecmp(key_table
[i
].name
, name
))
847 return key_table
[i
].value
;
849 if (strlen(name
) == 1 && isprint(*name
))
856 get_key(enum request request
)
858 static char buf
[BUFSIZ
];
859 static char key_char
[] = "'X'";
866 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
867 struct keybinding
*keybinding
= &default_keybindings
[i
];
871 if (keybinding
->request
!= request
)
874 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
875 if (key_table
[key
].value
== keybinding
->alias
)
876 seq
= key_table
[key
].name
;
879 keybinding
->alias
< 127 &&
880 isprint(keybinding
->alias
)) {
881 key_char
[1] = (char) keybinding
->alias
;
888 if (!string_format_from(buf
, &pos
, "%s%s", sep
, seq
))
889 return "Too many keybindings!";
898 * User config file handling.
901 static struct int_map color_map
[] = {
902 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
914 #define set_color(color, name) \
915 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
917 static struct int_map attr_map
[] = {
918 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
928 #define set_attribute(attr, name) \
929 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
931 static int config_lineno
;
932 static bool config_errors
;
933 static char *config_msg
;
935 /* Wants: object fgcolor bgcolor [attr] */
937 option_color_command(int argc
, char *argv
[])
939 struct line_info
*info
;
941 if (argc
!= 3 && argc
!= 4) {
942 config_msg
= "Wrong number of arguments given to color command";
946 info
= get_line_info(argv
[0], strlen(argv
[0]));
948 config_msg
= "Unknown color name";
952 if (set_color(&info
->fg
, argv
[1]) == ERR
||
953 set_color(&info
->bg
, argv
[2]) == ERR
) {
954 config_msg
= "Unknown color";
958 if (argc
== 4 && set_attribute(&info
->attr
, argv
[3]) == ERR
) {
959 config_msg
= "Unknown attribute";
966 /* Wants: name = value */
968 option_set_command(int argc
, char *argv
[])
971 config_msg
= "Wrong number of arguments given to set command";
975 if (strcmp(argv
[1], "=")) {
976 config_msg
= "No value assigned";
980 if (!strcmp(argv
[0], "show-rev-graph")) {
981 opt_rev_graph
= (!strcmp(argv
[2], "1") ||
982 !strcmp(argv
[2], "true") ||
983 !strcmp(argv
[2], "yes"));
987 if (!strcmp(argv
[0], "line-number-interval")) {
988 opt_num_interval
= atoi(argv
[2]);
992 if (!strcmp(argv
[0], "tab-size")) {
993 opt_tab_size
= atoi(argv
[2]);
997 if (!strcmp(argv
[0], "commit-encoding")) {
999 int delimiter
= *arg
;
1002 switch (delimiter
) {
1005 for (arg
++, i
= 0; arg
[i
]; i
++)
1006 if (arg
[i
] == delimiter
) {
1011 string_copy(opt_encoding
, arg
);
1016 config_msg
= "Unknown variable name";
1020 /* Wants: mode request key */
1022 option_bind_command(int argc
, char *argv
[])
1024 enum request request
;
1029 config_msg
= "Wrong number of arguments given to bind command";
1033 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1034 config_msg
= "Unknown key map";
1038 key
= get_key_value(argv
[1]);
1040 config_msg
= "Unknown key";
1044 request
= get_request(argv
[2]);
1045 if (request
== REQ_UNKNOWN
) {
1046 config_msg
= "Unknown request name";
1050 add_keybinding(keymap
, request
, key
);
1056 set_option(char *opt
, char *value
)
1063 while (argc
< ARRAY_SIZE(argv
) && (valuelen
= strcspn(value
, " \t"))) {
1064 argv
[argc
++] = value
;
1071 while (isspace(*value
))
1075 if (!strcmp(opt
, "color"))
1076 return option_color_command(argc
, argv
);
1078 if (!strcmp(opt
, "set"))
1079 return option_set_command(argc
, argv
);
1081 if (!strcmp(opt
, "bind"))
1082 return option_bind_command(argc
, argv
);
1084 config_msg
= "Unknown option command";
1089 read_option(char *opt
, int optlen
, char *value
, int valuelen
)
1094 config_msg
= "Internal error";
1096 /* Check for comment markers, since read_properties() will
1097 * only ensure opt and value are split at first " \t". */
1098 optlen
= strcspn(opt
, "#");
1102 if (opt
[optlen
] != 0) {
1103 config_msg
= "No option value";
1107 /* Look for comment endings in the value. */
1108 int len
= strcspn(value
, "#");
1110 if (len
< valuelen
) {
1112 value
[valuelen
] = 0;
1115 status
= set_option(opt
, value
);
1118 if (status
== ERR
) {
1119 fprintf(stderr
, "Error on line %d, near '%.*s': %s\n",
1120 config_lineno
, optlen
, opt
, config_msg
);
1121 config_errors
= TRUE
;
1124 /* Always keep going if errors are encountered. */
1131 char *home
= getenv("HOME");
1132 char buf
[SIZEOF_STR
];
1136 config_errors
= FALSE
;
1138 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1141 /* It's ok that the file doesn't exist. */
1142 file
= fopen(buf
, "r");
1146 if (read_properties(file
, " \t", read_option
) == ERR
||
1147 config_errors
== TRUE
)
1148 fprintf(stderr
, "Errors while loading %s.\n", buf
);
1161 /* The display array of active views and the index of the current view. */
1162 static struct view
*display
[2];
1163 static unsigned int current_view
;
1165 #define foreach_displayed_view(view, i) \
1166 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1168 #define displayed_views() (display[1] != NULL ? 2 : 1)
1170 /* Current head and commit ID */
1171 static char ref_blob
[SIZEOF_REF
] = "";
1172 static char ref_commit
[SIZEOF_REF
] = "HEAD";
1173 static char ref_head
[SIZEOF_REF
] = "HEAD";
1176 const char *name
; /* View name */
1177 const char *cmd_fmt
; /* Default command line format */
1178 const char *cmd_env
; /* Command line set via environment */
1179 const char *id
; /* Points to either of ref_{head,commit,blob} */
1181 struct view_ops
*ops
; /* View operations */
1183 enum keymap keymap
; /* What keymap does this view have */
1185 char cmd
[SIZEOF_STR
]; /* Command buffer */
1186 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
1187 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
1189 int height
, width
; /* The width and height of the main window */
1190 WINDOW
*win
; /* The main window */
1191 WINDOW
*title
; /* The title window living below the main window */
1194 unsigned long offset
; /* Offset of the window top */
1195 unsigned long lineno
; /* Current line number */
1198 char grep
[SIZEOF_STR
]; /* Search string */
1199 regex_t
*regex
; /* Pre-compiled regex */
1201 /* If non-NULL, points to the view that opened this view. If this view
1202 * is closed tig will switch back to the parent view. */
1203 struct view
*parent
;
1206 unsigned long lines
; /* Total number of lines */
1207 struct line
*line
; /* Line index */
1208 unsigned long line_size
;/* Total number of allocated lines */
1209 unsigned int digits
; /* Number of digits in the lines member. */
1217 /* What type of content being displayed. Used in the title bar. */
1219 /* Draw one line; @lineno must be < view->height. */
1220 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
, bool selected
);
1221 /* Read one line; updates view->line. */
1222 bool (*read
)(struct view
*view
, char *data
);
1223 /* Depending on view, change display based on current line. */
1224 bool (*enter
)(struct view
*view
, struct line
*line
);
1225 /* Search for regex in a line. */
1226 bool (*grep
)(struct view
*view
, struct line
*line
);
1228 void (*select
)(struct view
*view
, struct line
*line
);
1231 static struct view_ops pager_ops
;
1232 static struct view_ops main_ops
;
1233 static struct view_ops tree_ops
;
1234 static struct view_ops blob_ops
;
1236 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1237 { name, cmd, #env, ref, ops, map}
1239 #define VIEW_(id, name, ops, ref) \
1240 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1243 static struct view views
[] = {
1244 VIEW_(MAIN
, "main", &main_ops
, ref_head
),
1245 VIEW_(DIFF
, "diff", &pager_ops
, ref_commit
),
1246 VIEW_(LOG
, "log", &pager_ops
, ref_head
),
1247 VIEW_(TREE
, "tree", &tree_ops
, ref_commit
),
1248 VIEW_(BLOB
, "blob", &blob_ops
, ref_blob
),
1249 VIEW_(HELP
, "help", &pager_ops
, "static"),
1250 VIEW_(PAGER
, "pager", &pager_ops
, "static"),
1253 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1255 #define foreach_view(view, i) \
1256 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1258 #define view_is_displayed(view) \
1259 (view == display[0] || view == display[1])
1262 draw_view_line(struct view
*view
, unsigned int lineno
)
1265 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
1267 assert(view_is_displayed(view
));
1269 if (view
->offset
+ lineno
>= view
->lines
)
1272 line
= &view
->line
[view
->offset
+ lineno
];
1275 line
->selected
= TRUE
;
1276 view
->ops
->select(view
, line
);
1277 } else if (line
->selected
) {
1278 line
->selected
= FALSE
;
1279 wmove(view
->win
, lineno
, 0);
1280 wclrtoeol(view
->win
);
1283 return view
->ops
->draw(view
, line
, lineno
, selected
);
1287 redraw_view_from(struct view
*view
, int lineno
)
1289 assert(0 <= lineno
&& lineno
< view
->height
);
1291 for (; lineno
< view
->height
; lineno
++) {
1292 if (!draw_view_line(view
, lineno
))
1296 redrawwin(view
->win
);
1297 wrefresh(view
->win
);
1301 redraw_view(struct view
*view
)
1304 redraw_view_from(view
, 0);
1309 update_view_title(struct view
*view
)
1311 assert(view_is_displayed(view
));
1313 if (view
== display
[current_view
])
1314 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
1316 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
1318 werase(view
->title
);
1319 wmove(view
->title
, 0, 0);
1322 wprintw(view
->title
, "[%s] %s", view
->name
, view
->ref
);
1324 wprintw(view
->title
, "[%s]", view
->name
);
1326 if (view
->lines
|| view
->pipe
) {
1327 unsigned int view_lines
= view
->offset
+ view
->height
;
1328 unsigned int lines
= view
->lines
1329 ?
MIN(view_lines
, view
->lines
) * 100 / view
->lines
1332 wprintw(view
->title
, " - %s %d of %d (%d%%)",
1340 time_t secs
= time(NULL
) - view
->start_time
;
1342 /* Three git seconds are a long time ... */
1344 wprintw(view
->title
, " %lds", secs
);
1347 wmove(view
->title
, 0, view
->width
- 1);
1348 wrefresh(view
->title
);
1352 resize_display(void)
1355 struct view
*base
= display
[0];
1356 struct view
*view
= display
[1] ? display
[1] : display
[0];
1358 /* Setup window dimensions */
1360 getmaxyx(stdscr
, base
->height
, base
->width
);
1362 /* Make room for the status window. */
1366 /* Horizontal split. */
1367 view
->width
= base
->width
;
1368 view
->height
= SCALE_SPLIT_VIEW(base
->height
);
1369 base
->height
-= view
->height
;
1371 /* Make room for the title bar. */
1375 /* Make room for the title bar. */
1380 foreach_displayed_view (view
, i
) {
1382 view
->win
= newwin(view
->height
, 0, offset
, 0);
1384 die("Failed to create %s view", view
->name
);
1386 scrollok(view
->win
, TRUE
);
1388 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
1390 die("Failed to create title window");
1393 wresize(view
->win
, view
->height
, view
->width
);
1394 mvwin(view
->win
, offset
, 0);
1395 mvwin(view
->title
, offset
+ view
->height
, 0);
1398 offset
+= view
->height
+ 1;
1403 redraw_display(void)
1408 foreach_displayed_view (view
, i
) {
1410 update_view_title(view
);
1415 update_display_cursor(void)
1417 struct view
*view
= display
[current_view
];
1419 /* Move the cursor to the right-most column of the cursor line.
1421 * XXX: This could turn out to be a bit expensive, but it ensures that
1422 * the cursor does not jump around. */
1424 wmove(view
->win
, view
->lineno
- view
->offset
, view
->width
- 1);
1425 wrefresh(view
->win
);
1433 /* Scrolling backend */
1435 do_scroll_view(struct view
*view
, int lines
, bool redraw
)
1437 assert(view_is_displayed(view
));
1439 /* The rendering expects the new offset. */
1440 view
->offset
+= lines
;
1442 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
1445 /* Redraw the whole screen if scrolling is pointless. */
1446 if (view
->height
< ABS(lines
)) {
1450 int line
= lines
> 0 ? view
->height
- lines
: 0;
1451 int end
= line
+ ABS(lines
);
1453 wscrl(view
->win
, lines
);
1455 for (; line
< end
; line
++) {
1456 if (!draw_view_line(view
, line
))
1461 /* Move current line into the view. */
1462 if (view
->lineno
< view
->offset
) {
1463 view
->lineno
= view
->offset
;
1464 draw_view_line(view
, 0);
1466 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
1467 view
->lineno
= view
->offset
+ view
->height
- 1;
1468 draw_view_line(view
, view
->lineno
- view
->offset
);
1471 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
1476 redrawwin(view
->win
);
1477 wrefresh(view
->win
);
1481 /* Scroll frontend */
1483 scroll_view(struct view
*view
, enum request request
)
1488 case REQ_SCROLL_PAGE_DOWN
:
1489 lines
= view
->height
;
1490 case REQ_SCROLL_LINE_DOWN
:
1491 if (view
->offset
+ lines
> view
->lines
)
1492 lines
= view
->lines
- view
->offset
;
1494 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
1495 report("Cannot scroll beyond the last line");
1500 case REQ_SCROLL_PAGE_UP
:
1501 lines
= view
->height
;
1502 case REQ_SCROLL_LINE_UP
:
1503 if (lines
> view
->offset
)
1504 lines
= view
->offset
;
1507 report("Cannot scroll beyond the first line");
1515 die("request %d not handled in switch", request
);
1518 do_scroll_view(view
, lines
, TRUE
);
1523 move_view(struct view
*view
, enum request request
, bool redraw
)
1528 case REQ_MOVE_FIRST_LINE
:
1529 steps
= -view
->lineno
;
1532 case REQ_MOVE_LAST_LINE
:
1533 steps
= view
->lines
- view
->lineno
- 1;
1536 case REQ_MOVE_PAGE_UP
:
1537 steps
= view
->height
> view
->lineno
1538 ?
-view
->lineno
: -view
->height
;
1541 case REQ_MOVE_PAGE_DOWN
:
1542 steps
= view
->lineno
+ view
->height
>= view
->lines
1543 ? view
->lines
- view
->lineno
- 1 : view
->height
;
1555 die("request %d not handled in switch", request
);
1558 if (steps
<= 0 && view
->lineno
== 0) {
1559 report("Cannot move beyond the first line");
1562 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
1563 report("Cannot move beyond the last line");
1567 /* Move the current line */
1568 view
->lineno
+= steps
;
1569 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
1571 /* Repaint the old "current" line if we be scrolling */
1572 if (ABS(steps
) < view
->height
)
1573 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
1575 /* Check whether the view needs to be scrolled */
1576 if (view
->lineno
< view
->offset
||
1577 view
->lineno
>= view
->offset
+ view
->height
) {
1578 if (steps
< 0 && -steps
> view
->offset
) {
1579 steps
= -view
->offset
;
1581 } else if (steps
> 0) {
1582 if (view
->lineno
== view
->lines
- 1 &&
1583 view
->lines
> view
->height
) {
1584 steps
= view
->lines
- view
->offset
- 1;
1585 if (steps
>= view
->height
)
1586 steps
-= view
->height
- 1;
1590 do_scroll_view(view
, steps
, redraw
);
1594 /* Draw the current line */
1595 draw_view_line(view
, view
->lineno
- view
->offset
);
1600 redrawwin(view
->win
);
1601 wrefresh(view
->win
);
1610 static void search_view(struct view
*view
, enum request request
, const char *search
);
1613 find_next_line(struct view
*view
, unsigned long lineno
, struct line
*line
)
1615 assert(view_is_displayed(view
));
1617 if (!view
->ops
->grep(view
, line
))
1620 if (lineno
- view
->offset
>= view
->height
) {
1621 view
->offset
= lineno
;
1622 view
->lineno
= lineno
;
1626 unsigned long old_lineno
= view
->lineno
- view
->offset
;
1628 view
->lineno
= lineno
;
1629 draw_view_line(view
, old_lineno
);
1631 draw_view_line(view
, view
->lineno
- view
->offset
);
1632 redrawwin(view
->win
);
1633 wrefresh(view
->win
);
1636 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
1641 find_next(struct view
*view
, enum request request
)
1643 unsigned long lineno
= view
->lineno
;
1648 report("No previous search");
1650 search_view(view
, request
, opt_search
);
1660 case REQ_SEARCH_BACK
:
1669 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
1670 lineno
+= direction
;
1672 /* Note, lineno is unsigned long so will wrap around in which case it
1673 * will become bigger than view->lines. */
1674 for (; lineno
< view
->lines
; lineno
+= direction
) {
1675 struct line
*line
= &view
->line
[lineno
];
1677 if (find_next_line(view
, lineno
, line
))
1681 report("No match found for '%s'", view
->grep
);
1685 search_view(struct view
*view
, enum request request
, const char *search
)
1690 regfree(view
->regex
);
1693 view
->regex
= calloc(1, sizeof(*view
->regex
));
1698 regex_err
= regcomp(view
->regex
, search
, REG_EXTENDED
);
1699 if (regex_err
!= 0) {
1700 char buf
[SIZEOF_STR
] = "unknown error";
1702 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
1703 report("Search failed: %s", buf
);
1707 string_copy(view
->grep
, search
);
1709 find_next(view
, request
);
1713 * Incremental updating
1717 end_update(struct view
*view
)
1721 set_nonblocking_input(FALSE
);
1722 if (view
->pipe
== stdin
)
1730 begin_update(struct view
*view
)
1732 const char *id
= view
->id
;
1738 string_copy(view
->cmd
, opt_cmd
);
1740 /* When running random commands, the view ref could have become
1741 * invalid so clear it. */
1744 } else if (view
== VIEW(REQ_VIEW_TREE
)) {
1745 const char *format
= view
->cmd_env ? view
->cmd_env
: view
->cmd_fmt
;
1747 if (strcmp(view
->vid
, view
->id
))
1750 if (!string_format(view
->cmd
, format
, id
, opt_path
))
1754 const char *format
= view
->cmd_env ? view
->cmd_env
: view
->cmd_fmt
;
1756 if (!string_format(view
->cmd
, format
, id
, id
, id
, id
, id
))
1760 /* Special case for the pager view. */
1762 view
->pipe
= opt_pipe
;
1765 view
->pipe
= popen(view
->cmd
, "r");
1771 set_nonblocking_input(TRUE
);
1776 string_copy(view
->vid
, id
);
1781 for (i
= 0; i
< view
->lines
; i
++)
1782 if (view
->line
[i
].data
)
1783 free(view
->line
[i
].data
);
1789 view
->start_time
= time(NULL
);
1794 static struct line
*
1795 realloc_lines(struct view
*view
, size_t line_size
)
1797 struct line
*tmp
= realloc(view
->line
, sizeof(*view
->line
) * line_size
);
1803 view
->line_size
= line_size
;
1808 update_view(struct view
*view
)
1810 char in_buffer
[BUFSIZ
];
1811 char out_buffer
[BUFSIZ
* 2];
1813 /* The number of lines to read. If too low it will cause too much
1814 * redrawing (and possible flickering), if too high responsiveness
1816 unsigned long lines
= view
->height
;
1817 int redraw_from
= -1;
1822 /* Only redraw if lines are visible. */
1823 if (view
->offset
+ view
->height
>= view
->lines
)
1824 redraw_from
= view
->lines
- view
->offset
;
1826 /* FIXME: This is probably not perfect for backgrounded views. */
1827 if (!realloc_lines(view
, view
->lines
+ lines
))
1830 while ((line
= fgets(in_buffer
, sizeof(in_buffer
), view
->pipe
))) {
1831 size_t linelen
= strlen(line
);
1834 line
[linelen
- 1] = 0;
1836 if (opt_iconv
!= ICONV_NONE
) {
1838 size_t inlen
= linelen
;
1840 char *outbuf
= out_buffer
;
1841 size_t outlen
= sizeof(out_buffer
);
1845 ret
= iconv(opt_iconv
, &inbuf
, &inlen
, &outbuf
, &outlen
);
1846 if (ret
!= (size_t) -1) {
1848 linelen
= strlen(out_buffer
);
1852 if (!view
->ops
->read(view
, line
))
1862 lines
= view
->lines
;
1863 for (digits
= 0; lines
; digits
++)
1866 /* Keep the displayed view in sync with line number scaling. */
1867 if (digits
!= view
->digits
) {
1868 view
->digits
= digits
;
1873 if (!view_is_displayed(view
))
1876 if (view
== VIEW(REQ_VIEW_TREE
)) {
1877 /* Clear the view and redraw everything since the tree sorting
1878 * might have rearranged things. */
1881 } else if (redraw_from
>= 0) {
1882 /* If this is an incremental update, redraw the previous line
1883 * since for commits some members could have changed when
1884 * loading the main view. */
1885 if (redraw_from
> 0)
1888 /* Incrementally draw avoids flickering. */
1889 redraw_view_from(view
, redraw_from
);
1892 /* Update the title _after_ the redraw so that if the redraw picks up a
1893 * commit reference in view->ref it'll be available here. */
1894 update_view_title(view
);
1897 if (ferror(view
->pipe
)) {
1898 report("Failed to read: %s", strerror(errno
));
1901 } else if (feof(view
->pipe
)) {
1909 report("Allocation failure");
1921 static void open_help_view(struct view
*view
)
1924 int lines
= ARRAY_SIZE(req_info
) + 2;
1927 if (view
->lines
> 0)
1930 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1931 if (!req_info
[i
].request
)
1934 view
->line
= calloc(lines
, sizeof(*view
->line
));
1936 report("Allocation failure");
1940 view
->ops
->read(view
, "Quick reference for tig keybindings:");
1942 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
1945 if (!req_info
[i
].request
) {
1946 view
->ops
->read(view
, "");
1947 view
->ops
->read(view
, req_info
[i
].help
);
1951 key
= get_key(req_info
[i
].request
);
1952 if (!string_format(buf
, "%-25s %s", key
, req_info
[i
].help
))
1955 view
->ops
->read(view
, buf
);
1960 OPEN_DEFAULT
= 0, /* Use default view switching. */
1961 OPEN_SPLIT
= 1, /* Split current view. */
1962 OPEN_BACKGROUNDED
= 2, /* Backgrounded. */
1963 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
1967 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
1969 bool backgrounded
= !!(flags
& OPEN_BACKGROUNDED
);
1970 bool split
= !!(flags
& OPEN_SPLIT
);
1971 bool reload
= !!(flags
& OPEN_RELOAD
);
1972 struct view
*view
= VIEW(request
);
1973 int nviews
= displayed_views();
1974 struct view
*base_view
= display
[0];
1976 if (view
== prev
&& nviews
== 1 && !reload
) {
1977 report("Already in %s view", view
->name
);
1981 if (view
== VIEW(REQ_VIEW_HELP
)) {
1982 open_help_view(view
);
1984 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
1985 !begin_update(view
)) {
1986 report("Failed to load %s view", view
->name
);
1995 /* Maximize the current view. */
1996 memset(display
, 0, sizeof(display
));
1998 display
[current_view
] = view
;
2001 /* Resize the view when switching between split- and full-screen,
2002 * or when switching between two different full-screen views. */
2003 if (nviews
!= displayed_views() ||
2004 (nviews
== 1 && base_view
!= display
[0]))
2007 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
2008 /* Take the title line into account. */
2009 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
2011 /* Scroll the view that was split if the current line is
2012 * outside the new limited view. */
2013 do_scroll_view(prev
, lines
, TRUE
);
2016 if (prev
&& view
!= prev
) {
2017 if (split
&& !backgrounded
) {
2018 /* "Blur" the previous view. */
2019 update_view_title(prev
);
2022 view
->parent
= prev
;
2025 if (view
->pipe
&& view
->lines
== 0) {
2026 /* Clear the old view and let the incremental updating refill
2035 /* If the view is backgrounded the above calls to report()
2036 * won't redraw the view title. */
2038 update_view_title(view
);
2043 * User request switch noodle
2047 view_driver(struct view
*view
, enum request request
)
2054 case REQ_MOVE_PAGE_UP
:
2055 case REQ_MOVE_PAGE_DOWN
:
2056 case REQ_MOVE_FIRST_LINE
:
2057 case REQ_MOVE_LAST_LINE
:
2058 move_view(view
, request
, TRUE
);
2061 case REQ_SCROLL_LINE_DOWN
:
2062 case REQ_SCROLL_LINE_UP
:
2063 case REQ_SCROLL_PAGE_DOWN
:
2064 case REQ_SCROLL_PAGE_UP
:
2065 scroll_view(view
, request
);
2070 report("No file chosen, press 't' to open tree view");
2079 case REQ_VIEW_PAGER
:
2080 open_view(view
, request
, OPEN_DEFAULT
);
2085 request
= request
== REQ_NEXT ? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
2087 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
2088 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
2089 (view
== VIEW(REQ_VIEW_BLOB
) &&
2090 view
->parent
== VIEW(REQ_VIEW_TREE
))) {
2091 bool redraw
= display
[1] == view
;
2093 view
= view
->parent
;
2094 move_view(view
, request
, redraw
);
2096 update_view_title(view
);
2098 move_view(view
, request
, TRUE
);
2105 report("Nothing to enter");
2108 return view
->ops
->enter(view
, &view
->line
[view
->lineno
]);
2112 int nviews
= displayed_views();
2113 int next_view
= (current_view
+ 1) % nviews
;
2115 if (next_view
== current_view
) {
2116 report("Only one view is displayed");
2120 current_view
= next_view
;
2121 /* Blur out the title of the previous view. */
2122 update_view_title(view
);
2126 case REQ_TOGGLE_LINENO
:
2127 opt_line_number
= !opt_line_number
;
2131 case REQ_TOGGLE_REV_GRAPH
:
2132 opt_rev_graph
= !opt_rev_graph
;
2137 /* Always reload^Wrerun commands from the prompt. */
2138 open_view(view
, opt_request
, OPEN_RELOAD
);
2142 case REQ_SEARCH_BACK
:
2143 search_view(view
, request
, opt_search
);
2148 find_next(view
, request
);
2151 case REQ_STOP_LOADING
:
2152 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
2155 report("Stopped loading the %s view", view
->name
),
2160 case REQ_SHOW_VERSION
:
2161 report("%s (built %s)", VERSION
, __DATE__
);
2164 case REQ_SCREEN_RESIZE
:
2167 case REQ_SCREEN_REDRAW
:
2175 case REQ_VIEW_CLOSE
:
2176 /* XXX: Mark closed views by letting view->parent point to the
2177 * view itself. Parents to closed view should never be
2180 view
->parent
->parent
!= view
->parent
) {
2181 memset(display
, 0, sizeof(display
));
2183 display
[current_view
] = view
->parent
;
2184 view
->parent
= view
;
2194 /* An unknown key will show most commonly used commands. */
2195 report("Unknown key, press 'h' for help");
2208 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
, bool selected
)
2210 char *text
= line
->data
;
2211 enum line_type type
= line
->type
;
2212 int textlen
= strlen(text
);
2215 wmove(view
->win
, lineno
, 0);
2219 wchgat(view
->win
, -1, 0, type
, NULL
);
2222 attr
= get_line_attr(type
);
2223 wattrset(view
->win
, attr
);
2225 if (opt_line_number
|| opt_tab_size
< TABSIZE
) {
2226 static char spaces
[] = " ";
2227 int col_offset
= 0, col
= 0;
2229 if (opt_line_number
) {
2230 unsigned long real_lineno
= view
->offset
+ lineno
+ 1;
2232 if (real_lineno
== 1 ||
2233 (real_lineno
% opt_num_interval
) == 0) {
2234 wprintw(view
->win
, "%.*d", view
->digits
, real_lineno
);
2237 waddnstr(view
->win
, spaces
,
2238 MIN(view
->digits
, STRING_SIZE(spaces
)));
2240 waddstr(view
->win
, ": ");
2241 col_offset
= view
->digits
+ 2;
2244 while (text
&& col_offset
+ col
< view
->width
) {
2245 int cols_max
= view
->width
- col_offset
- col
;
2249 if (*text
== '\t') {
2251 assert(sizeof(spaces
) > TABSIZE
);
2253 cols
= opt_tab_size
- (col
% opt_tab_size
);
2256 text
= strchr(text
, '\t');
2257 cols
= line ? text
- pos
: strlen(pos
);
2260 waddnstr(view
->win
, pos
, MIN(cols
, cols_max
));
2265 int col
= 0, pos
= 0;
2267 for (; pos
< textlen
&& col
< view
->width
; pos
++, col
++)
2268 if (text
[pos
] == '\t')
2269 col
+= TABSIZE
- (col
% TABSIZE
) - 1;
2271 waddnstr(view
->win
, text
, pos
);
2278 add_describe_ref(char *buf
, size_t *bufpos
, char *commit_id
, const char *sep
)
2280 char refbuf
[SIZEOF_STR
];
2284 if (!string_format(refbuf
, "git describe %s", commit_id
))
2287 pipe
= popen(refbuf
, "r");
2291 if ((ref
= fgets(refbuf
, sizeof(refbuf
), pipe
)))
2292 ref
= chomp_string(ref
);
2298 /* This is the only fatal call, since it can "corrupt" the buffer. */
2299 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
2306 add_pager_refs(struct view
*view
, struct line
*line
)
2308 char buf
[SIZEOF_STR
];
2309 char *commit_id
= line
->data
+ STRING_SIZE("commit ");
2311 size_t bufpos
= 0, refpos
= 0;
2312 const char *sep
= "Refs: ";
2313 bool is_tag
= FALSE
;
2315 assert(line
->type
== LINE_COMMIT
);
2317 refs
= get_refs(commit_id
);
2319 if (view
== VIEW(REQ_VIEW_DIFF
))
2320 goto try_add_describe_ref
;
2325 struct ref
*ref
= refs
[refpos
];
2326 char *fmt
= ref
->tag ?
"%s[%s]" : "%s%s";
2328 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
2333 } while (refs
[refpos
++]->next
);
2335 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
2336 try_add_describe_ref
:
2337 /* Add <tag>-g<commit_id> "fake" reference. */
2338 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
2345 if (!realloc_lines(view
, view
->line_size
+ 1))
2348 line
= &view
->line
[view
->lines
];
2349 line
->data
= strdup(buf
);
2353 line
->type
= LINE_PP_REFS
;
2358 pager_read(struct view
*view
, char *data
)
2360 struct line
*line
= &view
->line
[view
->lines
];
2362 line
->data
= strdup(data
);
2366 line
->type
= get_line_type(line
->data
);
2369 if (line
->type
== LINE_COMMIT
&&
2370 (view
== VIEW(REQ_VIEW_DIFF
) ||
2371 view
== VIEW(REQ_VIEW_LOG
)))
2372 add_pager_refs(view
, line
);
2378 pager_enter(struct view
*view
, struct line
*line
)
2382 if (line
->type
== LINE_COMMIT
&&
2383 (view
== VIEW(REQ_VIEW_LOG
) ||
2384 view
== VIEW(REQ_VIEW_PAGER
))) {
2385 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
2389 /* Always scroll the view even if it was split. That way
2390 * you can use Enter to scroll through the log view and
2391 * split open each commit diff. */
2392 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
2394 /* FIXME: A minor workaround. Scrolling the view will call report("")
2395 * but if we are scrolling a non-current view this won't properly
2396 * update the view title. */
2398 update_view_title(view
);
2404 pager_grep(struct view
*view
, struct line
*line
)
2407 char *text
= line
->data
;
2412 if (regexec(view
->regex
, text
, 1, &pmatch
, 0) == REG_NOMATCH
)
2419 pager_select(struct view
*view
, struct line
*line
)
2421 if (line
->type
== LINE_COMMIT
) {
2422 char *text
= line
->data
;
2424 string_copy(view
->ref
, text
+ STRING_SIZE("commit "));
2425 string_copy(ref_commit
, view
->ref
);
2429 static struct view_ops pager_ops
= {
2443 /* Parse output from git ls-tree:
2445 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2446 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2447 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2448 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2451 #define SIZEOF_TREE_ATTR \
2452 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2454 #define TREE_UP_FORMAT "040000 tree %s\t.."
2457 tree_compare_entry(enum line_type type1
, char *name1
,
2458 enum line_type type2
, char *name2
)
2460 if (type1
!= type2
) {
2461 if (type1
== LINE_TREE_DIR
)
2466 return strcmp(name1
, name2
);
2470 tree_read(struct view
*view
, char *text
)
2472 size_t textlen
= strlen(text
);
2473 char buf
[SIZEOF_STR
];
2475 enum line_type type
;
2476 bool first_read
= view
->lines
== 0;
2478 if (textlen
<= SIZEOF_TREE_ATTR
)
2481 type
= text
[STRING_SIZE("100644 ")] == 't'
2482 ? LINE_TREE_DIR
: LINE_TREE_FILE
;
2485 /* Add path info line */
2486 if (string_format(buf
, "Directory path /%s", opt_path
) &&
2487 realloc_lines(view
, view
->line_size
+ 1) &&
2488 pager_read(view
, buf
))
2489 view
->line
[view
->lines
- 1].type
= LINE_DEFAULT
;
2493 /* Insert "link" to parent directory. */
2495 string_format(buf
, TREE_UP_FORMAT
, view
->ref
) &&
2496 realloc_lines(view
, view
->line_size
+ 1) &&
2497 pager_read(view
, buf
))
2498 view
->line
[view
->lines
- 1].type
= LINE_TREE_DIR
;
2503 /* Strip the path part ... */
2505 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
2506 size_t striplen
= strlen(opt_path
);
2507 char *path
= text
+ SIZEOF_TREE_ATTR
;
2509 if (pathlen
> striplen
)
2510 memmove(path
, path
+ striplen
,
2511 pathlen
- striplen
+ 1);
2514 /* Skip "Directory ..." and ".." line. */
2515 for (pos
= 1 + !!*opt_path
; pos
< view
->lines
; pos
++) {
2516 struct line
*line
= &view
->line
[pos
];
2517 char *path1
= ((char *) line
->data
) + SIZEOF_TREE_ATTR
;
2518 char *path2
= text
+ SIZEOF_TREE_ATTR
;
2519 int cmp
= tree_compare_entry(line
->type
, path1
, type
, path2
);
2524 text
= strdup(text
);
2528 if (view
->lines
> pos
)
2529 memmove(&view
->line
[pos
+ 1], &view
->line
[pos
],
2530 (view
->lines
- pos
) * sizeof(*line
));
2532 line
= &view
->line
[pos
];
2539 if (!pager_read(view
, text
))
2542 /* Move the current line to the first tree entry. */
2546 view
->line
[view
->lines
- 1].type
= type
;
2551 tree_enter(struct view
*view
, struct line
*line
)
2553 enum open_flags flags
= OPEN_DEFAULT
;
2554 char *data
= line
->data
;
2555 enum request request
;
2557 switch (line
->type
) {
2559 /* Depending on whether it is a subdir or parent (updir?) link
2560 * mangle the path buffer. */
2561 if (line
== &view
->line
[1] && *opt_path
) {
2562 size_t path_len
= strlen(opt_path
);
2563 char *dirsep
= opt_path
+ path_len
- 1;
2565 while (dirsep
> opt_path
&& dirsep
[-1] != '/')
2571 size_t pathlen
= strlen(opt_path
);
2572 size_t origlen
= pathlen
;
2573 char *basename
= data
+ SIZEOF_TREE_ATTR
;
2575 if (!string_format_from(opt_path
, &pathlen
, "%s/", basename
)) {
2576 opt_path
[origlen
] = 0;
2581 /* Trees and subtrees share the same ID, so they are not not
2582 * unique like blobs. */
2583 flags
|= OPEN_RELOAD
;
2584 request
= REQ_VIEW_TREE
;
2587 case LINE_TREE_FILE
:
2588 /* This causes the blob view to become split, and not having it
2589 * in the tree dir case will make the blob view automatically
2590 * disappear when moving to a different directory. */
2591 flags
|= OPEN_SPLIT
;
2592 request
= REQ_VIEW_BLOB
;
2599 open_view(view
, request
, flags
);
2601 if (!VIEW(request
)->pipe
)
2604 /* For tree views insert the path to the parent as the first line. */
2605 if (request
== REQ_VIEW_BLOB
) {
2606 /* Mirror what is showed in the title bar. */
2607 string_ncopy(ref_blob
, data
+ STRING_SIZE("100644 blob "), 40);
2608 string_copy(VIEW(REQ_VIEW_BLOB
)->ref
, ref_blob
);
2616 tree_select(struct view
*view
, struct line
*line
)
2618 if (line
->type
== LINE_TREE_DIR
|| line
->type
== LINE_TREE_FILE
) {
2619 char *text
= line
->data
;
2621 string_ncopy(view
->ref
, text
+ STRING_SIZE("100644 blob "), 40);
2622 string_copy(ref_blob
, view
->ref
);
2626 static struct view_ops tree_ops
= {
2636 blob_read(struct view
*view
, char *line
)
2638 bool state
= pager_read(view
, line
);
2641 view
->line
[view
->lines
- 1].type
= LINE_DEFAULT
;
2646 static struct view_ops blob_ops
= {
2661 char id
[41]; /* SHA1 ID. */
2662 char title
[75]; /* First line of the commit message. */
2663 char author
[75]; /* Author of the commit. */
2664 struct tm time
; /* Date from the author ident. */
2665 struct ref
**refs
; /* Repository references. */
2666 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
2667 size_t graph_size
; /* The width of the graph array. */
2671 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
, bool selected
)
2673 char buf
[DATE_COLS
+ 1];
2674 struct commit
*commit
= line
->data
;
2675 enum line_type type
;
2681 if (!*commit
->author
)
2684 wmove(view
->win
, lineno
, col
);
2688 wattrset(view
->win
, get_line_attr(type
));
2689 wchgat(view
->win
, -1, 0, type
, NULL
);
2692 type
= LINE_MAIN_COMMIT
;
2693 wattrset(view
->win
, get_line_attr(LINE_MAIN_DATE
));
2696 timelen
= strftime(buf
, sizeof(buf
), DATE_FORMAT
, &commit
->time
);
2697 waddnstr(view
->win
, buf
, timelen
);
2698 waddstr(view
->win
, " ");
2701 wmove(view
->win
, lineno
, col
);
2702 if (type
!= LINE_CURSOR
)
2703 wattrset(view
->win
, get_line_attr(LINE_MAIN_AUTHOR
));
2706 authorlen
= utf8_length(commit
->author
, AUTHOR_COLS
- 2, &col
, &trimmed
);
2708 authorlen
= strlen(commit
->author
);
2709 if (authorlen
> AUTHOR_COLS
- 2) {
2710 authorlen
= AUTHOR_COLS
- 2;
2716 waddnstr(view
->win
, commit
->author
, authorlen
);
2717 if (type
!= LINE_CURSOR
)
2718 wattrset(view
->win
, get_line_attr(LINE_MAIN_DELIM
));
2719 waddch(view
->win
, '~');
2721 waddstr(view
->win
, commit
->author
);
2725 if (type
!= LINE_CURSOR
)
2726 wattrset(view
->win
, A_NORMAL
);
2728 if (opt_rev_graph
&& commit
->graph_size
) {
2731 wmove(view
->win
, lineno
, col
);
2732 /* Using waddch() instead of waddnstr() ensures that
2733 * they'll be rendered correctly for the cursor line. */
2734 for (i
= 0; i
< commit
->graph_size
; i
++)
2735 waddch(view
->win
, commit
->graph
[i
]);
2737 col
+= commit
->graph_size
+ 1;
2740 wmove(view
->win
, lineno
, col
);
2746 if (type
== LINE_CURSOR
)
2748 else if (commit
->refs
[i
]->tag
)
2749 wattrset(view
->win
, get_line_attr(LINE_MAIN_TAG
));
2751 wattrset(view
->win
, get_line_attr(LINE_MAIN_REF
));
2752 waddstr(view
->win
, "[");
2753 waddstr(view
->win
, commit
->refs
[i
]->name
);
2754 waddstr(view
->win
, "]");
2755 if (type
!= LINE_CURSOR
)
2756 wattrset(view
->win
, A_NORMAL
);
2757 waddstr(view
->win
, " ");
2758 col
+= strlen(commit
->refs
[i
]->name
) + STRING_SIZE("[] ");
2759 } while (commit
->refs
[i
++]->next
);
2762 if (type
!= LINE_CURSOR
)
2763 wattrset(view
->win
, get_line_attr(type
));
2766 int titlelen
= strlen(commit
->title
);
2768 if (col
+ titlelen
> view
->width
)
2769 titlelen
= view
->width
- col
;
2771 waddnstr(view
->win
, commit
->title
, titlelen
);
2777 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2779 main_read(struct view
*view
, char *line
)
2781 enum line_type type
= get_line_type(line
);
2782 struct commit
*commit
= view
->lines
2783 ? view
->line
[view
->lines
- 1].data
: NULL
;
2787 commit
= calloc(1, sizeof(struct commit
));
2791 line
+= STRING_SIZE("commit ");
2793 view
->line
[view
->lines
++].data
= commit
;
2794 string_copy(commit
->id
, line
);
2795 commit
->refs
= get_refs(commit
->id
);
2796 commit
->graph
[commit
->graph_size
++] = ACS_LTEE
;
2801 char *ident
= line
+ STRING_SIZE("author ");
2802 char *end
= strchr(ident
, '<');
2808 char *email
= end
+ 1;
2810 for (; end
> ident
&& isspace(end
[-1]); end
--) ;
2812 if (end
== ident
&& *email
) {
2814 end
= strchr(ident
, '>');
2815 for (; end
> ident
&& isspace(end
[-1]); end
--) ;
2820 /* End is NULL or ident meaning there's no author. */
2824 string_copy(commit
->author
, ident
);
2826 /* Parse epoch and timezone */
2828 char *secs
= strchr(end
+ 1, '>');
2832 if (!secs
|| secs
[1] != ' ')
2836 time
= (time_t) atol(secs
);
2837 zone
= strchr(secs
, ' ');
2838 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700")) {
2842 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
2843 tz
+= ('0' - zone
[2]) * 60 * 60;
2844 tz
+= ('0' - zone
[3]) * 60;
2845 tz
+= ('0' - zone
[4]) * 60;
2852 gmtime_r(&time
, &commit
->time
);
2860 /* Fill in the commit title if it has not already been set. */
2861 if (commit
->title
[0])
2864 /* Require titles to start with a non-space character at the
2865 * offset used by git log. */
2866 /* FIXME: More gracefull handling of titles; append "..." to
2867 * shortened titles, etc. */
2868 if (strncmp(line
, " ", 4) ||
2872 string_copy(commit
->title
, line
+ 4);
2879 main_enter(struct view
*view
, struct line
*line
)
2881 enum open_flags flags
= display
[0] == view ? OPEN_SPLIT
: OPEN_DEFAULT
;
2883 open_view(view
, REQ_VIEW_DIFF
, flags
);
2888 main_grep(struct view
*view
, struct line
*line
)
2890 struct commit
*commit
= line
->data
;
2891 enum { S_TITLE
, S_AUTHOR
, S_DATE
, S_END
} state
;
2892 char buf
[DATE_COLS
+ 1];
2895 for (state
= S_TITLE
; state
< S_END
; state
++) {
2899 case S_TITLE
: text
= commit
->title
; break;
2900 case S_AUTHOR
: text
= commit
->author
; break;
2902 if (!strftime(buf
, sizeof(buf
), DATE_FORMAT
, &commit
->time
))
2911 if (regexec(view
->regex
, text
, 1, &pmatch
, 0) != REG_NOMATCH
)
2919 main_select(struct view
*view
, struct line
*line
)
2921 struct commit
*commit
= line
->data
;
2923 string_copy(view
->ref
, commit
->id
);
2924 string_copy(ref_commit
, view
->ref
);
2927 static struct view_ops main_ops
= {
2938 * Unicode / UTF-8 handling
2940 * NOTE: Much of the following code for dealing with unicode is derived from
2941 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2942 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2945 /* I've (over)annotated a lot of code snippets because I am not entirely
2946 * confident that the approach taken by this small UTF-8 interface is correct.
2950 unicode_width(unsigned long c
)
2953 (c
<= 0x115f /* Hangul Jamo */
2956 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
2958 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
2959 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
2960 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
2961 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
2962 || (c
>= 0xffe0 && c
<= 0xffe6)
2963 || (c
>= 0x20000 && c
<= 0x2fffd)
2964 || (c
>= 0x30000 && c
<= 0x3fffd)))
2970 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2971 * Illegal bytes are set one. */
2972 static const unsigned char utf8_bytes
[256] = {
2973 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,
2974 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,
2975 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,
2976 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,
2977 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,
2978 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,
2979 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,
2980 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,
2983 /* Decode UTF-8 multi-byte representation into a unicode character. */
2984 static inline unsigned long
2985 utf8_to_unicode(const char *string
, size_t length
)
2987 unsigned long unicode
;
2991 unicode
= string
[0];
2994 unicode
= (string
[0] & 0x1f) << 6;
2995 unicode
+= (string
[1] & 0x3f);
2998 unicode
= (string
[0] & 0x0f) << 12;
2999 unicode
+= ((string
[1] & 0x3f) << 6);
3000 unicode
+= (string
[2] & 0x3f);
3003 unicode
= (string
[0] & 0x0f) << 18;
3004 unicode
+= ((string
[1] & 0x3f) << 12);
3005 unicode
+= ((string
[2] & 0x3f) << 6);
3006 unicode
+= (string
[3] & 0x3f);
3009 unicode
= (string
[0] & 0x0f) << 24;
3010 unicode
+= ((string
[1] & 0x3f) << 18);
3011 unicode
+= ((string
[2] & 0x3f) << 12);
3012 unicode
+= ((string
[3] & 0x3f) << 6);
3013 unicode
+= (string
[4] & 0x3f);
3016 unicode
= (string
[0] & 0x01) << 30;
3017 unicode
+= ((string
[1] & 0x3f) << 24);
3018 unicode
+= ((string
[2] & 0x3f) << 18);
3019 unicode
+= ((string
[3] & 0x3f) << 12);
3020 unicode
+= ((string
[4] & 0x3f) << 6);
3021 unicode
+= (string
[5] & 0x3f);
3024 die("Invalid unicode length");
3027 /* Invalid characters could return the special 0xfffd value but NUL
3028 * should be just as good. */
3029 return unicode
> 0xffff ?
0 : unicode
;
3032 /* Calculates how much of string can be shown within the given maximum width
3033 * and sets trimmed parameter to non-zero value if all of string could not be
3036 * Additionally, adds to coloffset how many many columns to move to align with
3037 * the expected position. Takes into account how multi-byte and double-width
3038 * characters will effect the cursor position.
3040 * Returns the number of bytes to output from string to satisfy max_width. */
3042 utf8_length(const char *string
, size_t max_width
, int *coloffset
, int *trimmed
)
3044 const char *start
= string
;
3045 const char *end
= strchr(string
, '\0');
3051 while (string
< end
) {
3052 int c
= *(unsigned char *) string
;
3053 unsigned char bytes
= utf8_bytes
[c
];
3055 unsigned long unicode
;
3057 if (string
+ bytes
> end
)
3060 /* Change representation to figure out whether
3061 * it is a single- or double-width character. */
3063 unicode
= utf8_to_unicode(string
, bytes
);
3064 /* FIXME: Graceful handling of invalid unicode character. */
3068 ucwidth
= unicode_width(unicode
);
3070 if (width
> max_width
) {
3075 /* The column offset collects the differences between the
3076 * number of bytes encoding a character and the number of
3077 * columns will be used for rendering said character.
3079 * So if some character A is encoded in 2 bytes, but will be
3080 * represented on the screen using only 1 byte this will and up
3081 * adding 1 to the multi-byte column offset.
3083 * Assumes that no double-width character can be encoding in
3084 * less than two bytes. */
3085 if (bytes
> ucwidth
)
3086 mbwidth
+= bytes
- ucwidth
;
3091 *coloffset
+= mbwidth
;
3093 return string
- start
;
3101 /* Whether or not the curses interface has been initialized. */
3102 static bool cursed
= FALSE
;
3104 /* The status window is used for polling keystrokes. */
3105 static WINDOW
*status_win
;
3107 /* Update status and title window. */
3109 report(const char *msg
, ...)
3111 static bool empty
= TRUE
;
3112 struct view
*view
= display
[current_view
];
3114 if (!empty
|| *msg
) {
3117 va_start(args
, msg
);
3120 wmove(status_win
, 0, 0);
3122 vwprintw(status_win
, msg
, args
);
3127 wrefresh(status_win
);
3132 update_view_title(view
);
3133 update_display_cursor();
3136 /* Controls when nodelay should be in effect when polling user input. */
3138 set_nonblocking_input(bool loading
)
3140 static unsigned int loading_views
;
3142 if ((loading
== FALSE
&& loading_views
-- == 1) ||
3143 (loading
== TRUE
&& loading_views
++ == 0))
3144 nodelay(status_win
, loading
);
3152 /* Initialize the curses library */
3153 if (isatty(STDIN_FILENO
)) {
3154 cursed
= !!initscr();
3156 /* Leave stdin and stdout alone when acting as a pager. */
3157 FILE *io
= fopen("/dev/tty", "r+");
3160 die("Failed to open /dev/tty");
3161 cursed
= !!newterm(NULL
, io
, io
);
3165 die("Failed to initialize curses");
3167 nonl(); /* Tell curses not to do NL->CR/NL on output */
3168 cbreak(); /* Take input chars one at a time, no wait for \n */
3169 noecho(); /* Don't echo input */
3170 leaveok(stdscr
, TRUE
);
3175 getmaxyx(stdscr
, y
, x
);
3176 status_win
= newwin(1, 0, y
- 1, 0);
3178 die("Failed to create status window");
3180 /* Enable keyboard mapping */
3181 keypad(status_win
, TRUE
);
3182 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
3186 read_prompt(const char *prompt
)
3188 enum { READING
, STOP
, CANCEL
} status
= READING
;
3189 static char buf
[sizeof(opt_cmd
) - STRING_SIZE("git \0")];
3192 while (status
== READING
) {
3196 foreach_view (view
, i
)
3199 report("%s%.*s", prompt
, pos
, buf
);
3200 /* Refresh, accept single keystroke of input */
3201 key
= wgetch(status_win
);
3206 status
= pos ? STOP
: CANCEL
;
3224 if (pos
>= sizeof(buf
)) {
3225 report("Input string too long");
3230 buf
[pos
++] = (char) key
;
3234 if (status
== CANCEL
) {
3235 /* Clear the status window */
3246 * Repository references
3249 static struct ref
*refs
;
3250 static size_t refs_size
;
3252 /* Id <-> ref store */
3253 static struct ref
***id_refs
;
3254 static size_t id_refs_size
;
3256 static struct ref
**
3259 struct ref
***tmp_id_refs
;
3260 struct ref
**ref_list
= NULL
;
3261 size_t ref_list_size
= 0;
3264 for (i
= 0; i
< id_refs_size
; i
++)
3265 if (!strcmp(id
, id_refs
[i
][0]->id
))
3268 tmp_id_refs
= realloc(id_refs
, (id_refs_size
+ 1) * sizeof(*id_refs
));
3272 id_refs
= tmp_id_refs
;
3274 for (i
= 0; i
< refs_size
; i
++) {
3277 if (strcmp(id
, refs
[i
].id
))
3280 tmp
= realloc(ref_list
, (ref_list_size
+ 1) * sizeof(*ref_list
));
3288 if (ref_list_size
> 0)
3289 ref_list
[ref_list_size
- 1]->next
= 1;
3290 ref_list
[ref_list_size
] = &refs
[i
];
3292 /* XXX: The properties of the commit chains ensures that we can
3293 * safely modify the shared ref. The repo references will
3294 * always be similar for the same id. */
3295 ref_list
[ref_list_size
]->next
= 0;
3300 id_refs
[id_refs_size
++] = ref_list
;
3306 read_ref(char *id
, int idlen
, char *name
, int namelen
)
3311 if (!strncmp(name
, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3312 /* Commits referenced by tags has "^{}" appended. */
3313 if (name
[namelen
- 1] != '}')
3316 while (namelen
> 0 && name
[namelen
] != '^')
3320 namelen
-= STRING_SIZE("refs/tags/");
3321 name
+= STRING_SIZE("refs/tags/");
3323 } else if (!strncmp(name
, "refs/heads/", STRING_SIZE("refs/heads/"))) {
3324 namelen
-= STRING_SIZE("refs/heads/");
3325 name
+= STRING_SIZE("refs/heads/");
3327 } else if (!strcmp(name
, "HEAD")) {
3331 refs
= realloc(refs
, sizeof(*refs
) * (refs_size
+ 1));
3335 ref
= &refs
[refs_size
++];
3336 ref
->name
= malloc(namelen
+ 1);
3340 strncpy(ref
->name
, name
, namelen
);
3341 ref
->name
[namelen
] = 0;
3343 string_copy(ref
->id
, id
);
3351 const char *cmd_env
= getenv("TIG_LS_REMOTE");
3352 const char *cmd
= cmd_env
&& *cmd_env ? cmd_env
: TIG_LS_REMOTE
;
3354 return read_properties(popen(cmd
, "r"), "\t", read_ref
);
3358 read_repo_config_option(char *name
, int namelen
, char *value
, int valuelen
)
3360 if (!strcmp(name
, "i18n.commitencoding"))
3361 string_copy(opt_encoding
, value
);
3367 load_repo_config(void)
3369 return read_properties(popen("git repo-config --list", "r"),
3370 "=", read_repo_config_option
);
3374 read_properties(FILE *pipe
, const char *separators
,
3375 int (*read_property
)(char *, int, char *, int))
3377 char buffer
[BUFSIZ
];
3384 while (state
== OK
&& (name
= fgets(buffer
, sizeof(buffer
), pipe
))) {
3389 name
= chomp_string(name
);
3390 namelen
= strcspn(name
, separators
);
3392 if (name
[namelen
]) {
3394 value
= chomp_string(name
+ namelen
+ 1);
3395 valuelen
= strlen(value
);
3402 state
= read_property(name
, namelen
, value
, valuelen
);
3405 if (state
!= ERR
&& ferror(pipe
))
3418 static void __NORETURN
3421 /* XXX: Restore tty modes and let the OS cleanup the rest! */
3427 static void __NORETURN
3428 die(const char *err
, ...)
3434 va_start(args
, err
);
3435 fputs("tig: ", stderr
);
3436 vfprintf(stderr
, err
, args
);
3437 fputs("\n", stderr
);
3444 main(int argc
, char *argv
[])
3447 enum request request
;
3450 signal(SIGINT
, quit
);
3452 if (setlocale(LC_ALL
, "")) {
3453 string_copy(opt_codeset
, nl_langinfo(CODESET
));
3456 if (load_options() == ERR
)
3457 die("Failed to load user config.");
3459 /* Load the repo config file so options can be overwritten from
3460 * the command line. */
3461 if (load_repo_config() == ERR
)
3462 die("Failed to load repo config.");
3464 if (!parse_options(argc
, argv
))
3467 if (*opt_codeset
&& strcmp(opt_codeset
, opt_encoding
)) {
3468 opt_iconv
= iconv_open(opt_codeset
, opt_encoding
);
3469 if (opt_iconv
== ICONV_NONE
)
3470 die("Failed to initialize character set conversion");
3473 if (load_refs() == ERR
)
3474 die("Failed to load refs.");
3476 /* Require a git repository unless when running in pager mode. */
3477 if (refs_size
== 0 && opt_request
!= REQ_VIEW_PAGER
)
3478 die("Not a git repository");
3480 for (i
= 0; i
< ARRAY_SIZE(views
) && (view
= &views
[i
]); i
++)
3481 view
->cmd_env
= getenv(view
->cmd_env
);
3483 request
= opt_request
;
3487 while (view_driver(display
[current_view
], request
)) {
3491 foreach_view (view
, i
)
3494 /* Refresh, accept single keystroke of input */
3495 key
= wgetch(status_win
);
3497 request
= get_keybinding(display
[current_view
]->keymap
, key
);
3499 /* Some low-level request handling. This keeps access to
3500 * status_win restricted. */
3504 char *cmd
= read_prompt(":");
3506 if (cmd
&& string_format(opt_cmd
, "git %s", cmd
)) {
3507 if (strncmp(cmd
, "show", 4) && isspace(cmd
[4])) {
3508 opt_request
= REQ_VIEW_DIFF
;
3510 opt_request
= REQ_VIEW_PAGER
;
3519 case REQ_SEARCH_BACK
:
3521 const char *prompt
= request
== REQ_SEARCH
3523 char *search
= read_prompt(prompt
);
3526 string_copy(opt_search
, search
);
3531 case REQ_SCREEN_RESIZE
:
3535 getmaxyx(stdscr
, height
, width
);
3537 /* Resize the status view and let the view driver take
3538 * care of resizing the displayed views. */
3539 wresize(status_win
, 1, width
);
3540 mvwin(status_win
, height
- 1, 0);
3541 wrefresh(status_win
);