1 /* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2 *
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.
7 *
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.
12 */
14 #ifndef VERSION
15 #define VERSION "tig-0.4.git"
16 #endif
18 #ifndef DEBUG
19 #define NDEBUG
20 #endif
22 #include <assert.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <time.h>
33 #include <sys/types.h>
34 #include <regex.h>
36 #include <locale.h>
37 #include <langinfo.h>
38 #include <iconv.h>
40 #include <curses.h>
42 #if __GNUC__ >= 3
43 #define __NORETURN __attribute__((__noreturn__))
44 #else
45 #define __NORETURN
46 #endif
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
78 #define TABSIZE 8
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"
88 #define TIG_LOG_CMD \
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 /* XXX: Needs to be defined to the empty string. */
95 #define TIG_HELP_CMD ""
96 #define TIG_PAGER_CMD ""
98 /* Some ascii-shorthands fitted into the ncurses namespace. */
99 #define KEY_TAB '\t'
100 #define KEY_RETURN '\r'
101 #define KEY_ESC 27
104 struct ref {
105 char *name; /* Ref name; tag or head names are shortened. */
106 char id[41]; /* Commit SHA1 ID */
107 unsigned int tag:1; /* Is it a tag? */
108 unsigned int next:1; /* For ref lists: are there more refs? */
109 };
111 static struct ref **get_refs(char *id);
113 struct int_map {
114 const char *name;
115 int namelen;
116 int value;
117 };
119 static int
120 set_from_int_map(struct int_map *map, size_t map_size,
121 int *value, const char *name, int namelen)
122 {
124 int i;
126 for (i = 0; i < map_size; i++)
127 if (namelen == map[i].namelen &&
128 !strncasecmp(name, map[i].name, namelen)) {
129 *value = map[i].value;
130 return OK;
131 }
133 return ERR;
134 }
137 /*
138 * String helpers
139 */
141 static inline void
142 string_ncopy(char *dst, const char *src, int dstlen)
143 {
144 strncpy(dst, src, dstlen - 1);
145 dst[dstlen - 1] = 0;
147 }
149 /* Shorthand for safely copying into a fixed buffer. */
150 #define string_copy(dst, src) \
151 string_ncopy(dst, src, sizeof(dst))
153 static char *
154 chomp_string(char *name)
155 {
156 int namelen;
158 while (isspace(*name))
159 name++;
161 namelen = strlen(name) - 1;
162 while (namelen > 0 && isspace(name[namelen]))
163 name[namelen--] = 0;
165 return name;
166 }
168 static bool
169 string_nformat(char *buf, size_t bufsize, int *bufpos, const char *fmt, ...)
170 {
171 va_list args;
172 int pos = bufpos ? *bufpos : 0;
174 va_start(args, fmt);
175 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
176 va_end(args);
178 if (bufpos)
179 *bufpos = pos;
181 return pos >= bufsize ? FALSE : TRUE;
182 }
184 #define string_format(buf, fmt, args...) \
185 string_nformat(buf, sizeof(buf), NULL, fmt, args)
187 #define string_format_from(buf, from, fmt, args...) \
188 string_nformat(buf, sizeof(buf), from, fmt, args)
190 static int
191 string_enum_compare(const char *str1, const char *str2, int len)
192 {
193 size_t i;
195 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
197 /* Diff-Header == DIFF_HEADER */
198 for (i = 0; i < len; i++) {
199 if (toupper(str1[i]) == toupper(str2[i]))
200 continue;
202 if (string_enum_sep(str1[i]) &&
203 string_enum_sep(str2[i]))
204 continue;
206 return str1[i] - str2[i];
207 }
209 return 0;
210 }
212 /* Shell quoting
213 *
214 * NOTE: The following is a slightly modified copy of the git project's shell
215 * quoting routines found in the quote.c file.
216 *
217 * Help to copy the thing properly quoted for the shell safety. any single
218 * quote is replaced with '\'', any exclamation point is replaced with '\!',
219 * and the whole thing is enclosed in a
220 *
221 * E.g.
222 * original sq_quote result
223 * name ==> name ==> 'name'
224 * a b ==> a b ==> 'a b'
225 * a'b ==> a'\''b ==> 'a'\''b'
226 * a!b ==> a'\!'b ==> 'a'\!'b'
227 */
229 static size_t
230 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
231 {
232 char c;
234 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
236 BUFPUT('\'');
237 while ((c = *src++)) {
238 if (c == '\'' || c == '!') {
239 BUFPUT('\'');
240 BUFPUT('\\');
241 BUFPUT(c);
242 BUFPUT('\'');
243 } else {
244 BUFPUT(c);
245 }
246 }
247 BUFPUT('\'');
249 return bufsize;
250 }
253 /*
254 * User requests
255 */
257 #define REQ_INFO \
258 /* XXX: Keep the view request first and in sync with views[]. */ \
259 REQ_GROUP("View switching") \
260 REQ_(VIEW_MAIN, "Show main view"), \
261 REQ_(VIEW_DIFF, "Show diff view"), \
262 REQ_(VIEW_LOG, "Show log view"), \
263 REQ_(VIEW_HELP, "Show help page"), \
264 REQ_(VIEW_PAGER, "Show pager view"), \
265 \
266 REQ_GROUP("View manipulation") \
267 REQ_(ENTER, "Enter current line and scroll"), \
268 REQ_(NEXT, "Move to next"), \
269 REQ_(PREVIOUS, "Move to previous"), \
270 REQ_(VIEW_NEXT, "Move focus to next view"), \
271 REQ_(VIEW_CLOSE, "Close the current view"), \
272 REQ_(QUIT, "Close all views and quit"), \
273 \
274 REQ_GROUP("Cursor navigation") \
275 REQ_(MOVE_UP, "Move cursor one line up"), \
276 REQ_(MOVE_DOWN, "Move cursor one line down"), \
277 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
278 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
279 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
280 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
281 \
282 REQ_GROUP("Scrolling") \
283 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
284 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
285 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
286 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
287 \
288 REQ_GROUP("Searching") \
289 REQ_(SEARCH, "Search the view"), \
290 REQ_(SEARCH_BACK, "Search backwards in the view"), \
291 REQ_(FIND_NEXT, "Find next search match"), \
292 REQ_(FIND_PREV, "Find previous search match"), \
293 \
294 REQ_GROUP("Misc") \
295 REQ_(NONE, "Do nothing"), \
296 REQ_(PROMPT, "Bring up the prompt"), \
297 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
298 REQ_(SCREEN_RESIZE, "Resize the screen"), \
299 REQ_(SHOW_VERSION, "Show version information"), \
300 REQ_(STOP_LOADING, "Stop all loading views"), \
301 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
302 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization")
305 /* User action requests. */
306 enum request {
307 #define REQ_GROUP(help)
308 #define REQ_(req, help) REQ_##req
310 /* Offset all requests to avoid conflicts with ncurses getch values. */
311 REQ_OFFSET = KEY_MAX + 1,
312 REQ_INFO,
313 REQ_UNKNOWN,
315 #undef REQ_GROUP
316 #undef REQ_
317 };
319 struct request_info {
320 enum request request;
321 char *name;
322 int namelen;
323 char *help;
324 };
326 static struct request_info req_info[] = {
327 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
328 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
329 REQ_INFO
330 #undef REQ_GROUP
331 #undef REQ_
332 };
334 static enum request
335 get_request(const char *name)
336 {
337 int namelen = strlen(name);
338 int i;
340 for (i = 0; i < ARRAY_SIZE(req_info); i++)
341 if (req_info[i].namelen == namelen &&
342 !string_enum_compare(req_info[i].name, name, namelen))
343 return req_info[i].request;
345 return REQ_UNKNOWN;
346 }
349 /*
350 * Options
351 */
353 static const char usage[] =
354 VERSION " (" __DATE__ ")\n"
355 "\n"
356 "Usage: tig [options]\n"
357 " or: tig [options] [--] [git log options]\n"
358 " or: tig [options] log [git log options]\n"
359 " or: tig [options] diff [git diff options]\n"
360 " or: tig [options] show [git show options]\n"
361 " or: tig [options] < [git command output]\n"
362 "\n"
363 "Options:\n"
364 " -l Start up in log view\n"
365 " -d Start up in diff view\n"
366 " -n[I], --line-number[=I] Show line numbers with given interval\n"
367 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
368 " -- Mark end of tig options\n"
369 " -v, --version Show version and exit\n"
370 " -h, --help Show help message and exit\n";
372 /* Option and state variables. */
373 static bool opt_line_number = FALSE;
374 static bool opt_rev_graph = TRUE;
375 static int opt_num_interval = NUMBER_INTERVAL;
376 static int opt_tab_size = TABSIZE;
377 static enum request opt_request = REQ_VIEW_MAIN;
378 static char opt_cmd[SIZEOF_STR] = "";
379 static FILE *opt_pipe = NULL;
380 static char opt_encoding[20] = "UTF-8";
381 static bool opt_utf8 = TRUE;
382 static char opt_codeset[20] = "UTF-8";
383 static iconv_t opt_iconv = ICONV_NONE;
384 static char opt_search[SIZEOF_STR] = "";
386 enum option_type {
387 OPT_NONE,
388 OPT_INT,
389 };
391 static bool
392 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
393 {
394 va_list args;
395 char *value = "";
396 int *number;
398 if (opt[0] != '-')
399 return FALSE;
401 if (opt[1] == '-') {
402 int namelen = strlen(name);
404 opt += 2;
406 if (strncmp(opt, name, namelen))
407 return FALSE;
409 if (opt[namelen] == '=')
410 value = opt + namelen + 1;
412 } else {
413 if (!short_name || opt[1] != short_name)
414 return FALSE;
415 value = opt + 2;
416 }
418 va_start(args, type);
419 if (type == OPT_INT) {
420 number = va_arg(args, int *);
421 if (isdigit(*value))
422 *number = atoi(value);
423 }
424 va_end(args);
426 return TRUE;
427 }
429 /* Returns the index of log or diff command or -1 to exit. */
430 static bool
431 parse_options(int argc, char *argv[])
432 {
433 int i;
435 for (i = 1; i < argc; i++) {
436 char *opt = argv[i];
438 if (!strcmp(opt, "-l")) {
439 opt_request = REQ_VIEW_LOG;
440 continue;
441 }
443 if (!strcmp(opt, "-d")) {
444 opt_request = REQ_VIEW_DIFF;
445 continue;
446 }
448 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
449 opt_line_number = TRUE;
450 continue;
451 }
453 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
454 opt_tab_size = MIN(opt_tab_size, TABSIZE);
455 continue;
456 }
458 if (check_option(opt, 'v', "version", OPT_NONE)) {
459 printf("tig version %s\n", VERSION);
460 return FALSE;
461 }
463 if (check_option(opt, 'h', "help", OPT_NONE)) {
464 printf(usage);
465 return FALSE;
466 }
468 if (!strcmp(opt, "--")) {
469 i++;
470 break;
471 }
473 if (!strcmp(opt, "log") ||
474 !strcmp(opt, "diff") ||
475 !strcmp(opt, "show")) {
476 opt_request = opt[0] == 'l'
477 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
478 break;
479 }
481 if (opt[0] && opt[0] != '-')
482 break;
484 die("unknown option '%s'\n\n%s", opt, usage);
485 }
487 if (!isatty(STDIN_FILENO)) {
488 opt_request = REQ_VIEW_PAGER;
489 opt_pipe = stdin;
491 } else if (i < argc) {
492 size_t buf_size;
494 if (opt_request == REQ_VIEW_MAIN)
495 /* XXX: This is vulnerable to the user overriding
496 * options required for the main view parser. */
497 string_copy(opt_cmd, "git log --stat --pretty=raw");
498 else
499 string_copy(opt_cmd, "git");
500 buf_size = strlen(opt_cmd);
502 while (buf_size < sizeof(opt_cmd) && i < argc) {
503 opt_cmd[buf_size++] = ' ';
504 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
505 }
507 if (buf_size >= sizeof(opt_cmd))
508 die("command too long");
510 opt_cmd[buf_size] = 0;
512 }
514 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
515 opt_utf8 = FALSE;
517 return TRUE;
518 }
521 /*
522 * Line-oriented content detection.
523 */
525 #define LINE_INFO \
526 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
527 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
528 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
529 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
530 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
531 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
532 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
533 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
534 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
535 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
536 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
537 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
538 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
539 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
540 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
541 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
542 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
543 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
544 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
545 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
546 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
547 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
548 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
549 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
550 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
551 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
552 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
553 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
554 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
555 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
556 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
557 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
558 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
559 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
560 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
561 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
562 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
563 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
565 enum line_type {
566 #define LINE(type, line, fg, bg, attr) \
567 LINE_##type
568 LINE_INFO
569 #undef LINE
570 };
572 struct line_info {
573 const char *name; /* Option name. */
574 int namelen; /* Size of option name. */
575 const char *line; /* The start of line to match. */
576 int linelen; /* Size of string to match. */
577 int fg, bg, attr; /* Color and text attributes for the lines. */
578 };
580 static struct line_info line_info[] = {
581 #define LINE(type, line, fg, bg, attr) \
582 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
583 LINE_INFO
584 #undef LINE
585 };
587 static enum line_type
588 get_line_type(char *line)
589 {
590 int linelen = strlen(line);
591 enum line_type type;
593 for (type = 0; type < ARRAY_SIZE(line_info); type++)
594 /* Case insensitive search matches Signed-off-by lines better. */
595 if (linelen >= line_info[type].linelen &&
596 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
597 return type;
599 return LINE_DEFAULT;
600 }
602 static inline int
603 get_line_attr(enum line_type type)
604 {
605 assert(type < ARRAY_SIZE(line_info));
606 return COLOR_PAIR(type) | line_info[type].attr;
607 }
609 static struct line_info *
610 get_line_info(char *name, int namelen)
611 {
612 enum line_type type;
614 for (type = 0; type < ARRAY_SIZE(line_info); type++)
615 if (namelen == line_info[type].namelen &&
616 !string_enum_compare(line_info[type].name, name, namelen))
617 return &line_info[type];
619 return NULL;
620 }
622 static void
623 init_colors(void)
624 {
625 int default_bg = COLOR_BLACK;
626 int default_fg = COLOR_WHITE;
627 enum line_type type;
629 start_color();
631 if (use_default_colors() != ERR) {
632 default_bg = -1;
633 default_fg = -1;
634 }
636 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
637 struct line_info *info = &line_info[type];
638 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
639 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
641 init_pair(type, fg, bg);
642 }
643 }
645 struct line {
646 enum line_type type;
647 void *data; /* User data */
648 };
651 /*
652 * Keys
653 */
655 struct keybinding {
656 int alias;
657 enum request request;
658 struct keybinding *next;
659 };
661 static struct keybinding default_keybindings[] = {
662 /* View switching */
663 { 'm', REQ_VIEW_MAIN },
664 { 'd', REQ_VIEW_DIFF },
665 { 'l', REQ_VIEW_LOG },
666 { 'p', REQ_VIEW_PAGER },
667 { 'h', REQ_VIEW_HELP },
669 /* View manipulation */
670 { 'q', REQ_VIEW_CLOSE },
671 { KEY_TAB, REQ_VIEW_NEXT },
672 { KEY_RETURN, REQ_ENTER },
673 { KEY_UP, REQ_PREVIOUS },
674 { KEY_DOWN, REQ_NEXT },
676 /* Cursor navigation */
677 { 'k', REQ_MOVE_UP },
678 { 'j', REQ_MOVE_DOWN },
679 { KEY_HOME, REQ_MOVE_FIRST_LINE },
680 { KEY_END, REQ_MOVE_LAST_LINE },
681 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
682 { ' ', REQ_MOVE_PAGE_DOWN },
683 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
684 { 'b', REQ_MOVE_PAGE_UP },
685 { '-', REQ_MOVE_PAGE_UP },
687 /* Scrolling */
688 { KEY_IC, REQ_SCROLL_LINE_UP },
689 { KEY_DC, REQ_SCROLL_LINE_DOWN },
690 { 'w', REQ_SCROLL_PAGE_UP },
691 { 's', REQ_SCROLL_PAGE_DOWN },
693 /* Searching */
694 { '/', REQ_SEARCH },
695 { '?', REQ_SEARCH_BACK },
696 { 'n', REQ_FIND_NEXT },
697 { 'N', REQ_FIND_PREV },
699 /* Misc */
700 { 'Q', REQ_QUIT },
701 { 'z', REQ_STOP_LOADING },
702 { 'v', REQ_SHOW_VERSION },
703 { 'r', REQ_SCREEN_REDRAW },
704 { 'n', REQ_TOGGLE_LINENO },
705 { 'g', REQ_TOGGLE_REV_GRAPH },
706 { ':', REQ_PROMPT },
708 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
709 { ERR, REQ_NONE },
711 /* Using the ncurses SIGWINCH handler. */
712 { KEY_RESIZE, REQ_SCREEN_RESIZE },
713 };
715 #define KEYMAP_INFO \
716 KEYMAP_(GENERIC), \
717 KEYMAP_(MAIN), \
718 KEYMAP_(DIFF), \
719 KEYMAP_(LOG), \
720 KEYMAP_(PAGER), \
721 KEYMAP_(HELP) \
723 enum keymap {
724 #define KEYMAP_(name) KEYMAP_##name
725 KEYMAP_INFO
726 #undef KEYMAP_
727 };
729 static struct int_map keymap_table[] = {
730 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
731 KEYMAP_INFO
732 #undef KEYMAP_
733 };
735 #define set_keymap(map, name) \
736 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
738 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
740 static void
741 add_keybinding(enum keymap keymap, enum request request, int key)
742 {
743 struct keybinding *keybinding;
745 keybinding = calloc(1, sizeof(*keybinding));
746 if (!keybinding)
747 die("Failed to allocate keybinding");
749 keybinding->alias = key;
750 keybinding->request = request;
751 keybinding->next = keybindings[keymap];
752 keybindings[keymap] = keybinding;
753 }
755 /* Looks for a key binding first in the given map, then in the generic map, and
756 * lastly in the default keybindings. */
757 static enum request
758 get_keybinding(enum keymap keymap, int key)
759 {
760 struct keybinding *kbd;
761 int i;
763 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
764 if (kbd->alias == key)
765 return kbd->request;
767 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
768 if (kbd->alias == key)
769 return kbd->request;
771 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
772 if (default_keybindings[i].alias == key)
773 return default_keybindings[i].request;
775 return (enum request) key;
776 }
779 struct key {
780 char *name;
781 int value;
782 };
784 static struct key key_table[] = {
785 { "Enter", KEY_RETURN },
786 { "Space", ' ' },
787 { "Backspace", KEY_BACKSPACE },
788 { "Tab", KEY_TAB },
789 { "Escape", KEY_ESC },
790 { "Left", KEY_LEFT },
791 { "Right", KEY_RIGHT },
792 { "Up", KEY_UP },
793 { "Down", KEY_DOWN },
794 { "Insert", KEY_IC },
795 { "Delete", KEY_DC },
796 { "Hash", '#' },
797 { "Home", KEY_HOME },
798 { "End", KEY_END },
799 { "PageUp", KEY_PPAGE },
800 { "PageDown", KEY_NPAGE },
801 { "F1", KEY_F(1) },
802 { "F2", KEY_F(2) },
803 { "F3", KEY_F(3) },
804 { "F4", KEY_F(4) },
805 { "F5", KEY_F(5) },
806 { "F6", KEY_F(6) },
807 { "F7", KEY_F(7) },
808 { "F8", KEY_F(8) },
809 { "F9", KEY_F(9) },
810 { "F10", KEY_F(10) },
811 { "F11", KEY_F(11) },
812 { "F12", KEY_F(12) },
813 };
815 static int
816 get_key_value(const char *name)
817 {
818 int i;
820 for (i = 0; i < ARRAY_SIZE(key_table); i++)
821 if (!strcasecmp(key_table[i].name, name))
822 return key_table[i].value;
824 if (strlen(name) == 1 && isprint(*name))
825 return (int) *name;
827 return ERR;
828 }
830 static char *
831 get_key(enum request request)
832 {
833 static char buf[BUFSIZ];
834 static char key_char[] = "'X'";
835 int pos = 0;
836 char *sep = " ";
837 int i;
839 buf[pos] = 0;
841 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
842 struct keybinding *keybinding = &default_keybindings[i];
843 char *seq = NULL;
844 int key;
846 if (keybinding->request != request)
847 continue;
849 for (key = 0; key < ARRAY_SIZE(key_table); key++)
850 if (key_table[key].value == keybinding->alias)
851 seq = key_table[key].name;
853 if (seq == NULL &&
854 keybinding->alias < 127 &&
855 isprint(keybinding->alias)) {
856 key_char[1] = (char) keybinding->alias;
857 seq = key_char;
858 }
860 if (!seq)
861 seq = "'?'";
863 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
864 return "Too many keybindings!";
865 sep = ", ";
866 }
868 return buf;
869 }
872 /*
873 * User config file handling.
874 */
876 static struct int_map color_map[] = {
877 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
878 COLOR_MAP(DEFAULT),
879 COLOR_MAP(BLACK),
880 COLOR_MAP(BLUE),
881 COLOR_MAP(CYAN),
882 COLOR_MAP(GREEN),
883 COLOR_MAP(MAGENTA),
884 COLOR_MAP(RED),
885 COLOR_MAP(WHITE),
886 COLOR_MAP(YELLOW),
887 };
889 #define set_color(color, name) \
890 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
892 static struct int_map attr_map[] = {
893 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
894 ATTR_MAP(NORMAL),
895 ATTR_MAP(BLINK),
896 ATTR_MAP(BOLD),
897 ATTR_MAP(DIM),
898 ATTR_MAP(REVERSE),
899 ATTR_MAP(STANDOUT),
900 ATTR_MAP(UNDERLINE),
901 };
903 #define set_attribute(attr, name) \
904 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
906 static int config_lineno;
907 static bool config_errors;
908 static char *config_msg;
910 /* Wants: object fgcolor bgcolor [attr] */
911 static int
912 option_color_command(int argc, char *argv[])
913 {
914 struct line_info *info;
916 if (argc != 3 && argc != 4) {
917 config_msg = "Wrong number of arguments given to color command";
918 return ERR;
919 }
921 info = get_line_info(argv[0], strlen(argv[0]));
922 if (!info) {
923 config_msg = "Unknown color name";
924 return ERR;
925 }
927 if (set_color(&info->fg, argv[1]) == ERR ||
928 set_color(&info->bg, argv[2]) == ERR) {
929 config_msg = "Unknown color";
930 return ERR;
931 }
933 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
934 config_msg = "Unknown attribute";
935 return ERR;
936 }
938 return OK;
939 }
941 /* Wants: name = value */
942 static int
943 option_set_command(int argc, char *argv[])
944 {
945 if (argc != 3) {
946 config_msg = "Wrong number of arguments given to set command";
947 return ERR;
948 }
950 if (strcmp(argv[1], "=")) {
951 config_msg = "No value assigned";
952 return ERR;
953 }
955 if (!strcmp(argv[0], "show-rev-graph")) {
956 opt_rev_graph = (!strcmp(argv[2], "1") ||
957 !strcmp(argv[2], "true") ||
958 !strcmp(argv[2], "yes"));
959 return OK;
960 }
962 if (!strcmp(argv[0], "line-number-interval")) {
963 opt_num_interval = atoi(argv[2]);
964 return OK;
965 }
967 if (!strcmp(argv[0], "tab-size")) {
968 opt_tab_size = atoi(argv[2]);
969 return OK;
970 }
972 if (!strcmp(argv[0], "commit-encoding")) {
973 char *arg = argv[2];
974 int delimiter = *arg;
975 int i;
977 switch (delimiter) {
978 case '"':
979 case '\'':
980 for (arg++, i = 0; arg[i]; i++)
981 if (arg[i] == delimiter) {
982 arg[i] = 0;
983 break;
984 }
985 default:
986 string_copy(opt_encoding, arg);
987 return OK;
988 }
989 }
991 config_msg = "Unknown variable name";
992 return ERR;
993 }
995 /* Wants: mode request key */
996 static int
997 option_bind_command(int argc, char *argv[])
998 {
999 enum request request;
1000 int keymap;
1001 int key;
1003 if (argc != 3) {
1004 config_msg = "Wrong number of arguments given to bind command";
1005 return ERR;
1006 }
1008 if (set_keymap(&keymap, argv[0]) == ERR) {
1009 config_msg = "Unknown key map";
1010 return ERR;
1011 }
1013 key = get_key_value(argv[1]);
1014 if (key == ERR) {
1015 config_msg = "Unknown key";
1016 return ERR;
1017 }
1019 request = get_request(argv[2]);
1020 if (request == REQ_UNKNOWN) {
1021 config_msg = "Unknown request name";
1022 return ERR;
1023 }
1025 add_keybinding(keymap, request, key);
1027 return OK;
1028 }
1030 static int
1031 set_option(char *opt, char *value)
1032 {
1033 char *argv[16];
1034 int valuelen;
1035 int argc = 0;
1037 /* Tokenize */
1038 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1039 argv[argc++] = value;
1041 value += valuelen;
1042 if (!*value)
1043 break;
1045 *value++ = 0;
1046 while (isspace(*value))
1047 value++;
1048 }
1050 if (!strcmp(opt, "color"))
1051 return option_color_command(argc, argv);
1053 if (!strcmp(opt, "set"))
1054 return option_set_command(argc, argv);
1056 if (!strcmp(opt, "bind"))
1057 return option_bind_command(argc, argv);
1059 config_msg = "Unknown option command";
1060 return ERR;
1061 }
1063 static int
1064 read_option(char *opt, int optlen, char *value, int valuelen)
1065 {
1066 int status = OK;
1068 config_lineno++;
1069 config_msg = "Internal error";
1071 /* Check for comment markers, since read_properties() will
1072 * only ensure opt and value are split at first " \t". */
1073 optlen = strcspn(opt, "#");
1074 if (optlen == 0)
1075 return OK;
1077 if (opt[optlen] != 0) {
1078 config_msg = "No option value";
1079 status = ERR;
1081 } else {
1082 /* Look for comment endings in the value. */
1083 int len = strcspn(value, "#");
1085 if (len < valuelen) {
1086 valuelen = len;
1087 value[valuelen] = 0;
1088 }
1090 status = set_option(opt, value);
1091 }
1093 if (status == ERR) {
1094 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1095 config_lineno, optlen, opt, config_msg);
1096 config_errors = TRUE;
1097 }
1099 /* Always keep going if errors are encountered. */
1100 return OK;
1101 }
1103 static int
1104 load_options(void)
1105 {
1106 char *home = getenv("HOME");
1107 char buf[SIZEOF_STR];
1108 FILE *file;
1110 config_lineno = 0;
1111 config_errors = FALSE;
1113 if (!home || !string_format(buf, "%s/.tigrc", home))
1114 return ERR;
1116 /* It's ok that the file doesn't exist. */
1117 file = fopen(buf, "r");
1118 if (!file)
1119 return OK;
1121 if (read_properties(file, " \t", read_option) == ERR ||
1122 config_errors == TRUE)
1123 fprintf(stderr, "Errors while loading %s.\n", buf);
1125 return OK;
1126 }
1129 /*
1130 * The viewer
1131 */
1133 struct view;
1134 struct view_ops;
1136 /* The display array of active views and the index of the current view. */
1137 static struct view *display[2];
1138 static unsigned int current_view;
1140 #define foreach_view(view, i) \
1141 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1143 #define displayed_views() (display[1] != NULL ? 2 : 1)
1145 /* Current head and commit ID */
1146 static char ref_commit[SIZEOF_REF] = "HEAD";
1147 static char ref_head[SIZEOF_REF] = "HEAD";
1149 struct view {
1150 const char *name; /* View name */
1151 const char *cmd_fmt; /* Default command line format */
1152 const char *cmd_env; /* Command line set via environment */
1153 const char *id; /* Points to either of ref_{head,commit} */
1155 struct view_ops *ops; /* View operations */
1157 enum keymap keymap; /* What keymap does this view have */
1159 char cmd[SIZEOF_STR]; /* Command buffer */
1160 char ref[SIZEOF_REF]; /* Hovered commit reference */
1161 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1163 int height, width; /* The width and height of the main window */
1164 WINDOW *win; /* The main window */
1165 WINDOW *title; /* The title window living below the main window */
1167 /* Navigation */
1168 unsigned long offset; /* Offset of the window top */
1169 unsigned long lineno; /* Current line number */
1171 /* Searching */
1172 char grep[SIZEOF_STR]; /* Search string */
1173 regex_t regex; /* Pre-compiled regex */
1175 /* If non-NULL, points to the view that opened this view. If this view
1176 * is closed tig will switch back to the parent view. */
1177 struct view *parent;
1179 /* Buffering */
1180 unsigned long lines; /* Total number of lines */
1181 struct line *line; /* Line index */
1182 unsigned long line_size;/* Total number of allocated lines */
1183 unsigned int digits; /* Number of digits in the lines member. */
1185 /* Loading */
1186 FILE *pipe;
1187 time_t start_time;
1188 };
1190 struct view_ops {
1191 /* What type of content being displayed. Used in the title bar. */
1192 const char *type;
1193 /* Draw one line; @lineno must be < view->height. */
1194 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1195 /* Read one line; updates view->line. */
1196 bool (*read)(struct view *view, char *data);
1197 /* Depending on view, change display based on current line. */
1198 bool (*enter)(struct view *view, struct line *line);
1199 /* Search for regex in a line. */
1200 bool (*grep)(struct view *view, struct line *line);
1201 };
1203 static struct view_ops pager_ops;
1204 static struct view_ops main_ops;
1206 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1207 { name, cmd, #env, ref, ops, map}
1209 #define VIEW_(id, name, ops, ref) \
1210 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1213 static struct view views[] = {
1214 VIEW_(MAIN, "main", &main_ops, ref_head),
1215 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1216 VIEW_(LOG, "log", &pager_ops, ref_head),
1217 VIEW_(HELP, "help", &pager_ops, "static"),
1218 VIEW_(PAGER, "pager", &pager_ops, "static"),
1219 };
1221 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1224 static bool
1225 draw_view_line(struct view *view, unsigned int lineno)
1226 {
1227 if (view->offset + lineno >= view->lines)
1228 return FALSE;
1230 return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
1231 }
1233 static void
1234 redraw_view_from(struct view *view, int lineno)
1235 {
1236 assert(0 <= lineno && lineno < view->height);
1238 for (; lineno < view->height; lineno++) {
1239 if (!draw_view_line(view, lineno))
1240 break;
1241 }
1243 redrawwin(view->win);
1244 wrefresh(view->win);
1245 }
1247 static void
1248 redraw_view(struct view *view)
1249 {
1250 wclear(view->win);
1251 redraw_view_from(view, 0);
1252 }
1255 static void
1256 update_view_title(struct view *view)
1257 {
1258 if (view == display[current_view])
1259 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1260 else
1261 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1263 werase(view->title);
1264 wmove(view->title, 0, 0);
1266 if (*view->ref)
1267 wprintw(view->title, "[%s] %s", view->name, view->ref);
1268 else
1269 wprintw(view->title, "[%s]", view->name);
1271 if (view->lines || view->pipe) {
1272 unsigned int view_lines = view->offset + view->height;
1273 unsigned int lines = view->lines
1274 ? MIN(view_lines, view->lines) * 100 / view->lines
1275 : 0;
1277 wprintw(view->title, " - %s %d of %d (%d%%)",
1278 view->ops->type,
1279 view->lineno + 1,
1280 view->lines,
1281 lines);
1282 }
1284 if (view->pipe) {
1285 time_t secs = time(NULL) - view->start_time;
1287 /* Three git seconds are a long time ... */
1288 if (secs > 2)
1289 wprintw(view->title, " %lds", secs);
1290 }
1292 wmove(view->title, 0, view->width - 1);
1293 wrefresh(view->title);
1294 }
1296 static void
1297 resize_display(void)
1298 {
1299 int offset, i;
1300 struct view *base = display[0];
1301 struct view *view = display[1] ? display[1] : display[0];
1303 /* Setup window dimensions */
1305 getmaxyx(stdscr, base->height, base->width);
1307 /* Make room for the status window. */
1308 base->height -= 1;
1310 if (view != base) {
1311 /* Horizontal split. */
1312 view->width = base->width;
1313 view->height = SCALE_SPLIT_VIEW(base->height);
1314 base->height -= view->height;
1316 /* Make room for the title bar. */
1317 view->height -= 1;
1318 }
1320 /* Make room for the title bar. */
1321 base->height -= 1;
1323 offset = 0;
1325 foreach_view (view, i) {
1326 if (!view->win) {
1327 view->win = newwin(view->height, 0, offset, 0);
1328 if (!view->win)
1329 die("Failed to create %s view", view->name);
1331 scrollok(view->win, TRUE);
1333 view->title = newwin(1, 0, offset + view->height, 0);
1334 if (!view->title)
1335 die("Failed to create title window");
1337 } else {
1338 wresize(view->win, view->height, view->width);
1339 mvwin(view->win, offset, 0);
1340 mvwin(view->title, offset + view->height, 0);
1341 }
1343 offset += view->height + 1;
1344 }
1345 }
1347 static void
1348 redraw_display(void)
1349 {
1350 struct view *view;
1351 int i;
1353 foreach_view (view, i) {
1354 redraw_view(view);
1355 update_view_title(view);
1356 }
1357 }
1359 static void
1360 update_display_cursor(void)
1361 {
1362 struct view *view = display[current_view];
1364 /* Move the cursor to the right-most column of the cursor line.
1365 *
1366 * XXX: This could turn out to be a bit expensive, but it ensures that
1367 * the cursor does not jump around. */
1368 if (view->lines) {
1369 wmove(view->win, view->lineno - view->offset, view->width - 1);
1370 wrefresh(view->win);
1371 }
1372 }
1374 /*
1375 * Navigation
1376 */
1378 /* Scrolling backend */
1379 static void
1380 do_scroll_view(struct view *view, int lines, bool redraw)
1381 {
1382 /* The rendering expects the new offset. */
1383 view->offset += lines;
1385 assert(0 <= view->offset && view->offset < view->lines);
1386 assert(lines);
1388 /* Redraw the whole screen if scrolling is pointless. */
1389 if (view->height < ABS(lines)) {
1390 redraw_view(view);
1392 } else {
1393 int line = lines > 0 ? view->height - lines : 0;
1394 int end = line + ABS(lines);
1396 wscrl(view->win, lines);
1398 for (; line < end; line++) {
1399 if (!draw_view_line(view, line))
1400 break;
1401 }
1402 }
1404 /* Move current line into the view. */
1405 if (view->lineno < view->offset) {
1406 view->lineno = view->offset;
1407 draw_view_line(view, 0);
1409 } else if (view->lineno >= view->offset + view->height) {
1410 if (view->lineno == view->offset + view->height) {
1411 /* Clear the hidden line so it doesn't show if the view
1412 * is scrolled up. */
1413 wmove(view->win, view->height, 0);
1414 wclrtoeol(view->win);
1415 }
1416 view->lineno = view->offset + view->height - 1;
1417 draw_view_line(view, view->lineno - view->offset);
1418 }
1420 assert(view->offset <= view->lineno && view->lineno < view->lines);
1422 if (!redraw)
1423 return;
1425 redrawwin(view->win);
1426 wrefresh(view->win);
1427 report("");
1428 }
1430 /* Scroll frontend */
1431 static void
1432 scroll_view(struct view *view, enum request request)
1433 {
1434 int lines = 1;
1436 switch (request) {
1437 case REQ_SCROLL_PAGE_DOWN:
1438 lines = view->height;
1439 case REQ_SCROLL_LINE_DOWN:
1440 if (view->offset + lines > view->lines)
1441 lines = view->lines - view->offset;
1443 if (lines == 0 || view->offset + view->height >= view->lines) {
1444 report("Cannot scroll beyond the last line");
1445 return;
1446 }
1447 break;
1449 case REQ_SCROLL_PAGE_UP:
1450 lines = view->height;
1451 case REQ_SCROLL_LINE_UP:
1452 if (lines > view->offset)
1453 lines = view->offset;
1455 if (lines == 0) {
1456 report("Cannot scroll beyond the first line");
1457 return;
1458 }
1460 lines = -lines;
1461 break;
1463 default:
1464 die("request %d not handled in switch", request);
1465 }
1467 do_scroll_view(view, lines, TRUE);
1468 }
1470 /* Cursor moving */
1471 static void
1472 move_view(struct view *view, enum request request, bool redraw)
1473 {
1474 int steps;
1476 switch (request) {
1477 case REQ_MOVE_FIRST_LINE:
1478 steps = -view->lineno;
1479 break;
1481 case REQ_MOVE_LAST_LINE:
1482 steps = view->lines - view->lineno - 1;
1483 break;
1485 case REQ_MOVE_PAGE_UP:
1486 steps = view->height > view->lineno
1487 ? -view->lineno : -view->height;
1488 break;
1490 case REQ_MOVE_PAGE_DOWN:
1491 steps = view->lineno + view->height >= view->lines
1492 ? view->lines - view->lineno - 1 : view->height;
1493 break;
1495 case REQ_MOVE_UP:
1496 steps = -1;
1497 break;
1499 case REQ_MOVE_DOWN:
1500 steps = 1;
1501 break;
1503 default:
1504 die("request %d not handled in switch", request);
1505 }
1507 if (steps <= 0 && view->lineno == 0) {
1508 report("Cannot move beyond the first line");
1509 return;
1511 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1512 report("Cannot move beyond the last line");
1513 return;
1514 }
1516 /* Move the current line */
1517 view->lineno += steps;
1518 assert(0 <= view->lineno && view->lineno < view->lines);
1520 /* Repaint the old "current" line if we be scrolling */
1521 if (ABS(steps) < view->height) {
1522 int prev_lineno = view->lineno - steps - view->offset;
1524 wmove(view->win, prev_lineno, 0);
1525 wclrtoeol(view->win);
1526 draw_view_line(view, prev_lineno);
1527 }
1529 /* Check whether the view needs to be scrolled */
1530 if (view->lineno < view->offset ||
1531 view->lineno >= view->offset + view->height) {
1532 if (steps < 0 && -steps > view->offset) {
1533 steps = -view->offset;
1535 } else if (steps > 0) {
1536 if (view->lineno == view->lines - 1 &&
1537 view->lines > view->height) {
1538 steps = view->lines - view->offset - 1;
1539 if (steps >= view->height)
1540 steps -= view->height - 1;
1541 }
1542 }
1544 do_scroll_view(view, steps, redraw);
1545 return;
1546 }
1548 /* Draw the current line */
1549 draw_view_line(view, view->lineno - view->offset);
1551 if (!redraw)
1552 return;
1554 redrawwin(view->win);
1555 wrefresh(view->win);
1556 report("");
1557 }
1560 /*
1561 * Searching
1562 */
1564 static void search_view(struct view *view, enum request request, const char *search);
1566 static bool
1567 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1568 {
1569 if (!view->ops->grep(view, line))
1570 return FALSE;
1572 if (lineno - view->offset >= view->height) {
1573 view->offset = lineno;
1574 view->lineno = lineno;
1575 redraw_view(view);
1577 } else {
1578 unsigned long old_lineno = view->lineno - view->offset;
1580 view->lineno = lineno;
1582 wmove(view->win, old_lineno, 0);
1583 wclrtoeol(view->win);
1584 draw_view_line(view, old_lineno);
1586 draw_view_line(view, view->lineno - view->offset);
1587 redrawwin(view->win);
1588 wrefresh(view->win);
1589 }
1591 report("Line %ld matches '%s'", lineno + 1, view->grep);
1592 return TRUE;
1593 }
1595 static void
1596 find_next(struct view *view, enum request request)
1597 {
1598 unsigned long lineno = view->lineno;
1599 int direction;
1601 if (!*view->grep) {
1602 if (!*opt_search)
1603 report("No previous search");
1604 else
1605 search_view(view, request, opt_search);
1606 return;
1607 }
1609 switch (request) {
1610 case REQ_SEARCH:
1611 case REQ_FIND_NEXT:
1612 direction = 1;
1613 break;
1615 case REQ_SEARCH_BACK:
1616 case REQ_FIND_PREV:
1617 direction = -1;
1618 break;
1620 default:
1621 return;
1622 }
1624 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1625 lineno += direction;
1627 /* Note, lineno is unsigned long so will wrap around in which case it
1628 * will become bigger than view->lines. */
1629 for (; lineno < view->lines; lineno += direction) {
1630 struct line *line = &view->line[lineno];
1632 if (find_next_line(view, lineno, line))
1633 return;
1634 }
1636 report("No match found for '%s'", view->grep);
1637 }
1639 static void
1640 search_view(struct view *view, enum request request, const char *search)
1641 {
1642 int regex_err;
1644 if (*view->grep) {
1645 regfree(&view->regex);
1646 *view->grep = 0;
1647 }
1649 regex_err = regcomp(&view->regex, search, REG_EXTENDED);
1650 if (regex_err != 0) {
1651 char buf[SIZEOF_STR] = "unknown error";
1653 regerror(regex_err, &view->regex, buf, sizeof(buf));
1654 report("Search failed: %s", buf);;
1655 return;
1656 }
1658 string_copy(view->grep, search);
1660 find_next(view, request);
1661 }
1663 /*
1664 * Incremental updating
1665 */
1667 static void
1668 end_update(struct view *view)
1669 {
1670 if (!view->pipe)
1671 return;
1672 set_nonblocking_input(FALSE);
1673 if (view->pipe == stdin)
1674 fclose(view->pipe);
1675 else
1676 pclose(view->pipe);
1677 view->pipe = NULL;
1678 }
1680 static bool
1681 begin_update(struct view *view)
1682 {
1683 const char *id = view->id;
1685 if (view->pipe)
1686 end_update(view);
1688 if (opt_cmd[0]) {
1689 string_copy(view->cmd, opt_cmd);
1690 opt_cmd[0] = 0;
1691 /* When running random commands, the view ref could have become
1692 * invalid so clear it. */
1693 view->ref[0] = 0;
1694 } else {
1695 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1697 if (!string_format(view->cmd, format, id, id, id, id, id))
1698 return FALSE;
1699 }
1701 /* Special case for the pager view. */
1702 if (opt_pipe) {
1703 view->pipe = opt_pipe;
1704 opt_pipe = NULL;
1705 } else {
1706 view->pipe = popen(view->cmd, "r");
1707 }
1709 if (!view->pipe)
1710 return FALSE;
1712 set_nonblocking_input(TRUE);
1714 view->offset = 0;
1715 view->lines = 0;
1716 view->lineno = 0;
1717 string_copy(view->vid, id);
1719 if (view->line) {
1720 int i;
1722 for (i = 0; i < view->lines; i++)
1723 if (view->line[i].data)
1724 free(view->line[i].data);
1726 free(view->line);
1727 view->line = NULL;
1728 }
1730 view->start_time = time(NULL);
1732 return TRUE;
1733 }
1735 static struct line *
1736 realloc_lines(struct view *view, size_t line_size)
1737 {
1738 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1740 if (!tmp)
1741 return NULL;
1743 view->line = tmp;
1744 view->line_size = line_size;
1745 return view->line;
1746 }
1748 static bool
1749 update_view(struct view *view)
1750 {
1751 char in_buffer[BUFSIZ];
1752 char out_buffer[BUFSIZ * 2];
1753 char *line;
1754 /* The number of lines to read. If too low it will cause too much
1755 * redrawing (and possible flickering), if too high responsiveness
1756 * will suffer. */
1757 unsigned long lines = view->height;
1758 int redraw_from = -1;
1760 if (!view->pipe)
1761 return TRUE;
1763 /* Only redraw if lines are visible. */
1764 if (view->offset + view->height >= view->lines)
1765 redraw_from = view->lines - view->offset;
1767 if (!realloc_lines(view, view->lines + lines))
1768 goto alloc_error;
1770 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1771 size_t linelen = strlen(line);
1773 if (linelen)
1774 line[linelen - 1] = 0;
1776 if (opt_iconv != ICONV_NONE) {
1777 char *inbuf = line;
1778 size_t inlen = linelen;
1780 char *outbuf = out_buffer;
1781 size_t outlen = sizeof(out_buffer);
1783 size_t ret;
1785 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1786 if (ret != (size_t) -1) {
1787 line = out_buffer;
1788 linelen = strlen(out_buffer);
1789 }
1790 }
1792 if (!view->ops->read(view, line))
1793 goto alloc_error;
1795 if (lines-- == 1)
1796 break;
1797 }
1799 {
1800 int digits;
1802 lines = view->lines;
1803 for (digits = 0; lines; digits++)
1804 lines /= 10;
1806 /* Keep the displayed view in sync with line number scaling. */
1807 if (digits != view->digits) {
1808 view->digits = digits;
1809 redraw_from = 0;
1810 }
1811 }
1813 if (redraw_from >= 0) {
1814 /* If this is an incremental update, redraw the previous line
1815 * since for commits some members could have changed when
1816 * loading the main view. */
1817 if (redraw_from > 0)
1818 redraw_from--;
1820 /* Incrementally draw avoids flickering. */
1821 redraw_view_from(view, redraw_from);
1822 }
1824 /* Update the title _after_ the redraw so that if the redraw picks up a
1825 * commit reference in view->ref it'll be available here. */
1826 update_view_title(view);
1828 if (ferror(view->pipe)) {
1829 report("Failed to read: %s", strerror(errno));
1830 goto end;
1832 } else if (feof(view->pipe)) {
1833 report("");
1834 goto end;
1835 }
1837 return TRUE;
1839 alloc_error:
1840 report("Allocation failure");
1842 end:
1843 end_update(view);
1844 return FALSE;
1845 }
1848 /*
1849 * View opening
1850 */
1852 static void open_help_view(struct view *view)
1853 {
1854 char buf[BUFSIZ];
1855 int lines = ARRAY_SIZE(req_info) + 2;
1856 int i;
1858 if (view->lines > 0)
1859 return;
1861 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1862 if (!req_info[i].request)
1863 lines++;
1865 view->line = calloc(lines, sizeof(*view->line));
1866 if (!view->line) {
1867 report("Allocation failure");
1868 return;
1869 }
1871 view->ops->read(view, "Quick reference for tig keybindings:");
1873 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1874 char *key;
1876 if (!req_info[i].request) {
1877 view->ops->read(view, "");
1878 view->ops->read(view, req_info[i].help);
1879 continue;
1880 }
1882 key = get_key(req_info[i].request);
1883 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1884 continue;
1886 view->ops->read(view, buf);
1887 }
1888 }
1890 enum open_flags {
1891 OPEN_DEFAULT = 0, /* Use default view switching. */
1892 OPEN_SPLIT = 1, /* Split current view. */
1893 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
1894 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
1895 };
1897 static void
1898 open_view(struct view *prev, enum request request, enum open_flags flags)
1899 {
1900 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1901 bool split = !!(flags & OPEN_SPLIT);
1902 bool reload = !!(flags & OPEN_RELOAD);
1903 struct view *view = VIEW(request);
1904 int nviews = displayed_views();
1905 struct view *base_view = display[0];
1907 if (view == prev && nviews == 1 && !reload) {
1908 report("Already in %s view", view->name);
1909 return;
1910 }
1912 if (view == VIEW(REQ_VIEW_HELP)) {
1913 open_help_view(view);
1915 } else if ((reload || strcmp(view->vid, view->id)) &&
1916 !begin_update(view)) {
1917 report("Failed to load %s view", view->name);
1918 return;
1919 }
1921 if (split) {
1922 display[1] = view;
1923 if (!backgrounded)
1924 current_view = 1;
1925 } else {
1926 /* Maximize the current view. */
1927 memset(display, 0, sizeof(display));
1928 current_view = 0;
1929 display[current_view] = view;
1930 }
1932 /* Resize the view when switching between split- and full-screen,
1933 * or when switching between two different full-screen views. */
1934 if (nviews != displayed_views() ||
1935 (nviews == 1 && base_view != display[0]))
1936 resize_display();
1938 if (split && prev->lineno - prev->offset >= prev->height) {
1939 /* Take the title line into account. */
1940 int lines = prev->lineno - prev->offset - prev->height + 1;
1942 /* Scroll the view that was split if the current line is
1943 * outside the new limited view. */
1944 do_scroll_view(prev, lines, TRUE);
1945 }
1947 if (prev && view != prev) {
1948 if (split && !backgrounded) {
1949 /* "Blur" the previous view. */
1950 update_view_title(prev);
1951 }
1953 view->parent = prev;
1954 }
1956 if (view->pipe && view->lines == 0) {
1957 /* Clear the old view and let the incremental updating refill
1958 * the screen. */
1959 wclear(view->win);
1960 report("");
1961 } else {
1962 redraw_view(view);
1963 report("");
1964 }
1966 /* If the view is backgrounded the above calls to report()
1967 * won't redraw the view title. */
1968 if (backgrounded)
1969 update_view_title(view);
1970 }
1973 /*
1974 * User request switch noodle
1975 */
1977 static int
1978 view_driver(struct view *view, enum request request)
1979 {
1980 int i;
1982 switch (request) {
1983 case REQ_MOVE_UP:
1984 case REQ_MOVE_DOWN:
1985 case REQ_MOVE_PAGE_UP:
1986 case REQ_MOVE_PAGE_DOWN:
1987 case REQ_MOVE_FIRST_LINE:
1988 case REQ_MOVE_LAST_LINE:
1989 move_view(view, request, TRUE);
1990 break;
1992 case REQ_SCROLL_LINE_DOWN:
1993 case REQ_SCROLL_LINE_UP:
1994 case REQ_SCROLL_PAGE_DOWN:
1995 case REQ_SCROLL_PAGE_UP:
1996 scroll_view(view, request);
1997 break;
1999 case REQ_VIEW_MAIN:
2000 case REQ_VIEW_DIFF:
2001 case REQ_VIEW_LOG:
2002 case REQ_VIEW_HELP:
2003 case REQ_VIEW_PAGER:
2004 open_view(view, request, OPEN_DEFAULT);
2005 break;
2007 case REQ_NEXT:
2008 case REQ_PREVIOUS:
2009 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2011 if (view == VIEW(REQ_VIEW_DIFF) &&
2012 view->parent == VIEW(REQ_VIEW_MAIN)) {
2013 bool redraw = display[1] == view;
2015 view = view->parent;
2016 move_view(view, request, redraw);
2017 if (redraw)
2018 update_view_title(view);
2019 } else {
2020 move_view(view, request, TRUE);
2021 break;
2022 }
2023 /* Fall-through */
2025 case REQ_ENTER:
2026 if (!view->lines) {
2027 report("Nothing to enter");
2028 break;
2029 }
2030 return view->ops->enter(view, &view->line[view->lineno]);
2032 case REQ_VIEW_NEXT:
2033 {
2034 int nviews = displayed_views();
2035 int next_view = (current_view + 1) % nviews;
2037 if (next_view == current_view) {
2038 report("Only one view is displayed");
2039 break;
2040 }
2042 current_view = next_view;
2043 /* Blur out the title of the previous view. */
2044 update_view_title(view);
2045 report("");
2046 break;
2047 }
2048 case REQ_TOGGLE_LINENO:
2049 opt_line_number = !opt_line_number;
2050 redraw_display();
2051 break;
2053 case REQ_TOGGLE_REV_GRAPH:
2054 opt_rev_graph = !opt_rev_graph;
2055 redraw_display();
2056 break;
2058 case REQ_PROMPT:
2059 /* Always reload^Wrerun commands from the prompt. */
2060 open_view(view, opt_request, OPEN_RELOAD);
2061 break;
2063 case REQ_SEARCH:
2064 case REQ_SEARCH_BACK:
2065 search_view(view, request, opt_search);
2066 break;
2068 case REQ_FIND_NEXT:
2069 case REQ_FIND_PREV:
2070 find_next(view, request);
2071 break;
2073 case REQ_STOP_LOADING:
2074 for (i = 0; i < ARRAY_SIZE(views); i++) {
2075 view = &views[i];
2076 if (view->pipe)
2077 report("Stopped loading the %s view", view->name),
2078 end_update(view);
2079 }
2080 break;
2082 case REQ_SHOW_VERSION:
2083 report("%s (built %s)", VERSION, __DATE__);
2084 return TRUE;
2086 case REQ_SCREEN_RESIZE:
2087 resize_display();
2088 /* Fall-through */
2089 case REQ_SCREEN_REDRAW:
2090 redraw_display();
2091 break;
2093 case REQ_NONE:
2094 doupdate();
2095 return TRUE;
2097 case REQ_VIEW_CLOSE:
2098 /* XXX: Mark closed views by letting view->parent point to the
2099 * view itself. Parents to closed view should never be
2100 * followed. */
2101 if (view->parent &&
2102 view->parent->parent != view->parent) {
2103 memset(display, 0, sizeof(display));
2104 current_view = 0;
2105 display[current_view] = view->parent;
2106 view->parent = view;
2107 resize_display();
2108 redraw_display();
2109 break;
2110 }
2111 /* Fall-through */
2112 case REQ_QUIT:
2113 return FALSE;
2115 default:
2116 /* An unknown key will show most commonly used commands. */
2117 report("Unknown key, press 'h' for help");
2118 return TRUE;
2119 }
2121 return TRUE;
2122 }
2125 /*
2126 * Pager backend
2127 */
2129 static bool
2130 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2131 {
2132 char *text = line->data;
2133 enum line_type type = line->type;
2134 int textlen = strlen(text);
2135 int attr;
2137 wmove(view->win, lineno, 0);
2139 if (view->offset + lineno == view->lineno) {
2140 if (type == LINE_COMMIT) {
2141 string_copy(view->ref, text + 7);
2142 string_copy(ref_commit, view->ref);
2143 }
2145 type = LINE_CURSOR;
2146 wchgat(view->win, -1, 0, type, NULL);
2147 }
2149 attr = get_line_attr(type);
2150 wattrset(view->win, attr);
2152 if (opt_line_number || opt_tab_size < TABSIZE) {
2153 static char spaces[] = " ";
2154 int col_offset = 0, col = 0;
2156 if (opt_line_number) {
2157 unsigned long real_lineno = view->offset + lineno + 1;
2159 if (real_lineno == 1 ||
2160 (real_lineno % opt_num_interval) == 0) {
2161 wprintw(view->win, "%.*d", view->digits, real_lineno);
2163 } else {
2164 waddnstr(view->win, spaces,
2165 MIN(view->digits, STRING_SIZE(spaces)));
2166 }
2167 waddstr(view->win, ": ");
2168 col_offset = view->digits + 2;
2169 }
2171 while (text && col_offset + col < view->width) {
2172 int cols_max = view->width - col_offset - col;
2173 char *pos = text;
2174 int cols;
2176 if (*text == '\t') {
2177 text++;
2178 assert(sizeof(spaces) > TABSIZE);
2179 pos = spaces;
2180 cols = opt_tab_size - (col % opt_tab_size);
2182 } else {
2183 text = strchr(text, '\t');
2184 cols = line ? text - pos : strlen(pos);
2185 }
2187 waddnstr(view->win, pos, MIN(cols, cols_max));
2188 col += cols;
2189 }
2191 } else {
2192 int col = 0, pos = 0;
2194 for (; pos < textlen && col < view->width; pos++, col++)
2195 if (text[pos] == '\t')
2196 col += TABSIZE - (col % TABSIZE) - 1;
2198 waddnstr(view->win, text, pos);
2199 }
2201 return TRUE;
2202 }
2204 static bool
2205 add_describe_ref(char *buf, int *bufpos, char *commit_id, const char *sep)
2206 {
2207 char refbuf[SIZEOF_STR];
2208 char *ref = NULL;
2209 FILE *pipe;
2211 if (!string_format(refbuf, "git describe %s", commit_id))
2212 return TRUE;
2214 pipe = popen(refbuf, "r");
2215 if (!pipe)
2216 return TRUE;
2218 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2219 ref = chomp_string(ref);
2220 pclose(pipe);
2222 if (!ref || !*ref)
2223 return TRUE;
2225 /* This is the only fatal call, since it can "corrupt" the buffer. */
2226 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2227 return FALSE;
2229 return TRUE;
2230 }
2232 static void
2233 add_pager_refs(struct view *view, struct line *line)
2234 {
2235 char buf[SIZEOF_STR];
2236 char *commit_id = line->data + STRING_SIZE("commit ");
2237 struct ref **refs;
2238 int bufpos = 0, refpos = 0;
2239 const char *sep = "Refs: ";
2240 bool is_tag = FALSE;
2242 assert(line->type == LINE_COMMIT);
2244 refs = get_refs(commit_id);
2245 if (!refs) {
2246 if (view == VIEW(REQ_VIEW_DIFF))
2247 goto try_add_describe_ref;
2248 return;
2249 }
2251 do {
2252 struct ref *ref = refs[refpos];
2253 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
2255 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2256 return;
2257 sep = ", ";
2258 if (ref->tag)
2259 is_tag = TRUE;
2260 } while (refs[refpos++]->next);
2262 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2263 try_add_describe_ref:
2264 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2265 return;
2266 }
2268 if (!realloc_lines(view, view->line_size + 1))
2269 return;
2271 line = &view->line[view->lines];
2272 line->data = strdup(buf);
2273 if (!line->data)
2274 return;
2276 line->type = LINE_PP_REFS;
2277 view->lines++;
2278 }
2280 static bool
2281 pager_read(struct view *view, char *data)
2282 {
2283 struct line *line = &view->line[view->lines];
2285 line->data = strdup(data);
2286 if (!line->data)
2287 return FALSE;
2289 line->type = get_line_type(line->data);
2290 view->lines++;
2292 if (line->type == LINE_COMMIT &&
2293 (view == VIEW(REQ_VIEW_DIFF) ||
2294 view == VIEW(REQ_VIEW_LOG)))
2295 add_pager_refs(view, line);
2297 return TRUE;
2298 }
2300 static bool
2301 pager_enter(struct view *view, struct line *line)
2302 {
2303 int split = 0;
2305 if (line->type == LINE_COMMIT &&
2306 (view == VIEW(REQ_VIEW_LOG) ||
2307 view == VIEW(REQ_VIEW_PAGER))) {
2308 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2309 split = 1;
2310 }
2312 /* Always scroll the view even if it was split. That way
2313 * you can use Enter to scroll through the log view and
2314 * split open each commit diff. */
2315 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2317 /* FIXME: A minor workaround. Scrolling the view will call report("")
2318 * but if we are scrolling a non-current view this won't properly
2319 * update the view title. */
2320 if (split)
2321 update_view_title(view);
2323 return TRUE;
2324 }
2326 static bool
2327 pager_grep(struct view *view, struct line *line)
2328 {
2329 regmatch_t pmatch;
2330 char *text = line->data;
2332 if (!*text)
2333 return FALSE;
2335 if (regexec(&view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2336 return FALSE;
2338 return TRUE;
2339 }
2341 static struct view_ops pager_ops = {
2342 "line",
2343 pager_draw,
2344 pager_read,
2345 pager_enter,
2346 pager_grep,
2347 };
2350 /*
2351 * Main view backend
2352 */
2354 struct commit {
2355 char id[41]; /* SHA1 ID. */
2356 char title[75]; /* First line of the commit message. */
2357 char author[75]; /* Author of the commit. */
2358 struct tm time; /* Date from the author ident. */
2359 struct ref **refs; /* Repository references. */
2360 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
2361 size_t graph_size; /* The width of the graph array. */
2362 };
2364 static bool
2365 main_draw(struct view *view, struct line *line, unsigned int lineno)
2366 {
2367 char buf[DATE_COLS + 1];
2368 struct commit *commit = line->data;
2369 enum line_type type;
2370 int col = 0;
2371 size_t timelen;
2372 size_t authorlen;
2373 int trimmed = 1;
2375 if (!*commit->author)
2376 return FALSE;
2378 wmove(view->win, lineno, col);
2380 if (view->offset + lineno == view->lineno) {
2381 string_copy(view->ref, commit->id);
2382 string_copy(ref_commit, view->ref);
2383 type = LINE_CURSOR;
2384 wattrset(view->win, get_line_attr(type));
2385 wchgat(view->win, -1, 0, type, NULL);
2387 } else {
2388 type = LINE_MAIN_COMMIT;
2389 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2390 }
2392 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2393 waddnstr(view->win, buf, timelen);
2394 waddstr(view->win, " ");
2396 col += DATE_COLS;
2397 wmove(view->win, lineno, col);
2398 if (type != LINE_CURSOR)
2399 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2401 if (opt_utf8) {
2402 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2403 } else {
2404 authorlen = strlen(commit->author);
2405 if (authorlen > AUTHOR_COLS - 2) {
2406 authorlen = AUTHOR_COLS - 2;
2407 trimmed = 1;
2408 }
2409 }
2411 if (trimmed) {
2412 waddnstr(view->win, commit->author, authorlen);
2413 if (type != LINE_CURSOR)
2414 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2415 waddch(view->win, '~');
2416 } else {
2417 waddstr(view->win, commit->author);
2418 }
2420 col += AUTHOR_COLS;
2421 if (type != LINE_CURSOR)
2422 wattrset(view->win, A_NORMAL);
2424 if (opt_rev_graph && commit->graph_size) {
2425 size_t i;
2427 wmove(view->win, lineno, col);
2428 /* Using waddch() instead of waddnstr() ensures that
2429 * they'll be rendered correctly for the cursor line. */
2430 for (i = 0; i < commit->graph_size; i++)
2431 waddch(view->win, commit->graph[i]);
2433 col += commit->graph_size + 1;
2434 }
2436 wmove(view->win, lineno, col);
2438 if (commit->refs) {
2439 size_t i = 0;
2441 do {
2442 if (type == LINE_CURSOR)
2443 ;
2444 else if (commit->refs[i]->tag)
2445 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2446 else
2447 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2448 waddstr(view->win, "[");
2449 waddstr(view->win, commit->refs[i]->name);
2450 waddstr(view->win, "]");
2451 if (type != LINE_CURSOR)
2452 wattrset(view->win, A_NORMAL);
2453 waddstr(view->win, " ");
2454 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2455 } while (commit->refs[i++]->next);
2456 }
2458 if (type != LINE_CURSOR)
2459 wattrset(view->win, get_line_attr(type));
2461 {
2462 int titlelen = strlen(commit->title);
2464 if (col + titlelen > view->width)
2465 titlelen = view->width - col;
2467 waddnstr(view->win, commit->title, titlelen);
2468 }
2470 return TRUE;
2471 }
2473 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2474 static bool
2475 main_read(struct view *view, char *line)
2476 {
2477 enum line_type type = get_line_type(line);
2478 struct commit *commit = view->lines
2479 ? view->line[view->lines - 1].data : NULL;
2481 switch (type) {
2482 case LINE_COMMIT:
2483 commit = calloc(1, sizeof(struct commit));
2484 if (!commit)
2485 return FALSE;
2487 line += STRING_SIZE("commit ");
2489 view->line[view->lines++].data = commit;
2490 string_copy(commit->id, line);
2491 commit->refs = get_refs(commit->id);
2492 commit->graph[commit->graph_size++] = ACS_LTEE;
2493 break;
2495 case LINE_AUTHOR:
2496 {
2497 char *ident = line + STRING_SIZE("author ");
2498 char *end = strchr(ident, '<');
2500 if (!commit)
2501 break;
2503 if (end) {
2504 char *email = end + 1;
2506 for (; end > ident && isspace(end[-1]); end--) ;
2508 if (end == ident && *email) {
2509 ident = email;
2510 end = strchr(ident, '>');
2511 for (; end > ident && isspace(end[-1]); end--) ;
2512 }
2513 *end = 0;
2514 }
2516 /* End is NULL or ident meaning there's no author. */
2517 if (end <= ident)
2518 ident = "Unknown";
2520 string_copy(commit->author, ident);
2522 /* Parse epoch and timezone */
2523 if (end) {
2524 char *secs = strchr(end + 1, '>');
2525 char *zone;
2526 time_t time;
2528 if (!secs || secs[1] != ' ')
2529 break;
2531 secs += 2;
2532 time = (time_t) atol(secs);
2533 zone = strchr(secs, ' ');
2534 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
2535 long tz;
2537 zone++;
2538 tz = ('0' - zone[1]) * 60 * 60 * 10;
2539 tz += ('0' - zone[2]) * 60 * 60;
2540 tz += ('0' - zone[3]) * 60;
2541 tz += ('0' - zone[4]) * 60;
2543 if (zone[0] == '-')
2544 tz = -tz;
2546 time -= tz;
2547 }
2548 gmtime_r(&time, &commit->time);
2549 }
2550 break;
2551 }
2552 default:
2553 if (!commit)
2554 break;
2556 /* Fill in the commit title if it has not already been set. */
2557 if (commit->title[0])
2558 break;
2560 /* Require titles to start with a non-space character at the
2561 * offset used by git log. */
2562 /* FIXME: More gracefull handling of titles; append "..." to
2563 * shortened titles, etc. */
2564 if (strncmp(line, " ", 4) ||
2565 isspace(line[4]))
2566 break;
2568 string_copy(commit->title, line + 4);
2569 }
2571 return TRUE;
2572 }
2574 static bool
2575 main_enter(struct view *view, struct line *line)
2576 {
2577 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2579 open_view(view, REQ_VIEW_DIFF, flags);
2580 return TRUE;
2581 }
2583 static bool
2584 main_grep(struct view *view, struct line *line)
2585 {
2586 struct commit *commit = line->data;
2587 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
2588 char buf[DATE_COLS + 1];
2589 regmatch_t pmatch;
2591 for (state = S_TITLE; state < S_END; state++) {
2592 char *text;
2594 switch (state) {
2595 case S_TITLE: text = commit->title; break;
2596 case S_AUTHOR: text = commit->author; break;
2597 case S_DATE:
2598 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
2599 continue;
2600 text = buf;
2601 break;
2603 default:
2604 return FALSE;
2605 }
2607 if (regexec(&view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
2608 return TRUE;
2609 }
2611 return FALSE;
2612 }
2614 static struct view_ops main_ops = {
2615 "commit",
2616 main_draw,
2617 main_read,
2618 main_enter,
2619 main_grep,
2620 };
2623 /*
2624 * Unicode / UTF-8 handling
2625 *
2626 * NOTE: Much of the following code for dealing with unicode is derived from
2627 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2628 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2629 */
2631 /* I've (over)annotated a lot of code snippets because I am not entirely
2632 * confident that the approach taken by this small UTF-8 interface is correct.
2633 * --jonas */
2635 static inline int
2636 unicode_width(unsigned long c)
2637 {
2638 if (c >= 0x1100 &&
2639 (c <= 0x115f /* Hangul Jamo */
2640 || c == 0x2329
2641 || c == 0x232a
2642 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
2643 /* CJK ... Yi */
2644 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
2645 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
2646 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
2647 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
2648 || (c >= 0xffe0 && c <= 0xffe6)
2649 || (c >= 0x20000 && c <= 0x2fffd)
2650 || (c >= 0x30000 && c <= 0x3fffd)))
2651 return 2;
2653 return 1;
2654 }
2656 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2657 * Illegal bytes are set one. */
2658 static const unsigned char utf8_bytes[256] = {
2659 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,
2660 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,
2661 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,
2662 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,
2663 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,
2664 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,
2665 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,
2666 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,
2667 };
2669 /* Decode UTF-8 multi-byte representation into a unicode character. */
2670 static inline unsigned long
2671 utf8_to_unicode(const char *string, size_t length)
2672 {
2673 unsigned long unicode;
2675 switch (length) {
2676 case 1:
2677 unicode = string[0];
2678 break;
2679 case 2:
2680 unicode = (string[0] & 0x1f) << 6;
2681 unicode += (string[1] & 0x3f);
2682 break;
2683 case 3:
2684 unicode = (string[0] & 0x0f) << 12;
2685 unicode += ((string[1] & 0x3f) << 6);
2686 unicode += (string[2] & 0x3f);
2687 break;
2688 case 4:
2689 unicode = (string[0] & 0x0f) << 18;
2690 unicode += ((string[1] & 0x3f) << 12);
2691 unicode += ((string[2] & 0x3f) << 6);
2692 unicode += (string[3] & 0x3f);
2693 break;
2694 case 5:
2695 unicode = (string[0] & 0x0f) << 24;
2696 unicode += ((string[1] & 0x3f) << 18);
2697 unicode += ((string[2] & 0x3f) << 12);
2698 unicode += ((string[3] & 0x3f) << 6);
2699 unicode += (string[4] & 0x3f);
2700 break;
2701 case 6:
2702 unicode = (string[0] & 0x01) << 30;
2703 unicode += ((string[1] & 0x3f) << 24);
2704 unicode += ((string[2] & 0x3f) << 18);
2705 unicode += ((string[3] & 0x3f) << 12);
2706 unicode += ((string[4] & 0x3f) << 6);
2707 unicode += (string[5] & 0x3f);
2708 break;
2709 default:
2710 die("Invalid unicode length");
2711 }
2713 /* Invalid characters could return the special 0xfffd value but NUL
2714 * should be just as good. */
2715 return unicode > 0xffff ? 0 : unicode;
2716 }
2718 /* Calculates how much of string can be shown within the given maximum width
2719 * and sets trimmed parameter to non-zero value if all of string could not be
2720 * shown.
2721 *
2722 * Additionally, adds to coloffset how many many columns to move to align with
2723 * the expected position. Takes into account how multi-byte and double-width
2724 * characters will effect the cursor position.
2725 *
2726 * Returns the number of bytes to output from string to satisfy max_width. */
2727 static size_t
2728 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2729 {
2730 const char *start = string;
2731 const char *end = strchr(string, '\0');
2732 size_t mbwidth = 0;
2733 size_t width = 0;
2735 *trimmed = 0;
2737 while (string < end) {
2738 int c = *(unsigned char *) string;
2739 unsigned char bytes = utf8_bytes[c];
2740 size_t ucwidth;
2741 unsigned long unicode;
2743 if (string + bytes > end)
2744 break;
2746 /* Change representation to figure out whether
2747 * it is a single- or double-width character. */
2749 unicode = utf8_to_unicode(string, bytes);
2750 /* FIXME: Graceful handling of invalid unicode character. */
2751 if (!unicode)
2752 break;
2754 ucwidth = unicode_width(unicode);
2755 width += ucwidth;
2756 if (width > max_width) {
2757 *trimmed = 1;
2758 break;
2759 }
2761 /* The column offset collects the differences between the
2762 * number of bytes encoding a character and the number of
2763 * columns will be used for rendering said character.
2764 *
2765 * So if some character A is encoded in 2 bytes, but will be
2766 * represented on the screen using only 1 byte this will and up
2767 * adding 1 to the multi-byte column offset.
2768 *
2769 * Assumes that no double-width character can be encoding in
2770 * less than two bytes. */
2771 if (bytes > ucwidth)
2772 mbwidth += bytes - ucwidth;
2774 string += bytes;
2775 }
2777 *coloffset += mbwidth;
2779 return string - start;
2780 }
2783 /*
2784 * Status management
2785 */
2787 /* Whether or not the curses interface has been initialized. */
2788 static bool cursed = FALSE;
2790 /* The status window is used for polling keystrokes. */
2791 static WINDOW *status_win;
2793 /* Update status and title window. */
2794 static void
2795 report(const char *msg, ...)
2796 {
2797 static bool empty = TRUE;
2798 struct view *view = display[current_view];
2800 if (!empty || *msg) {
2801 va_list args;
2803 va_start(args, msg);
2805 werase(status_win);
2806 wmove(status_win, 0, 0);
2807 if (*msg) {
2808 vwprintw(status_win, msg, args);
2809 empty = FALSE;
2810 } else {
2811 empty = TRUE;
2812 }
2813 wrefresh(status_win);
2815 va_end(args);
2816 }
2818 update_view_title(view);
2819 update_display_cursor();
2820 }
2822 /* Controls when nodelay should be in effect when polling user input. */
2823 static void
2824 set_nonblocking_input(bool loading)
2825 {
2826 static unsigned int loading_views;
2828 if ((loading == FALSE && loading_views-- == 1) ||
2829 (loading == TRUE && loading_views++ == 0))
2830 nodelay(status_win, loading);
2831 }
2833 static void
2834 init_display(void)
2835 {
2836 int x, y;
2838 /* Initialize the curses library */
2839 if (isatty(STDIN_FILENO)) {
2840 cursed = !!initscr();
2841 } else {
2842 /* Leave stdin and stdout alone when acting as a pager. */
2843 FILE *io = fopen("/dev/tty", "r+");
2845 if (!io)
2846 die("Failed to open /dev/tty");
2847 cursed = !!newterm(NULL, io, io);
2848 }
2850 if (!cursed)
2851 die("Failed to initialize curses");
2853 nonl(); /* Tell curses not to do NL->CR/NL on output */
2854 cbreak(); /* Take input chars one at a time, no wait for \n */
2855 noecho(); /* Don't echo input */
2856 leaveok(stdscr, TRUE);
2858 if (has_colors())
2859 init_colors();
2861 getmaxyx(stdscr, y, x);
2862 status_win = newwin(1, 0, y - 1, 0);
2863 if (!status_win)
2864 die("Failed to create status window");
2866 /* Enable keyboard mapping */
2867 keypad(status_win, TRUE);
2868 wbkgdset(status_win, get_line_attr(LINE_STATUS));
2869 }
2871 static char *
2872 read_prompt(const char *prompt)
2873 {
2874 enum { READING, STOP, CANCEL } status = READING;
2875 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
2876 int pos = 0;
2878 while (status == READING) {
2879 struct view *view;
2880 int i, key;
2882 foreach_view (view, i)
2883 update_view(view);
2885 report("%s%.*s", prompt, pos, buf);
2886 /* Refresh, accept single keystroke of input */
2887 key = wgetch(status_win);
2888 switch (key) {
2889 case KEY_RETURN:
2890 case KEY_ENTER:
2891 case '\n':
2892 status = pos ? STOP : CANCEL;
2893 break;
2895 case KEY_BACKSPACE:
2896 if (pos > 0)
2897 pos--;
2898 else
2899 status = CANCEL;
2900 break;
2902 case KEY_ESC:
2903 status = CANCEL;
2904 break;
2906 case ERR:
2907 break;
2909 default:
2910 if (pos >= sizeof(buf)) {
2911 report("Input string too long");
2912 return NULL;
2913 }
2915 if (isprint(key))
2916 buf[pos++] = (char) key;
2917 }
2918 }
2920 if (status == CANCEL) {
2921 /* Clear the status window */
2922 report("");
2923 return NULL;
2924 }
2926 buf[pos++] = 0;
2928 return buf;
2929 }
2931 /*
2932 * Repository references
2933 */
2935 static struct ref *refs;
2936 static size_t refs_size;
2938 /* Id <-> ref store */
2939 static struct ref ***id_refs;
2940 static size_t id_refs_size;
2942 static struct ref **
2943 get_refs(char *id)
2944 {
2945 struct ref ***tmp_id_refs;
2946 struct ref **ref_list = NULL;
2947 size_t ref_list_size = 0;
2948 size_t i;
2950 for (i = 0; i < id_refs_size; i++)
2951 if (!strcmp(id, id_refs[i][0]->id))
2952 return id_refs[i];
2954 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2955 if (!tmp_id_refs)
2956 return NULL;
2958 id_refs = tmp_id_refs;
2960 for (i = 0; i < refs_size; i++) {
2961 struct ref **tmp;
2963 if (strcmp(id, refs[i].id))
2964 continue;
2966 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2967 if (!tmp) {
2968 if (ref_list)
2969 free(ref_list);
2970 return NULL;
2971 }
2973 ref_list = tmp;
2974 if (ref_list_size > 0)
2975 ref_list[ref_list_size - 1]->next = 1;
2976 ref_list[ref_list_size] = &refs[i];
2978 /* XXX: The properties of the commit chains ensures that we can
2979 * safely modify the shared ref. The repo references will
2980 * always be similar for the same id. */
2981 ref_list[ref_list_size]->next = 0;
2982 ref_list_size++;
2983 }
2985 if (ref_list)
2986 id_refs[id_refs_size++] = ref_list;
2988 return ref_list;
2989 }
2991 static int
2992 read_ref(char *id, int idlen, char *name, int namelen)
2993 {
2994 struct ref *ref;
2995 bool tag = FALSE;
2997 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2998 /* Commits referenced by tags has "^{}" appended. */
2999 if (name[namelen - 1] != '}')
3000 return OK;
3002 while (namelen > 0 && name[namelen] != '^')
3003 namelen--;
3005 tag = TRUE;
3006 namelen -= STRING_SIZE("refs/tags/");
3007 name += STRING_SIZE("refs/tags/");
3009 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
3010 namelen -= STRING_SIZE("refs/heads/");
3011 name += STRING_SIZE("refs/heads/");
3013 } else if (!strcmp(name, "HEAD")) {
3014 return OK;
3015 }
3017 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3018 if (!refs)
3019 return ERR;
3021 ref = &refs[refs_size++];
3022 ref->name = malloc(namelen + 1);
3023 if (!ref->name)
3024 return ERR;
3026 strncpy(ref->name, name, namelen);
3027 ref->name[namelen] = 0;
3028 ref->tag = tag;
3029 string_copy(ref->id, id);
3031 return OK;
3032 }
3034 static int
3035 load_refs(void)
3036 {
3037 const char *cmd_env = getenv("TIG_LS_REMOTE");
3038 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
3040 return read_properties(popen(cmd, "r"), "\t", read_ref);
3041 }
3043 static int
3044 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
3045 {
3046 if (!strcmp(name, "i18n.commitencoding"))
3047 string_copy(opt_encoding, value);
3049 return OK;
3050 }
3052 static int
3053 load_repo_config(void)
3054 {
3055 return read_properties(popen("git repo-config --list", "r"),
3056 "=", read_repo_config_option);
3057 }
3059 static int
3060 read_properties(FILE *pipe, const char *separators,
3061 int (*read_property)(char *, int, char *, int))
3062 {
3063 char buffer[BUFSIZ];
3064 char *name;
3065 int state = OK;
3067 if (!pipe)
3068 return ERR;
3070 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
3071 char *value;
3072 size_t namelen;
3073 size_t valuelen;
3075 name = chomp_string(name);
3076 namelen = strcspn(name, separators);
3078 if (name[namelen]) {
3079 name[namelen] = 0;
3080 value = chomp_string(name + namelen + 1);
3081 valuelen = strlen(value);
3083 } else {
3084 value = "";
3085 valuelen = 0;
3086 }
3088 state = read_property(name, namelen, value, valuelen);
3089 }
3091 if (state != ERR && ferror(pipe))
3092 state = ERR;
3094 pclose(pipe);
3096 return state;
3097 }
3100 /*
3101 * Main
3102 */
3104 static void __NORETURN
3105 quit(int sig)
3106 {
3107 /* XXX: Restore tty modes and let the OS cleanup the rest! */
3108 if (cursed)
3109 endwin();
3110 exit(0);
3111 }
3113 static void __NORETURN
3114 die(const char *err, ...)
3115 {
3116 va_list args;
3118 endwin();
3120 va_start(args, err);
3121 fputs("tig: ", stderr);
3122 vfprintf(stderr, err, args);
3123 fputs("\n", stderr);
3124 va_end(args);
3126 exit(1);
3127 }
3129 int
3130 main(int argc, char *argv[])
3131 {
3132 struct view *view;
3133 enum request request;
3134 size_t i;
3136 signal(SIGINT, quit);
3138 if (setlocale(LC_ALL, "")) {
3139 string_copy(opt_codeset, nl_langinfo(CODESET));
3140 }
3142 if (load_options() == ERR)
3143 die("Failed to load user config.");
3145 /* Load the repo config file so options can be overwritten from
3146 * the command line. */
3147 if (load_repo_config() == ERR)
3148 die("Failed to load repo config.");
3150 if (!parse_options(argc, argv))
3151 return 0;
3153 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3154 opt_iconv = iconv_open(opt_codeset, opt_encoding);
3155 if (opt_iconv == (iconv_t) -1)
3156 die("Failed to initialize character set conversion");
3157 }
3159 if (load_refs() == ERR)
3160 die("Failed to load refs.");
3162 /* Require a git repository unless when running in pager mode. */
3163 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3164 die("Not a git repository");
3166 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3167 view->cmd_env = getenv(view->cmd_env);
3169 request = opt_request;
3171 init_display();
3173 while (view_driver(display[current_view], request)) {
3174 int key;
3175 int i;
3177 foreach_view (view, i)
3178 update_view(view);
3180 /* Refresh, accept single keystroke of input */
3181 key = wgetch(status_win);
3183 request = get_keybinding(display[current_view]->keymap, key);
3185 /* Some low-level request handling. This keeps access to
3186 * status_win restricted. */
3187 switch (request) {
3188 case REQ_PROMPT:
3189 {
3190 char *cmd = read_prompt(":");
3192 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3193 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3194 opt_request = REQ_VIEW_DIFF;
3195 } else {
3196 opt_request = REQ_VIEW_PAGER;
3197 }
3198 break;
3199 }
3201 request = REQ_NONE;
3202 break;
3203 }
3204 case REQ_SEARCH:
3205 case REQ_SEARCH_BACK:
3206 {
3207 const char *prompt = request == REQ_SEARCH
3208 ? "/" : "?";
3209 char *search = read_prompt(prompt);
3211 if (search)
3212 string_copy(opt_search, search);
3213 else
3214 request = REQ_NONE;
3215 break;
3216 }
3217 case REQ_SCREEN_RESIZE:
3218 {
3219 int height, width;
3221 getmaxyx(stdscr, height, width);
3223 /* Resize the status view and let the view driver take
3224 * care of resizing the displayed views. */
3225 wresize(status_win, 1, width);
3226 mvwin(status_win, height - 1, 0);
3227 wrefresh(status_win);
3228 break;
3229 }
3230 default:
3231 break;
3232 }
3233 }
3235 quit(0);
3237 return 0;
3238 }