8549620769546a21d5f1a90712336025ac063874
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_REV 41 /* Holds a SHA-1 and an ending NUL */
64 /* Revision graph */
66 #define REVGRAPH_INIT 'I'
67 #define REVGRAPH_MERGE 'M'
68 #define REVGRAPH_BRANCH '+'
69 #define REVGRAPH_COMMIT '*'
70 #define REVGRAPH_LINE '|'
72 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
74 /* Size of rev graph with no "padding" columns */
75 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
77 /* This color name can be used to refer to the default term colors. */
78 #define COLOR_DEFAULT (-1)
80 #define ICONV_NONE ((iconv_t) -1)
82 /* The format and size of the date column in the main view. */
83 #define DATE_FORMAT "%Y-%m-%d %H:%M"
84 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
86 #define AUTHOR_COLS 20
88 /* The default interval between line numbers. */
89 #define NUMBER_INTERVAL 1
91 #define TABSIZE 8
93 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
95 #define TIG_LS_REMOTE \
96 "git ls-remote . 2>/dev/null"
98 #define TIG_DIFF_CMD \
99 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
101 #define TIG_LOG_CMD \
102 "git log --cc --stat -n100 %s 2>/dev/null"
104 #define TIG_MAIN_CMD \
105 "git log --topo-order --pretty=raw %s 2>/dev/null"
107 #define TIG_TREE_CMD \
108 "git ls-tree %s %s"
110 #define TIG_BLOB_CMD \
111 "git cat-file blob %s"
113 /* XXX: Needs to be defined to the empty string. */
114 #define TIG_HELP_CMD ""
115 #define TIG_PAGER_CMD ""
117 /* Some ascii-shorthands fitted into the ncurses namespace. */
118 #define KEY_TAB '\t'
119 #define KEY_RETURN '\r'
120 #define KEY_ESC 27
123 struct ref {
124 char *name; /* Ref name; tag or head names are shortened. */
125 char id[SIZEOF_REV]; /* Commit SHA1 ID */
126 unsigned int tag:1; /* Is it a tag? */
127 unsigned int next:1; /* For ref lists: are there more refs? */
128 };
130 static struct ref **get_refs(char *id);
132 struct int_map {
133 const char *name;
134 int namelen;
135 int value;
136 };
138 static int
139 set_from_int_map(struct int_map *map, size_t map_size,
140 int *value, const char *name, int namelen)
141 {
143 int i;
145 for (i = 0; i < map_size; i++)
146 if (namelen == map[i].namelen &&
147 !strncasecmp(name, map[i].name, namelen)) {
148 *value = map[i].value;
149 return OK;
150 }
152 return ERR;
153 }
156 /*
157 * String helpers
158 */
160 static inline void
161 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
162 {
163 if (srclen > dstlen - 1)
164 srclen = dstlen - 1;
166 strncpy(dst, src, srclen);
167 dst[srclen] = 0;
168 }
170 /* Shorthands for safely copying into a fixed buffer. */
172 #define string_copy(dst, src) \
173 string_ncopy_do(dst, sizeof(dst), src, sizeof(dst))
175 #define string_ncopy(dst, src, srclen) \
176 string_ncopy_do(dst, sizeof(dst), src, srclen)
178 static char *
179 chomp_string(char *name)
180 {
181 int namelen;
183 while (isspace(*name))
184 name++;
186 namelen = strlen(name) - 1;
187 while (namelen > 0 && isspace(name[namelen]))
188 name[namelen--] = 0;
190 return name;
191 }
193 static bool
194 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
195 {
196 va_list args;
197 size_t pos = bufpos ? *bufpos : 0;
199 va_start(args, fmt);
200 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
201 va_end(args);
203 if (bufpos)
204 *bufpos = pos;
206 return pos >= bufsize ? FALSE : TRUE;
207 }
209 #define string_format(buf, fmt, args...) \
210 string_nformat(buf, sizeof(buf), NULL, fmt, args)
212 #define string_format_from(buf, from, fmt, args...) \
213 string_nformat(buf, sizeof(buf), from, fmt, args)
215 static int
216 string_enum_compare(const char *str1, const char *str2, int len)
217 {
218 size_t i;
220 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
222 /* Diff-Header == DIFF_HEADER */
223 for (i = 0; i < len; i++) {
224 if (toupper(str1[i]) == toupper(str2[i]))
225 continue;
227 if (string_enum_sep(str1[i]) &&
228 string_enum_sep(str2[i]))
229 continue;
231 return str1[i] - str2[i];
232 }
234 return 0;
235 }
237 /* Shell quoting
238 *
239 * NOTE: The following is a slightly modified copy of the git project's shell
240 * quoting routines found in the quote.c file.
241 *
242 * Help to copy the thing properly quoted for the shell safety. any single
243 * quote is replaced with '\'', any exclamation point is replaced with '\!',
244 * and the whole thing is enclosed in a
245 *
246 * E.g.
247 * original sq_quote result
248 * name ==> name ==> 'name'
249 * a b ==> a b ==> 'a b'
250 * a'b ==> a'\''b ==> 'a'\''b'
251 * a!b ==> a'\!'b ==> 'a'\!'b'
252 */
254 static size_t
255 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
256 {
257 char c;
259 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
261 BUFPUT('\'');
262 while ((c = *src++)) {
263 if (c == '\'' || c == '!') {
264 BUFPUT('\'');
265 BUFPUT('\\');
266 BUFPUT(c);
267 BUFPUT('\'');
268 } else {
269 BUFPUT(c);
270 }
271 }
272 BUFPUT('\'');
274 return bufsize;
275 }
278 /*
279 * User requests
280 */
282 #define REQ_INFO \
283 /* XXX: Keep the view request first and in sync with views[]. */ \
284 REQ_GROUP("View switching") \
285 REQ_(VIEW_MAIN, "Show main view"), \
286 REQ_(VIEW_DIFF, "Show diff view"), \
287 REQ_(VIEW_LOG, "Show log view"), \
288 REQ_(VIEW_TREE, "Show tree view"), \
289 REQ_(VIEW_BLOB, "Show blob view"), \
290 REQ_(VIEW_HELP, "Show help page"), \
291 REQ_(VIEW_PAGER, "Show pager view"), \
292 \
293 REQ_GROUP("View manipulation") \
294 REQ_(ENTER, "Enter current line and scroll"), \
295 REQ_(NEXT, "Move to next"), \
296 REQ_(PREVIOUS, "Move to previous"), \
297 REQ_(VIEW_NEXT, "Move focus to next view"), \
298 REQ_(VIEW_CLOSE, "Close the current view"), \
299 REQ_(QUIT, "Close all views and quit"), \
300 \
301 REQ_GROUP("Cursor navigation") \
302 REQ_(MOVE_UP, "Move cursor one line up"), \
303 REQ_(MOVE_DOWN, "Move cursor one line down"), \
304 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
305 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
306 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
307 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
308 \
309 REQ_GROUP("Scrolling") \
310 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
311 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
312 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
313 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
314 \
315 REQ_GROUP("Searching") \
316 REQ_(SEARCH, "Search the view"), \
317 REQ_(SEARCH_BACK, "Search backwards in the view"), \
318 REQ_(FIND_NEXT, "Find next search match"), \
319 REQ_(FIND_PREV, "Find previous search match"), \
320 \
321 REQ_GROUP("Misc") \
322 REQ_(NONE, "Do nothing"), \
323 REQ_(PROMPT, "Bring up the prompt"), \
324 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
325 REQ_(SCREEN_RESIZE, "Resize the screen"), \
326 REQ_(SHOW_VERSION, "Show version information"), \
327 REQ_(STOP_LOADING, "Stop all loading views"), \
328 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
329 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization")
332 /* User action requests. */
333 enum request {
334 #define REQ_GROUP(help)
335 #define REQ_(req, help) REQ_##req
337 /* Offset all requests to avoid conflicts with ncurses getch values. */
338 REQ_OFFSET = KEY_MAX + 1,
339 REQ_INFO,
340 REQ_UNKNOWN,
342 #undef REQ_GROUP
343 #undef REQ_
344 };
346 struct request_info {
347 enum request request;
348 char *name;
349 int namelen;
350 char *help;
351 };
353 static struct request_info req_info[] = {
354 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
355 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
356 REQ_INFO
357 #undef REQ_GROUP
358 #undef REQ_
359 };
361 static enum request
362 get_request(const char *name)
363 {
364 int namelen = strlen(name);
365 int i;
367 for (i = 0; i < ARRAY_SIZE(req_info); i++)
368 if (req_info[i].namelen == namelen &&
369 !string_enum_compare(req_info[i].name, name, namelen))
370 return req_info[i].request;
372 return REQ_UNKNOWN;
373 }
376 /*
377 * Options
378 */
380 static const char usage[] =
381 VERSION " (" __DATE__ ")\n"
382 "\n"
383 "Usage: tig [options]\n"
384 " or: tig [options] [--] [git log options]\n"
385 " or: tig [options] log [git log options]\n"
386 " or: tig [options] diff [git diff options]\n"
387 " or: tig [options] show [git show options]\n"
388 " or: tig [options] < [git command output]\n"
389 "\n"
390 "Options:\n"
391 " -l Start up in log view\n"
392 " -d Start up in diff view\n"
393 " -n[I], --line-number[=I] Show line numbers with given interval\n"
394 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
395 " -- Mark end of tig options\n"
396 " -v, --version Show version and exit\n"
397 " -h, --help Show help message and exit\n";
399 /* Option and state variables. */
400 static bool opt_line_number = FALSE;
401 static bool opt_rev_graph = TRUE;
402 static int opt_num_interval = NUMBER_INTERVAL;
403 static int opt_tab_size = TABSIZE;
404 static enum request opt_request = REQ_VIEW_MAIN;
405 static char opt_cmd[SIZEOF_STR] = "";
406 static char opt_path[SIZEOF_STR] = "";
407 static FILE *opt_pipe = NULL;
408 static char opt_encoding[20] = "UTF-8";
409 static bool opt_utf8 = TRUE;
410 static char opt_codeset[20] = "UTF-8";
411 static iconv_t opt_iconv = ICONV_NONE;
412 static char opt_search[SIZEOF_STR] = "";
414 enum option_type {
415 OPT_NONE,
416 OPT_INT,
417 };
419 static bool
420 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
421 {
422 va_list args;
423 char *value = "";
424 int *number;
426 if (opt[0] != '-')
427 return FALSE;
429 if (opt[1] == '-') {
430 int namelen = strlen(name);
432 opt += 2;
434 if (strncmp(opt, name, namelen))
435 return FALSE;
437 if (opt[namelen] == '=')
438 value = opt + namelen + 1;
440 } else {
441 if (!short_name || opt[1] != short_name)
442 return FALSE;
443 value = opt + 2;
444 }
446 va_start(args, type);
447 if (type == OPT_INT) {
448 number = va_arg(args, int *);
449 if (isdigit(*value))
450 *number = atoi(value);
451 }
452 va_end(args);
454 return TRUE;
455 }
457 /* Returns the index of log or diff command or -1 to exit. */
458 static bool
459 parse_options(int argc, char *argv[])
460 {
461 int i;
463 for (i = 1; i < argc; i++) {
464 char *opt = argv[i];
466 if (!strcmp(opt, "-l")) {
467 opt_request = REQ_VIEW_LOG;
468 continue;
469 }
471 if (!strcmp(opt, "-d")) {
472 opt_request = REQ_VIEW_DIFF;
473 continue;
474 }
476 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
477 opt_line_number = TRUE;
478 continue;
479 }
481 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
482 opt_tab_size = MIN(opt_tab_size, TABSIZE);
483 continue;
484 }
486 if (check_option(opt, 'v', "version", OPT_NONE)) {
487 printf("tig version %s\n", VERSION);
488 return FALSE;
489 }
491 if (check_option(opt, 'h', "help", OPT_NONE)) {
492 printf(usage);
493 return FALSE;
494 }
496 if (!strcmp(opt, "--")) {
497 i++;
498 break;
499 }
501 if (!strcmp(opt, "log") ||
502 !strcmp(opt, "diff") ||
503 !strcmp(opt, "show")) {
504 opt_request = opt[0] == 'l'
505 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
506 break;
507 }
509 if (opt[0] && opt[0] != '-')
510 break;
512 die("unknown option '%s'\n\n%s", opt, usage);
513 }
515 if (!isatty(STDIN_FILENO)) {
516 opt_request = REQ_VIEW_PAGER;
517 opt_pipe = stdin;
519 } else if (i < argc) {
520 size_t buf_size;
522 if (opt_request == REQ_VIEW_MAIN)
523 /* XXX: This is vulnerable to the user overriding
524 * options required for the main view parser. */
525 string_copy(opt_cmd, "git log --stat --pretty=raw");
526 else
527 string_copy(opt_cmd, "git");
528 buf_size = strlen(opt_cmd);
530 while (buf_size < sizeof(opt_cmd) && i < argc) {
531 opt_cmd[buf_size++] = ' ';
532 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
533 }
535 if (buf_size >= sizeof(opt_cmd))
536 die("command too long");
538 opt_cmd[buf_size] = 0;
540 }
542 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
543 opt_utf8 = FALSE;
545 return TRUE;
546 }
549 /*
550 * Line-oriented content detection.
551 */
553 #define LINE_INFO \
554 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
555 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
556 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
557 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
558 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
559 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
560 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
561 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
562 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
563 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
564 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
565 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
566 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
567 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
568 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
569 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
570 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
571 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
572 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
573 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
574 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
575 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
576 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
577 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
578 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
579 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
580 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
581 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
582 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
583 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
584 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
585 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
586 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
587 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
588 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
589 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
590 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
591 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
592 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
593 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
594 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL)
596 enum line_type {
597 #define LINE(type, line, fg, bg, attr) \
598 LINE_##type
599 LINE_INFO
600 #undef LINE
601 };
603 struct line_info {
604 const char *name; /* Option name. */
605 int namelen; /* Size of option name. */
606 const char *line; /* The start of line to match. */
607 int linelen; /* Size of string to match. */
608 int fg, bg, attr; /* Color and text attributes for the lines. */
609 };
611 static struct line_info line_info[] = {
612 #define LINE(type, line, fg, bg, attr) \
613 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
614 LINE_INFO
615 #undef LINE
616 };
618 static enum line_type
619 get_line_type(char *line)
620 {
621 int linelen = strlen(line);
622 enum line_type type;
624 for (type = 0; type < ARRAY_SIZE(line_info); type++)
625 /* Case insensitive search matches Signed-off-by lines better. */
626 if (linelen >= line_info[type].linelen &&
627 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
628 return type;
630 return LINE_DEFAULT;
631 }
633 static inline int
634 get_line_attr(enum line_type type)
635 {
636 assert(type < ARRAY_SIZE(line_info));
637 return COLOR_PAIR(type) | line_info[type].attr;
638 }
640 static struct line_info *
641 get_line_info(char *name, int namelen)
642 {
643 enum line_type type;
645 for (type = 0; type < ARRAY_SIZE(line_info); type++)
646 if (namelen == line_info[type].namelen &&
647 !string_enum_compare(line_info[type].name, name, namelen))
648 return &line_info[type];
650 return NULL;
651 }
653 static void
654 init_colors(void)
655 {
656 int default_bg = COLOR_BLACK;
657 int default_fg = COLOR_WHITE;
658 enum line_type type;
660 start_color();
662 if (use_default_colors() != ERR) {
663 default_bg = -1;
664 default_fg = -1;
665 }
667 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
668 struct line_info *info = &line_info[type];
669 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
670 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
672 init_pair(type, fg, bg);
673 }
674 }
676 struct line {
677 enum line_type type;
679 /* State flags */
680 unsigned int selected:1;
682 void *data; /* User data */
683 };
686 /*
687 * Keys
688 */
690 struct keybinding {
691 int alias;
692 enum request request;
693 struct keybinding *next;
694 };
696 static struct keybinding default_keybindings[] = {
697 /* View switching */
698 { 'm', REQ_VIEW_MAIN },
699 { 'd', REQ_VIEW_DIFF },
700 { 'l', REQ_VIEW_LOG },
701 { 't', REQ_VIEW_TREE },
702 { 'f', REQ_VIEW_BLOB },
703 { 'p', REQ_VIEW_PAGER },
704 { 'h', REQ_VIEW_HELP },
706 /* View manipulation */
707 { 'q', REQ_VIEW_CLOSE },
708 { KEY_TAB, REQ_VIEW_NEXT },
709 { KEY_RETURN, REQ_ENTER },
710 { KEY_UP, REQ_PREVIOUS },
711 { KEY_DOWN, REQ_NEXT },
713 /* Cursor navigation */
714 { 'k', REQ_MOVE_UP },
715 { 'j', REQ_MOVE_DOWN },
716 { KEY_HOME, REQ_MOVE_FIRST_LINE },
717 { KEY_END, REQ_MOVE_LAST_LINE },
718 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
719 { ' ', REQ_MOVE_PAGE_DOWN },
720 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
721 { 'b', REQ_MOVE_PAGE_UP },
722 { '-', REQ_MOVE_PAGE_UP },
724 /* Scrolling */
725 { KEY_IC, REQ_SCROLL_LINE_UP },
726 { KEY_DC, REQ_SCROLL_LINE_DOWN },
727 { 'w', REQ_SCROLL_PAGE_UP },
728 { 's', REQ_SCROLL_PAGE_DOWN },
730 /* Searching */
731 { '/', REQ_SEARCH },
732 { '?', REQ_SEARCH_BACK },
733 { 'n', REQ_FIND_NEXT },
734 { 'N', REQ_FIND_PREV },
736 /* Misc */
737 { 'Q', REQ_QUIT },
738 { 'z', REQ_STOP_LOADING },
739 { 'v', REQ_SHOW_VERSION },
740 { 'r', REQ_SCREEN_REDRAW },
741 { '.', REQ_TOGGLE_LINENO },
742 { 'g', REQ_TOGGLE_REV_GRAPH },
743 { ':', REQ_PROMPT },
745 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
746 { ERR, REQ_NONE },
748 /* Using the ncurses SIGWINCH handler. */
749 { KEY_RESIZE, REQ_SCREEN_RESIZE },
750 };
752 #define KEYMAP_INFO \
753 KEYMAP_(GENERIC), \
754 KEYMAP_(MAIN), \
755 KEYMAP_(DIFF), \
756 KEYMAP_(LOG), \
757 KEYMAP_(TREE), \
758 KEYMAP_(BLOB), \
759 KEYMAP_(PAGER), \
760 KEYMAP_(HELP) \
762 enum keymap {
763 #define KEYMAP_(name) KEYMAP_##name
764 KEYMAP_INFO
765 #undef KEYMAP_
766 };
768 static struct int_map keymap_table[] = {
769 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
770 KEYMAP_INFO
771 #undef KEYMAP_
772 };
774 #define set_keymap(map, name) \
775 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
777 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
779 static void
780 add_keybinding(enum keymap keymap, enum request request, int key)
781 {
782 struct keybinding *keybinding;
784 keybinding = calloc(1, sizeof(*keybinding));
785 if (!keybinding)
786 die("Failed to allocate keybinding");
788 keybinding->alias = key;
789 keybinding->request = request;
790 keybinding->next = keybindings[keymap];
791 keybindings[keymap] = keybinding;
792 }
794 /* Looks for a key binding first in the given map, then in the generic map, and
795 * lastly in the default keybindings. */
796 static enum request
797 get_keybinding(enum keymap keymap, int key)
798 {
799 struct keybinding *kbd;
800 int i;
802 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
803 if (kbd->alias == key)
804 return kbd->request;
806 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
807 if (kbd->alias == key)
808 return kbd->request;
810 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
811 if (default_keybindings[i].alias == key)
812 return default_keybindings[i].request;
814 return (enum request) key;
815 }
818 struct key {
819 char *name;
820 int value;
821 };
823 static struct key key_table[] = {
824 { "Enter", KEY_RETURN },
825 { "Space", ' ' },
826 { "Backspace", KEY_BACKSPACE },
827 { "Tab", KEY_TAB },
828 { "Escape", KEY_ESC },
829 { "Left", KEY_LEFT },
830 { "Right", KEY_RIGHT },
831 { "Up", KEY_UP },
832 { "Down", KEY_DOWN },
833 { "Insert", KEY_IC },
834 { "Delete", KEY_DC },
835 { "Hash", '#' },
836 { "Home", KEY_HOME },
837 { "End", KEY_END },
838 { "PageUp", KEY_PPAGE },
839 { "PageDown", KEY_NPAGE },
840 { "F1", KEY_F(1) },
841 { "F2", KEY_F(2) },
842 { "F3", KEY_F(3) },
843 { "F4", KEY_F(4) },
844 { "F5", KEY_F(5) },
845 { "F6", KEY_F(6) },
846 { "F7", KEY_F(7) },
847 { "F8", KEY_F(8) },
848 { "F9", KEY_F(9) },
849 { "F10", KEY_F(10) },
850 { "F11", KEY_F(11) },
851 { "F12", KEY_F(12) },
852 };
854 static int
855 get_key_value(const char *name)
856 {
857 int i;
859 for (i = 0; i < ARRAY_SIZE(key_table); i++)
860 if (!strcasecmp(key_table[i].name, name))
861 return key_table[i].value;
863 if (strlen(name) == 1 && isprint(*name))
864 return (int) *name;
866 return ERR;
867 }
869 static char *
870 get_key(enum request request)
871 {
872 static char buf[BUFSIZ];
873 static char key_char[] = "'X'";
874 size_t pos = 0;
875 char *sep = " ";
876 int i;
878 buf[pos] = 0;
880 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
881 struct keybinding *keybinding = &default_keybindings[i];
882 char *seq = NULL;
883 int key;
885 if (keybinding->request != request)
886 continue;
888 for (key = 0; key < ARRAY_SIZE(key_table); key++)
889 if (key_table[key].value == keybinding->alias)
890 seq = key_table[key].name;
892 if (seq == NULL &&
893 keybinding->alias < 127 &&
894 isprint(keybinding->alias)) {
895 key_char[1] = (char) keybinding->alias;
896 seq = key_char;
897 }
899 if (!seq)
900 seq = "'?'";
902 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
903 return "Too many keybindings!";
904 sep = ", ";
905 }
907 return buf;
908 }
911 /*
912 * User config file handling.
913 */
915 static struct int_map color_map[] = {
916 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
917 COLOR_MAP(DEFAULT),
918 COLOR_MAP(BLACK),
919 COLOR_MAP(BLUE),
920 COLOR_MAP(CYAN),
921 COLOR_MAP(GREEN),
922 COLOR_MAP(MAGENTA),
923 COLOR_MAP(RED),
924 COLOR_MAP(WHITE),
925 COLOR_MAP(YELLOW),
926 };
928 #define set_color(color, name) \
929 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
931 static struct int_map attr_map[] = {
932 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
933 ATTR_MAP(NORMAL),
934 ATTR_MAP(BLINK),
935 ATTR_MAP(BOLD),
936 ATTR_MAP(DIM),
937 ATTR_MAP(REVERSE),
938 ATTR_MAP(STANDOUT),
939 ATTR_MAP(UNDERLINE),
940 };
942 #define set_attribute(attr, name) \
943 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
945 static int config_lineno;
946 static bool config_errors;
947 static char *config_msg;
949 /* Wants: object fgcolor bgcolor [attr] */
950 static int
951 option_color_command(int argc, char *argv[])
952 {
953 struct line_info *info;
955 if (argc != 3 && argc != 4) {
956 config_msg = "Wrong number of arguments given to color command";
957 return ERR;
958 }
960 info = get_line_info(argv[0], strlen(argv[0]));
961 if (!info) {
962 config_msg = "Unknown color name";
963 return ERR;
964 }
966 if (set_color(&info->fg, argv[1]) == ERR ||
967 set_color(&info->bg, argv[2]) == ERR) {
968 config_msg = "Unknown color";
969 return ERR;
970 }
972 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
973 config_msg = "Unknown attribute";
974 return ERR;
975 }
977 return OK;
978 }
980 /* Wants: name = value */
981 static int
982 option_set_command(int argc, char *argv[])
983 {
984 if (argc != 3) {
985 config_msg = "Wrong number of arguments given to set command";
986 return ERR;
987 }
989 if (strcmp(argv[1], "=")) {
990 config_msg = "No value assigned";
991 return ERR;
992 }
994 if (!strcmp(argv[0], "show-rev-graph")) {
995 opt_rev_graph = (!strcmp(argv[2], "1") ||
996 !strcmp(argv[2], "true") ||
997 !strcmp(argv[2], "yes"));
998 return OK;
999 }
1001 if (!strcmp(argv[0], "line-number-interval")) {
1002 opt_num_interval = atoi(argv[2]);
1003 return OK;
1004 }
1006 if (!strcmp(argv[0], "tab-size")) {
1007 opt_tab_size = atoi(argv[2]);
1008 return OK;
1009 }
1011 if (!strcmp(argv[0], "commit-encoding")) {
1012 char *arg = argv[2];
1013 int delimiter = *arg;
1014 int i;
1016 switch (delimiter) {
1017 case '"':
1018 case '\'':
1019 for (arg++, i = 0; arg[i]; i++)
1020 if (arg[i] == delimiter) {
1021 arg[i] = 0;
1022 break;
1023 }
1024 default:
1025 string_copy(opt_encoding, arg);
1026 return OK;
1027 }
1028 }
1030 config_msg = "Unknown variable name";
1031 return ERR;
1032 }
1034 /* Wants: mode request key */
1035 static int
1036 option_bind_command(int argc, char *argv[])
1037 {
1038 enum request request;
1039 int keymap;
1040 int key;
1042 if (argc != 3) {
1043 config_msg = "Wrong number of arguments given to bind command";
1044 return ERR;
1045 }
1047 if (set_keymap(&keymap, argv[0]) == ERR) {
1048 config_msg = "Unknown key map";
1049 return ERR;
1050 }
1052 key = get_key_value(argv[1]);
1053 if (key == ERR) {
1054 config_msg = "Unknown key";
1055 return ERR;
1056 }
1058 request = get_request(argv[2]);
1059 if (request == REQ_UNKNOWN) {
1060 config_msg = "Unknown request name";
1061 return ERR;
1062 }
1064 add_keybinding(keymap, request, key);
1066 return OK;
1067 }
1069 static int
1070 set_option(char *opt, char *value)
1071 {
1072 char *argv[16];
1073 int valuelen;
1074 int argc = 0;
1076 /* Tokenize */
1077 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1078 argv[argc++] = value;
1080 value += valuelen;
1081 if (!*value)
1082 break;
1084 *value++ = 0;
1085 while (isspace(*value))
1086 value++;
1087 }
1089 if (!strcmp(opt, "color"))
1090 return option_color_command(argc, argv);
1092 if (!strcmp(opt, "set"))
1093 return option_set_command(argc, argv);
1095 if (!strcmp(opt, "bind"))
1096 return option_bind_command(argc, argv);
1098 config_msg = "Unknown option command";
1099 return ERR;
1100 }
1102 static int
1103 read_option(char *opt, int optlen, char *value, int valuelen)
1104 {
1105 int status = OK;
1107 config_lineno++;
1108 config_msg = "Internal error";
1110 /* Check for comment markers, since read_properties() will
1111 * only ensure opt and value are split at first " \t". */
1112 optlen = strcspn(opt, "#");
1113 if (optlen == 0)
1114 return OK;
1116 if (opt[optlen] != 0) {
1117 config_msg = "No option value";
1118 status = ERR;
1120 } else {
1121 /* Look for comment endings in the value. */
1122 int len = strcspn(value, "#");
1124 if (len < valuelen) {
1125 valuelen = len;
1126 value[valuelen] = 0;
1127 }
1129 status = set_option(opt, value);
1130 }
1132 if (status == ERR) {
1133 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1134 config_lineno, optlen, opt, config_msg);
1135 config_errors = TRUE;
1136 }
1138 /* Always keep going if errors are encountered. */
1139 return OK;
1140 }
1142 static int
1143 load_options(void)
1144 {
1145 char *home = getenv("HOME");
1146 char buf[SIZEOF_STR];
1147 FILE *file;
1149 config_lineno = 0;
1150 config_errors = FALSE;
1152 if (!home || !string_format(buf, "%s/.tigrc", home))
1153 return ERR;
1155 /* It's ok that the file doesn't exist. */
1156 file = fopen(buf, "r");
1157 if (!file)
1158 return OK;
1160 if (read_properties(file, " \t", read_option) == ERR ||
1161 config_errors == TRUE)
1162 fprintf(stderr, "Errors while loading %s.\n", buf);
1164 return OK;
1165 }
1168 /*
1169 * The viewer
1170 */
1172 struct view;
1173 struct view_ops;
1175 /* The display array of active views and the index of the current view. */
1176 static struct view *display[2];
1177 static unsigned int current_view;
1179 #define foreach_displayed_view(view, i) \
1180 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1182 #define displayed_views() (display[1] != NULL ? 2 : 1)
1184 /* Current head and commit ID */
1185 static char ref_blob[SIZEOF_REF] = "";
1186 static char ref_commit[SIZEOF_REF] = "HEAD";
1187 static char ref_head[SIZEOF_REF] = "HEAD";
1189 struct view {
1190 const char *name; /* View name */
1191 const char *cmd_fmt; /* Default command line format */
1192 const char *cmd_env; /* Command line set via environment */
1193 const char *id; /* Points to either of ref_{head,commit,blob} */
1195 struct view_ops *ops; /* View operations */
1197 enum keymap keymap; /* What keymap does this view have */
1199 char cmd[SIZEOF_STR]; /* Command buffer */
1200 char ref[SIZEOF_REF]; /* Hovered commit reference */
1201 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1203 int height, width; /* The width and height of the main window */
1204 WINDOW *win; /* The main window */
1205 WINDOW *title; /* The title window living below the main window */
1207 /* Navigation */
1208 unsigned long offset; /* Offset of the window top */
1209 unsigned long lineno; /* Current line number */
1211 /* Searching */
1212 char grep[SIZEOF_STR]; /* Search string */
1213 regex_t *regex; /* Pre-compiled regex */
1215 /* If non-NULL, points to the view that opened this view. If this view
1216 * is closed tig will switch back to the parent view. */
1217 struct view *parent;
1219 /* Buffering */
1220 unsigned long lines; /* Total number of lines */
1221 struct line *line; /* Line index */
1222 unsigned long line_size;/* Total number of allocated lines */
1223 unsigned int digits; /* Number of digits in the lines member. */
1225 /* Loading */
1226 FILE *pipe;
1227 time_t start_time;
1228 };
1230 struct view_ops {
1231 /* What type of content being displayed. Used in the title bar. */
1232 const char *type;
1233 /* Draw one line; @lineno must be < view->height. */
1234 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1235 /* Read one line; updates view->line. */
1236 bool (*read)(struct view *view, char *data);
1237 /* Depending on view, change display based on current line. */
1238 bool (*enter)(struct view *view, struct line *line);
1239 /* Search for regex in a line. */
1240 bool (*grep)(struct view *view, struct line *line);
1241 /* Select line */
1242 void (*select)(struct view *view, struct line *line);
1243 };
1245 static struct view_ops pager_ops;
1246 static struct view_ops main_ops;
1247 static struct view_ops tree_ops;
1248 static struct view_ops blob_ops;
1250 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1251 { name, cmd, #env, ref, ops, map}
1253 #define VIEW_(id, name, ops, ref) \
1254 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1257 static struct view views[] = {
1258 VIEW_(MAIN, "main", &main_ops, ref_head),
1259 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1260 VIEW_(LOG, "log", &pager_ops, ref_head),
1261 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1262 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1263 VIEW_(HELP, "help", &pager_ops, "static"),
1264 VIEW_(PAGER, "pager", &pager_ops, "static"),
1265 };
1267 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1269 #define foreach_view(view, i) \
1270 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1272 #define view_is_displayed(view) \
1273 (view == display[0] || view == display[1])
1275 static bool
1276 draw_view_line(struct view *view, unsigned int lineno)
1277 {
1278 struct line *line;
1279 bool selected = (view->offset + lineno == view->lineno);
1281 assert(view_is_displayed(view));
1283 if (view->offset + lineno >= view->lines)
1284 return FALSE;
1286 line = &view->line[view->offset + lineno];
1288 if (selected) {
1289 line->selected = TRUE;
1290 view->ops->select(view, line);
1291 } else if (line->selected) {
1292 line->selected = FALSE;
1293 wmove(view->win, lineno, 0);
1294 wclrtoeol(view->win);
1295 }
1297 return view->ops->draw(view, line, lineno, selected);
1298 }
1300 static void
1301 redraw_view_from(struct view *view, int lineno)
1302 {
1303 assert(0 <= lineno && lineno < view->height);
1305 for (; lineno < view->height; lineno++) {
1306 if (!draw_view_line(view, lineno))
1307 break;
1308 }
1310 redrawwin(view->win);
1311 wrefresh(view->win);
1312 }
1314 static void
1315 redraw_view(struct view *view)
1316 {
1317 wclear(view->win);
1318 redraw_view_from(view, 0);
1319 }
1322 static void
1323 update_view_title(struct view *view)
1324 {
1325 assert(view_is_displayed(view));
1327 if (view == display[current_view])
1328 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1329 else
1330 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1332 werase(view->title);
1333 wmove(view->title, 0, 0);
1335 if (*view->ref)
1336 wprintw(view->title, "[%s] %s", view->name, view->ref);
1337 else
1338 wprintw(view->title, "[%s]", view->name);
1340 if (view->lines || view->pipe) {
1341 unsigned int view_lines = view->offset + view->height;
1342 unsigned int lines = view->lines
1343 ? MIN(view_lines, view->lines) * 100 / view->lines
1344 : 0;
1346 wprintw(view->title, " - %s %d of %d (%d%%)",
1347 view->ops->type,
1348 view->lineno + 1,
1349 view->lines,
1350 lines);
1351 }
1353 if (view->pipe) {
1354 time_t secs = time(NULL) - view->start_time;
1356 /* Three git seconds are a long time ... */
1357 if (secs > 2)
1358 wprintw(view->title, " %lds", secs);
1359 }
1361 wmove(view->title, 0, view->width - 1);
1362 wrefresh(view->title);
1363 }
1365 static void
1366 resize_display(void)
1367 {
1368 int offset, i;
1369 struct view *base = display[0];
1370 struct view *view = display[1] ? display[1] : display[0];
1372 /* Setup window dimensions */
1374 getmaxyx(stdscr, base->height, base->width);
1376 /* Make room for the status window. */
1377 base->height -= 1;
1379 if (view != base) {
1380 /* Horizontal split. */
1381 view->width = base->width;
1382 view->height = SCALE_SPLIT_VIEW(base->height);
1383 base->height -= view->height;
1385 /* Make room for the title bar. */
1386 view->height -= 1;
1387 }
1389 /* Make room for the title bar. */
1390 base->height -= 1;
1392 offset = 0;
1394 foreach_displayed_view (view, i) {
1395 if (!view->win) {
1396 view->win = newwin(view->height, 0, offset, 0);
1397 if (!view->win)
1398 die("Failed to create %s view", view->name);
1400 scrollok(view->win, TRUE);
1402 view->title = newwin(1, 0, offset + view->height, 0);
1403 if (!view->title)
1404 die("Failed to create title window");
1406 } else {
1407 wresize(view->win, view->height, view->width);
1408 mvwin(view->win, offset, 0);
1409 mvwin(view->title, offset + view->height, 0);
1410 }
1412 offset += view->height + 1;
1413 }
1414 }
1416 static void
1417 redraw_display(void)
1418 {
1419 struct view *view;
1420 int i;
1422 foreach_displayed_view (view, i) {
1423 redraw_view(view);
1424 update_view_title(view);
1425 }
1426 }
1428 static void
1429 update_display_cursor(void)
1430 {
1431 struct view *view = display[current_view];
1433 /* Move the cursor to the right-most column of the cursor line.
1434 *
1435 * XXX: This could turn out to be a bit expensive, but it ensures that
1436 * the cursor does not jump around. */
1437 if (view->lines) {
1438 wmove(view->win, view->lineno - view->offset, view->width - 1);
1439 wrefresh(view->win);
1440 }
1441 }
1443 /*
1444 * Navigation
1445 */
1447 /* Scrolling backend */
1448 static void
1449 do_scroll_view(struct view *view, int lines)
1450 {
1451 bool redraw_current_line = FALSE;
1453 /* The rendering expects the new offset. */
1454 view->offset += lines;
1456 assert(0 <= view->offset && view->offset < view->lines);
1457 assert(lines);
1459 /* Move current line into the view. */
1460 if (view->lineno < view->offset) {
1461 view->lineno = view->offset;
1462 redraw_current_line = TRUE;
1463 } else if (view->lineno >= view->offset + view->height) {
1464 view->lineno = view->offset + view->height - 1;
1465 redraw_current_line = TRUE;
1466 }
1468 assert(view->offset <= view->lineno && view->lineno < view->lines);
1470 /* Redraw the whole screen if scrolling is pointless. */
1471 if (view->height < ABS(lines)) {
1472 redraw_view(view);
1474 } else {
1475 int line = lines > 0 ? view->height - lines : 0;
1476 int end = line + ABS(lines);
1478 wscrl(view->win, lines);
1480 for (; line < end; line++) {
1481 if (!draw_view_line(view, line))
1482 break;
1483 }
1485 if (redraw_current_line)
1486 draw_view_line(view, view->lineno - view->offset);
1487 }
1489 redrawwin(view->win);
1490 wrefresh(view->win);
1491 report("");
1492 }
1494 /* Scroll frontend */
1495 static void
1496 scroll_view(struct view *view, enum request request)
1497 {
1498 int lines = 1;
1500 assert(view_is_displayed(view));
1502 switch (request) {
1503 case REQ_SCROLL_PAGE_DOWN:
1504 lines = view->height;
1505 case REQ_SCROLL_LINE_DOWN:
1506 if (view->offset + lines > view->lines)
1507 lines = view->lines - view->offset;
1509 if (lines == 0 || view->offset + view->height >= view->lines) {
1510 report("Cannot scroll beyond the last line");
1511 return;
1512 }
1513 break;
1515 case REQ_SCROLL_PAGE_UP:
1516 lines = view->height;
1517 case REQ_SCROLL_LINE_UP:
1518 if (lines > view->offset)
1519 lines = view->offset;
1521 if (lines == 0) {
1522 report("Cannot scroll beyond the first line");
1523 return;
1524 }
1526 lines = -lines;
1527 break;
1529 default:
1530 die("request %d not handled in switch", request);
1531 }
1533 do_scroll_view(view, lines);
1534 }
1536 /* Cursor moving */
1537 static void
1538 move_view(struct view *view, enum request request)
1539 {
1540 int scroll_steps = 0;
1541 int steps;
1543 switch (request) {
1544 case REQ_MOVE_FIRST_LINE:
1545 steps = -view->lineno;
1546 break;
1548 case REQ_MOVE_LAST_LINE:
1549 steps = view->lines - view->lineno - 1;
1550 break;
1552 case REQ_MOVE_PAGE_UP:
1553 steps = view->height > view->lineno
1554 ? -view->lineno : -view->height;
1555 break;
1557 case REQ_MOVE_PAGE_DOWN:
1558 steps = view->lineno + view->height >= view->lines
1559 ? view->lines - view->lineno - 1 : view->height;
1560 break;
1562 case REQ_MOVE_UP:
1563 steps = -1;
1564 break;
1566 case REQ_MOVE_DOWN:
1567 steps = 1;
1568 break;
1570 default:
1571 die("request %d not handled in switch", request);
1572 }
1574 if (steps <= 0 && view->lineno == 0) {
1575 report("Cannot move beyond the first line");
1576 return;
1578 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1579 report("Cannot move beyond the last line");
1580 return;
1581 }
1583 /* Move the current line */
1584 view->lineno += steps;
1585 assert(0 <= view->lineno && view->lineno < view->lines);
1587 /* Check whether the view needs to be scrolled */
1588 if (view->lineno < view->offset ||
1589 view->lineno >= view->offset + view->height) {
1590 scroll_steps = steps;
1591 if (steps < 0 && -steps > view->offset) {
1592 scroll_steps = -view->offset;
1594 } else if (steps > 0) {
1595 if (view->lineno == view->lines - 1 &&
1596 view->lines > view->height) {
1597 scroll_steps = view->lines - view->offset - 1;
1598 if (scroll_steps >= view->height)
1599 scroll_steps -= view->height - 1;
1600 }
1601 }
1602 }
1604 if (!view_is_displayed(view)) {
1605 view->offset += steps;
1606 view->ops->select(view, &view->line[view->lineno]);
1607 return;
1608 }
1610 /* Repaint the old "current" line if we be scrolling */
1611 if (ABS(steps) < view->height)
1612 draw_view_line(view, view->lineno - steps - view->offset);
1614 if (scroll_steps) {
1615 do_scroll_view(view, scroll_steps);
1616 return;
1617 }
1619 /* Draw the current line */
1620 draw_view_line(view, view->lineno - view->offset);
1622 redrawwin(view->win);
1623 wrefresh(view->win);
1624 report("");
1625 }
1628 /*
1629 * Searching
1630 */
1632 static void search_view(struct view *view, enum request request);
1634 static bool
1635 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1636 {
1637 assert(view_is_displayed(view));
1639 if (!view->ops->grep(view, line))
1640 return FALSE;
1642 if (lineno - view->offset >= view->height) {
1643 view->offset = lineno;
1644 view->lineno = lineno;
1645 redraw_view(view);
1647 } else {
1648 unsigned long old_lineno = view->lineno - view->offset;
1650 view->lineno = lineno;
1651 draw_view_line(view, old_lineno);
1653 draw_view_line(view, view->lineno - view->offset);
1654 redrawwin(view->win);
1655 wrefresh(view->win);
1656 }
1658 report("Line %ld matches '%s'", lineno + 1, view->grep);
1659 return TRUE;
1660 }
1662 static void
1663 find_next(struct view *view, enum request request)
1664 {
1665 unsigned long lineno = view->lineno;
1666 int direction;
1668 if (!*view->grep) {
1669 if (!*opt_search)
1670 report("No previous search");
1671 else
1672 search_view(view, request);
1673 return;
1674 }
1676 switch (request) {
1677 case REQ_SEARCH:
1678 case REQ_FIND_NEXT:
1679 direction = 1;
1680 break;
1682 case REQ_SEARCH_BACK:
1683 case REQ_FIND_PREV:
1684 direction = -1;
1685 break;
1687 default:
1688 return;
1689 }
1691 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1692 lineno += direction;
1694 /* Note, lineno is unsigned long so will wrap around in which case it
1695 * will become bigger than view->lines. */
1696 for (; lineno < view->lines; lineno += direction) {
1697 struct line *line = &view->line[lineno];
1699 if (find_next_line(view, lineno, line))
1700 return;
1701 }
1703 report("No match found for '%s'", view->grep);
1704 }
1706 static void
1707 search_view(struct view *view, enum request request)
1708 {
1709 int regex_err;
1711 if (view->regex) {
1712 regfree(view->regex);
1713 *view->grep = 0;
1714 } else {
1715 view->regex = calloc(1, sizeof(*view->regex));
1716 if (!view->regex)
1717 return;
1718 }
1720 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1721 if (regex_err != 0) {
1722 char buf[SIZEOF_STR] = "unknown error";
1724 regerror(regex_err, view->regex, buf, sizeof(buf));
1725 report("Search failed: %s", buf);
1726 return;
1727 }
1729 string_copy(view->grep, opt_search);
1731 find_next(view, request);
1732 }
1734 /*
1735 * Incremental updating
1736 */
1738 static void
1739 end_update(struct view *view)
1740 {
1741 if (!view->pipe)
1742 return;
1743 set_nonblocking_input(FALSE);
1744 if (view->pipe == stdin)
1745 fclose(view->pipe);
1746 else
1747 pclose(view->pipe);
1748 view->pipe = NULL;
1749 }
1751 static bool
1752 begin_update(struct view *view)
1753 {
1754 const char *id = view->id;
1756 if (view->pipe)
1757 end_update(view);
1759 if (opt_cmd[0]) {
1760 string_copy(view->cmd, opt_cmd);
1761 opt_cmd[0] = 0;
1762 /* When running random commands, the view ref could have become
1763 * invalid so clear it. */
1764 view->ref[0] = 0;
1766 } else if (view == VIEW(REQ_VIEW_TREE)) {
1767 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1769 if (strcmp(view->vid, view->id))
1770 opt_path[0] = 0;
1772 if (!string_format(view->cmd, format, id, opt_path))
1773 return FALSE;
1775 } else {
1776 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1778 if (!string_format(view->cmd, format, id, id, id, id, id))
1779 return FALSE;
1780 }
1782 /* Special case for the pager view. */
1783 if (opt_pipe) {
1784 view->pipe = opt_pipe;
1785 opt_pipe = NULL;
1786 } else {
1787 view->pipe = popen(view->cmd, "r");
1788 }
1790 if (!view->pipe)
1791 return FALSE;
1793 set_nonblocking_input(TRUE);
1795 view->offset = 0;
1796 view->lines = 0;
1797 view->lineno = 0;
1798 string_copy(view->vid, id);
1800 if (view->line) {
1801 int i;
1803 for (i = 0; i < view->lines; i++)
1804 if (view->line[i].data)
1805 free(view->line[i].data);
1807 free(view->line);
1808 view->line = NULL;
1809 }
1811 view->start_time = time(NULL);
1813 return TRUE;
1814 }
1816 static struct line *
1817 realloc_lines(struct view *view, size_t line_size)
1818 {
1819 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1821 if (!tmp)
1822 return NULL;
1824 view->line = tmp;
1825 view->line_size = line_size;
1826 return view->line;
1827 }
1829 static bool
1830 update_view(struct view *view)
1831 {
1832 char in_buffer[BUFSIZ];
1833 char out_buffer[BUFSIZ * 2];
1834 char *line;
1835 /* The number of lines to read. If too low it will cause too much
1836 * redrawing (and possible flickering), if too high responsiveness
1837 * will suffer. */
1838 unsigned long lines = view->height;
1839 int redraw_from = -1;
1841 if (!view->pipe)
1842 return TRUE;
1844 /* Only redraw if lines are visible. */
1845 if (view->offset + view->height >= view->lines)
1846 redraw_from = view->lines - view->offset;
1848 /* FIXME: This is probably not perfect for backgrounded views. */
1849 if (!realloc_lines(view, view->lines + lines))
1850 goto alloc_error;
1852 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1853 size_t linelen = strlen(line);
1855 if (linelen)
1856 line[linelen - 1] = 0;
1858 if (opt_iconv != ICONV_NONE) {
1859 char *inbuf = line;
1860 size_t inlen = linelen;
1862 char *outbuf = out_buffer;
1863 size_t outlen = sizeof(out_buffer);
1865 size_t ret;
1867 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1868 if (ret != (size_t) -1) {
1869 line = out_buffer;
1870 linelen = strlen(out_buffer);
1871 }
1872 }
1874 if (!view->ops->read(view, line))
1875 goto alloc_error;
1877 if (lines-- == 1)
1878 break;
1879 }
1881 {
1882 int digits;
1884 lines = view->lines;
1885 for (digits = 0; lines; digits++)
1886 lines /= 10;
1888 /* Keep the displayed view in sync with line number scaling. */
1889 if (digits != view->digits) {
1890 view->digits = digits;
1891 redraw_from = 0;
1892 }
1893 }
1895 if (!view_is_displayed(view))
1896 goto check_pipe;
1898 if (view == VIEW(REQ_VIEW_TREE)) {
1899 /* Clear the view and redraw everything since the tree sorting
1900 * might have rearranged things. */
1901 redraw_view(view);
1903 } else if (redraw_from >= 0) {
1904 /* If this is an incremental update, redraw the previous line
1905 * since for commits some members could have changed when
1906 * loading the main view. */
1907 if (redraw_from > 0)
1908 redraw_from--;
1910 /* Incrementally draw avoids flickering. */
1911 redraw_view_from(view, redraw_from);
1912 }
1914 /* Update the title _after_ the redraw so that if the redraw picks up a
1915 * commit reference in view->ref it'll be available here. */
1916 update_view_title(view);
1918 check_pipe:
1919 if (ferror(view->pipe)) {
1920 report("Failed to read: %s", strerror(errno));
1921 goto end;
1923 } else if (feof(view->pipe)) {
1924 report("");
1925 goto end;
1926 }
1928 return TRUE;
1930 alloc_error:
1931 report("Allocation failure");
1933 end:
1934 end_update(view);
1935 return FALSE;
1936 }
1939 /*
1940 * View opening
1941 */
1943 static void open_help_view(struct view *view)
1944 {
1945 char buf[BUFSIZ];
1946 int lines = ARRAY_SIZE(req_info) + 2;
1947 int i;
1949 if (view->lines > 0)
1950 return;
1952 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1953 if (!req_info[i].request)
1954 lines++;
1956 view->line = calloc(lines, sizeof(*view->line));
1957 if (!view->line) {
1958 report("Allocation failure");
1959 return;
1960 }
1962 view->ops->read(view, "Quick reference for tig keybindings:");
1964 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1965 char *key;
1967 if (!req_info[i].request) {
1968 view->ops->read(view, "");
1969 view->ops->read(view, req_info[i].help);
1970 continue;
1971 }
1973 key = get_key(req_info[i].request);
1974 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1975 continue;
1977 view->ops->read(view, buf);
1978 }
1979 }
1981 enum open_flags {
1982 OPEN_DEFAULT = 0, /* Use default view switching. */
1983 OPEN_SPLIT = 1, /* Split current view. */
1984 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
1985 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
1986 };
1988 static void
1989 open_view(struct view *prev, enum request request, enum open_flags flags)
1990 {
1991 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1992 bool split = !!(flags & OPEN_SPLIT);
1993 bool reload = !!(flags & OPEN_RELOAD);
1994 struct view *view = VIEW(request);
1995 int nviews = displayed_views();
1996 struct view *base_view = display[0];
1998 if (view == prev && nviews == 1 && !reload) {
1999 report("Already in %s view", view->name);
2000 return;
2001 }
2003 if (view == VIEW(REQ_VIEW_HELP)) {
2004 open_help_view(view);
2006 } else if ((reload || strcmp(view->vid, view->id)) &&
2007 !begin_update(view)) {
2008 report("Failed to load %s view", view->name);
2009 return;
2010 }
2012 if (split) {
2013 display[1] = view;
2014 if (!backgrounded)
2015 current_view = 1;
2016 } else {
2017 /* Maximize the current view. */
2018 memset(display, 0, sizeof(display));
2019 current_view = 0;
2020 display[current_view] = view;
2021 }
2023 /* Resize the view when switching between split- and full-screen,
2024 * or when switching between two different full-screen views. */
2025 if (nviews != displayed_views() ||
2026 (nviews == 1 && base_view != display[0]))
2027 resize_display();
2029 if (split && prev->lineno - prev->offset >= prev->height) {
2030 /* Take the title line into account. */
2031 int lines = prev->lineno - prev->offset - prev->height + 1;
2033 /* Scroll the view that was split if the current line is
2034 * outside the new limited view. */
2035 do_scroll_view(prev, lines);
2036 }
2038 if (prev && view != prev) {
2039 if (split && !backgrounded) {
2040 /* "Blur" the previous view. */
2041 update_view_title(prev);
2042 }
2044 view->parent = prev;
2045 }
2047 if (view->pipe && view->lines == 0) {
2048 /* Clear the old view and let the incremental updating refill
2049 * the screen. */
2050 wclear(view->win);
2051 report("");
2052 } else {
2053 redraw_view(view);
2054 report("");
2055 }
2057 /* If the view is backgrounded the above calls to report()
2058 * won't redraw the view title. */
2059 if (backgrounded)
2060 update_view_title(view);
2061 }
2064 /*
2065 * User request switch noodle
2066 */
2068 static int
2069 view_driver(struct view *view, enum request request)
2070 {
2071 int i;
2073 switch (request) {
2074 case REQ_MOVE_UP:
2075 case REQ_MOVE_DOWN:
2076 case REQ_MOVE_PAGE_UP:
2077 case REQ_MOVE_PAGE_DOWN:
2078 case REQ_MOVE_FIRST_LINE:
2079 case REQ_MOVE_LAST_LINE:
2080 move_view(view, request);
2081 break;
2083 case REQ_SCROLL_LINE_DOWN:
2084 case REQ_SCROLL_LINE_UP:
2085 case REQ_SCROLL_PAGE_DOWN:
2086 case REQ_SCROLL_PAGE_UP:
2087 scroll_view(view, request);
2088 break;
2090 case REQ_VIEW_BLOB:
2091 if (!ref_blob[0]) {
2092 report("No file chosen, press 't' to open tree view");
2093 break;
2094 }
2095 /* Fall-through */
2096 case REQ_VIEW_MAIN:
2097 case REQ_VIEW_DIFF:
2098 case REQ_VIEW_LOG:
2099 case REQ_VIEW_TREE:
2100 case REQ_VIEW_HELP:
2101 case REQ_VIEW_PAGER:
2102 open_view(view, request, OPEN_DEFAULT);
2103 break;
2105 case REQ_NEXT:
2106 case REQ_PREVIOUS:
2107 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2109 if ((view == VIEW(REQ_VIEW_DIFF) &&
2110 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2111 (view == VIEW(REQ_VIEW_BLOB) &&
2112 view->parent == VIEW(REQ_VIEW_TREE))) {
2113 view = view->parent;
2114 move_view(view, request);
2115 if (view_is_displayed(view))
2116 update_view_title(view);
2117 } else {
2118 move_view(view, request);
2119 break;
2120 }
2121 /* Fall-through */
2123 case REQ_ENTER:
2124 if (!view->lines) {
2125 report("Nothing to enter");
2126 break;
2127 }
2128 return view->ops->enter(view, &view->line[view->lineno]);
2130 case REQ_VIEW_NEXT:
2131 {
2132 int nviews = displayed_views();
2133 int next_view = (current_view + 1) % nviews;
2135 if (next_view == current_view) {
2136 report("Only one view is displayed");
2137 break;
2138 }
2140 current_view = next_view;
2141 /* Blur out the title of the previous view. */
2142 update_view_title(view);
2143 report("");
2144 break;
2145 }
2146 case REQ_TOGGLE_LINENO:
2147 opt_line_number = !opt_line_number;
2148 redraw_display();
2149 break;
2151 case REQ_TOGGLE_REV_GRAPH:
2152 opt_rev_graph = !opt_rev_graph;
2153 redraw_display();
2154 break;
2156 case REQ_PROMPT:
2157 /* Always reload^Wrerun commands from the prompt. */
2158 open_view(view, opt_request, OPEN_RELOAD);
2159 break;
2161 case REQ_SEARCH:
2162 case REQ_SEARCH_BACK:
2163 search_view(view, request);
2164 break;
2166 case REQ_FIND_NEXT:
2167 case REQ_FIND_PREV:
2168 find_next(view, request);
2169 break;
2171 case REQ_STOP_LOADING:
2172 for (i = 0; i < ARRAY_SIZE(views); i++) {
2173 view = &views[i];
2174 if (view->pipe)
2175 report("Stopped loading the %s view", view->name),
2176 end_update(view);
2177 }
2178 break;
2180 case REQ_SHOW_VERSION:
2181 report("%s (built %s)", VERSION, __DATE__);
2182 return TRUE;
2184 case REQ_SCREEN_RESIZE:
2185 resize_display();
2186 /* Fall-through */
2187 case REQ_SCREEN_REDRAW:
2188 redraw_display();
2189 break;
2191 case REQ_NONE:
2192 doupdate();
2193 return TRUE;
2195 case REQ_VIEW_CLOSE:
2196 /* XXX: Mark closed views by letting view->parent point to the
2197 * view itself. Parents to closed view should never be
2198 * followed. */
2199 if (view->parent &&
2200 view->parent->parent != view->parent) {
2201 memset(display, 0, sizeof(display));
2202 current_view = 0;
2203 display[current_view] = view->parent;
2204 view->parent = view;
2205 resize_display();
2206 redraw_display();
2207 break;
2208 }
2209 /* Fall-through */
2210 case REQ_QUIT:
2211 return FALSE;
2213 default:
2214 /* An unknown key will show most commonly used commands. */
2215 report("Unknown key, press 'h' for help");
2216 return TRUE;
2217 }
2219 return TRUE;
2220 }
2223 /*
2224 * Pager backend
2225 */
2227 static bool
2228 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2229 {
2230 char *text = line->data;
2231 enum line_type type = line->type;
2232 int textlen = strlen(text);
2233 int attr;
2235 wmove(view->win, lineno, 0);
2237 if (selected) {
2238 type = LINE_CURSOR;
2239 wchgat(view->win, -1, 0, type, NULL);
2240 }
2242 attr = get_line_attr(type);
2243 wattrset(view->win, attr);
2245 if (opt_line_number || opt_tab_size < TABSIZE) {
2246 static char spaces[] = " ";
2247 int col_offset = 0, col = 0;
2249 if (opt_line_number) {
2250 unsigned long real_lineno = view->offset + lineno + 1;
2252 if (real_lineno == 1 ||
2253 (real_lineno % opt_num_interval) == 0) {
2254 wprintw(view->win, "%.*d", view->digits, real_lineno);
2256 } else {
2257 waddnstr(view->win, spaces,
2258 MIN(view->digits, STRING_SIZE(spaces)));
2259 }
2260 waddstr(view->win, ": ");
2261 col_offset = view->digits + 2;
2262 }
2264 while (text && col_offset + col < view->width) {
2265 int cols_max = view->width - col_offset - col;
2266 char *pos = text;
2267 int cols;
2269 if (*text == '\t') {
2270 text++;
2271 assert(sizeof(spaces) > TABSIZE);
2272 pos = spaces;
2273 cols = opt_tab_size - (col % opt_tab_size);
2275 } else {
2276 text = strchr(text, '\t');
2277 cols = line ? text - pos : strlen(pos);
2278 }
2280 waddnstr(view->win, pos, MIN(cols, cols_max));
2281 col += cols;
2282 }
2284 } else {
2285 int col = 0, pos = 0;
2287 for (; pos < textlen && col < view->width; pos++, col++)
2288 if (text[pos] == '\t')
2289 col += TABSIZE - (col % TABSIZE) - 1;
2291 waddnstr(view->win, text, pos);
2292 }
2294 return TRUE;
2295 }
2297 static bool
2298 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2299 {
2300 char refbuf[SIZEOF_STR];
2301 char *ref = NULL;
2302 FILE *pipe;
2304 if (!string_format(refbuf, "git describe %s", commit_id))
2305 return TRUE;
2307 pipe = popen(refbuf, "r");
2308 if (!pipe)
2309 return TRUE;
2311 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2312 ref = chomp_string(ref);
2313 pclose(pipe);
2315 if (!ref || !*ref)
2316 return TRUE;
2318 /* This is the only fatal call, since it can "corrupt" the buffer. */
2319 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2320 return FALSE;
2322 return TRUE;
2323 }
2325 static void
2326 add_pager_refs(struct view *view, struct line *line)
2327 {
2328 char buf[SIZEOF_STR];
2329 char *commit_id = line->data + STRING_SIZE("commit ");
2330 struct ref **refs;
2331 size_t bufpos = 0, refpos = 0;
2332 const char *sep = "Refs: ";
2333 bool is_tag = FALSE;
2335 assert(line->type == LINE_COMMIT);
2337 refs = get_refs(commit_id);
2338 if (!refs) {
2339 if (view == VIEW(REQ_VIEW_DIFF))
2340 goto try_add_describe_ref;
2341 return;
2342 }
2344 do {
2345 struct ref *ref = refs[refpos];
2346 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
2348 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2349 return;
2350 sep = ", ";
2351 if (ref->tag)
2352 is_tag = TRUE;
2353 } while (refs[refpos++]->next);
2355 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2356 try_add_describe_ref:
2357 /* Add <tag>-g<commit_id> "fake" reference. */
2358 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2359 return;
2360 }
2362 if (bufpos == 0)
2363 return;
2365 if (!realloc_lines(view, view->line_size + 1))
2366 return;
2368 line = &view->line[view->lines];
2369 line->data = strdup(buf);
2370 if (!line->data)
2371 return;
2373 line->type = LINE_PP_REFS;
2374 view->lines++;
2375 }
2377 static bool
2378 pager_read(struct view *view, char *data)
2379 {
2380 struct line *line = &view->line[view->lines];
2382 line->data = strdup(data);
2383 if (!line->data)
2384 return FALSE;
2386 line->type = get_line_type(line->data);
2387 view->lines++;
2389 if (line->type == LINE_COMMIT &&
2390 (view == VIEW(REQ_VIEW_DIFF) ||
2391 view == VIEW(REQ_VIEW_LOG)))
2392 add_pager_refs(view, line);
2394 return TRUE;
2395 }
2397 static bool
2398 pager_enter(struct view *view, struct line *line)
2399 {
2400 int split = 0;
2402 if (line->type == LINE_COMMIT &&
2403 (view == VIEW(REQ_VIEW_LOG) ||
2404 view == VIEW(REQ_VIEW_PAGER))) {
2405 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2406 split = 1;
2407 }
2409 /* Always scroll the view even if it was split. That way
2410 * you can use Enter to scroll through the log view and
2411 * split open each commit diff. */
2412 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2414 /* FIXME: A minor workaround. Scrolling the view will call report("")
2415 * but if we are scrolling a non-current view this won't properly
2416 * update the view title. */
2417 if (split)
2418 update_view_title(view);
2420 return TRUE;
2421 }
2423 static bool
2424 pager_grep(struct view *view, struct line *line)
2425 {
2426 regmatch_t pmatch;
2427 char *text = line->data;
2429 if (!*text)
2430 return FALSE;
2432 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2433 return FALSE;
2435 return TRUE;
2436 }
2438 static void
2439 pager_select(struct view *view, struct line *line)
2440 {
2441 if (line->type == LINE_COMMIT) {
2442 char *text = line->data;
2444 string_copy(view->ref, text + STRING_SIZE("commit "));
2445 string_copy(ref_commit, view->ref);
2446 }
2447 }
2449 static struct view_ops pager_ops = {
2450 "line",
2451 pager_draw,
2452 pager_read,
2453 pager_enter,
2454 pager_grep,
2455 pager_select,
2456 };
2459 /*
2460 * Tree backend
2461 */
2463 /* Parse output from git-ls-tree(1):
2464 *
2465 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2466 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2467 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2468 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2469 */
2471 #define SIZEOF_TREE_ATTR \
2472 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2474 #define TREE_UP_FORMAT "040000 tree %s\t.."
2476 static int
2477 tree_compare_entry(enum line_type type1, char *name1,
2478 enum line_type type2, char *name2)
2479 {
2480 if (type1 != type2) {
2481 if (type1 == LINE_TREE_DIR)
2482 return -1;
2483 return 1;
2484 }
2486 return strcmp(name1, name2);
2487 }
2489 static bool
2490 tree_read(struct view *view, char *text)
2491 {
2492 size_t textlen = strlen(text);
2493 char buf[SIZEOF_STR];
2494 unsigned long pos;
2495 enum line_type type;
2496 bool first_read = view->lines == 0;
2498 if (textlen <= SIZEOF_TREE_ATTR)
2499 return FALSE;
2501 type = text[STRING_SIZE("100644 ")] == 't'
2502 ? LINE_TREE_DIR : LINE_TREE_FILE;
2504 if (first_read) {
2505 /* Add path info line */
2506 if (string_format(buf, "Directory path /%s", opt_path) &&
2507 realloc_lines(view, view->line_size + 1) &&
2508 pager_read(view, buf))
2509 view->line[view->lines - 1].type = LINE_DEFAULT;
2510 else
2511 return FALSE;
2513 /* Insert "link" to parent directory. */
2514 if (*opt_path &&
2515 string_format(buf, TREE_UP_FORMAT, view->ref) &&
2516 realloc_lines(view, view->line_size + 1) &&
2517 pager_read(view, buf))
2518 view->line[view->lines - 1].type = LINE_TREE_DIR;
2519 else if (*opt_path)
2520 return FALSE;
2521 }
2523 /* Strip the path part ... */
2524 if (*opt_path) {
2525 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2526 size_t striplen = strlen(opt_path);
2527 char *path = text + SIZEOF_TREE_ATTR;
2529 if (pathlen > striplen)
2530 memmove(path, path + striplen,
2531 pathlen - striplen + 1);
2532 }
2534 /* Skip "Directory ..." and ".." line. */
2535 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2536 struct line *line = &view->line[pos];
2537 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2538 char *path2 = text + SIZEOF_TREE_ATTR;
2539 int cmp = tree_compare_entry(line->type, path1, type, path2);
2541 if (cmp <= 0)
2542 continue;
2544 text = strdup(text);
2545 if (!text)
2546 return FALSE;
2548 if (view->lines > pos)
2549 memmove(&view->line[pos + 1], &view->line[pos],
2550 (view->lines - pos) * sizeof(*line));
2552 line = &view->line[pos];
2553 line->data = text;
2554 line->type = type;
2555 view->lines++;
2556 return TRUE;
2557 }
2559 if (!pager_read(view, text))
2560 return FALSE;
2562 /* Move the current line to the first tree entry. */
2563 if (first_read)
2564 view->lineno++;
2566 view->line[view->lines - 1].type = type;
2567 return TRUE;
2568 }
2570 static bool
2571 tree_enter(struct view *view, struct line *line)
2572 {
2573 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2574 enum request request;
2576 switch (line->type) {
2577 case LINE_TREE_DIR:
2578 /* Depending on whether it is a subdir or parent (updir?) link
2579 * mangle the path buffer. */
2580 if (line == &view->line[1] && *opt_path) {
2581 size_t path_len = strlen(opt_path);
2582 char *dirsep = opt_path + path_len - 1;
2584 while (dirsep > opt_path && dirsep[-1] != '/')
2585 dirsep--;
2587 dirsep[0] = 0;
2589 } else {
2590 size_t pathlen = strlen(opt_path);
2591 size_t origlen = pathlen;
2592 char *data = line->data;
2593 char *basename = data + SIZEOF_TREE_ATTR;
2595 if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2596 opt_path[origlen] = 0;
2597 return TRUE;
2598 }
2599 }
2601 /* Trees and subtrees share the same ID, so they are not not
2602 * unique like blobs. */
2603 flags |= OPEN_RELOAD;
2604 request = REQ_VIEW_TREE;
2605 break;
2607 case LINE_TREE_FILE:
2608 request = REQ_VIEW_BLOB;
2609 break;
2611 default:
2612 return TRUE;
2613 }
2615 open_view(view, request, flags);
2617 return TRUE;
2618 }
2620 static void
2621 tree_select(struct view *view, struct line *line)
2622 {
2623 char *text = line->data;
2625 text += STRING_SIZE("100644 blob ");
2627 if (line->type == LINE_TREE_FILE) {
2628 string_ncopy(ref_blob, text, 40);
2629 /* Also update the blob view's ref, since all there must always
2630 * be in sync. */
2631 string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob);
2633 } else if (line->type != LINE_TREE_DIR) {
2634 return;
2635 }
2637 string_ncopy(view->ref, text, 40);
2638 }
2640 static struct view_ops tree_ops = {
2641 "file",
2642 pager_draw,
2643 tree_read,
2644 tree_enter,
2645 pager_grep,
2646 tree_select,
2647 };
2649 static bool
2650 blob_read(struct view *view, char *line)
2651 {
2652 bool state = pager_read(view, line);
2654 if (state == TRUE)
2655 view->line[view->lines - 1].type = LINE_DEFAULT;
2657 return state;
2658 }
2660 static struct view_ops blob_ops = {
2661 "line",
2662 pager_draw,
2663 blob_read,
2664 pager_enter,
2665 pager_grep,
2666 pager_select,
2667 };
2670 /*
2671 * Main view backend
2672 */
2674 struct commit {
2675 char id[SIZEOF_REV]; /* SHA1 ID. */
2676 char title[75]; /* First line of the commit message. */
2677 char author[75]; /* Author of the commit. */
2678 struct tm time; /* Date from the author ident. */
2679 struct ref **refs; /* Repository references. */
2680 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
2681 size_t graph_size; /* The width of the graph array. */
2682 };
2684 static bool
2685 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2686 {
2687 char buf[DATE_COLS + 1];
2688 struct commit *commit = line->data;
2689 enum line_type type;
2690 int col = 0;
2691 size_t timelen;
2692 size_t authorlen;
2693 int trimmed = 1;
2695 if (!*commit->author)
2696 return FALSE;
2698 wmove(view->win, lineno, col);
2700 if (selected) {
2701 type = LINE_CURSOR;
2702 wattrset(view->win, get_line_attr(type));
2703 wchgat(view->win, -1, 0, type, NULL);
2705 } else {
2706 type = LINE_MAIN_COMMIT;
2707 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2708 }
2710 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2711 waddnstr(view->win, buf, timelen);
2712 waddstr(view->win, " ");
2714 col += DATE_COLS;
2715 wmove(view->win, lineno, col);
2716 if (type != LINE_CURSOR)
2717 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2719 if (opt_utf8) {
2720 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2721 } else {
2722 authorlen = strlen(commit->author);
2723 if (authorlen > AUTHOR_COLS - 2) {
2724 authorlen = AUTHOR_COLS - 2;
2725 trimmed = 1;
2726 }
2727 }
2729 if (trimmed) {
2730 waddnstr(view->win, commit->author, authorlen);
2731 if (type != LINE_CURSOR)
2732 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2733 waddch(view->win, '~');
2734 } else {
2735 waddstr(view->win, commit->author);
2736 }
2738 col += AUTHOR_COLS;
2739 if (type != LINE_CURSOR)
2740 wattrset(view->win, A_NORMAL);
2742 if (opt_rev_graph && commit->graph_size) {
2743 size_t i;
2745 wmove(view->win, lineno, col);
2746 /* Using waddch() instead of waddnstr() ensures that
2747 * they'll be rendered correctly for the cursor line. */
2748 for (i = 0; i < commit->graph_size; i++)
2749 waddch(view->win, commit->graph[i]);
2751 col += commit->graph_size + 1;
2752 }
2754 wmove(view->win, lineno, col);
2756 if (commit->refs) {
2757 size_t i = 0;
2759 do {
2760 if (type == LINE_CURSOR)
2761 ;
2762 else if (commit->refs[i]->tag)
2763 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2764 else
2765 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2766 waddstr(view->win, "[");
2767 waddstr(view->win, commit->refs[i]->name);
2768 waddstr(view->win, "]");
2769 if (type != LINE_CURSOR)
2770 wattrset(view->win, A_NORMAL);
2771 waddstr(view->win, " ");
2772 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2773 } while (commit->refs[i++]->next);
2774 }
2776 if (type != LINE_CURSOR)
2777 wattrset(view->win, get_line_attr(type));
2779 {
2780 int titlelen = strlen(commit->title);
2782 if (col + titlelen > view->width)
2783 titlelen = view->width - col;
2785 waddnstr(view->win, commit->title, titlelen);
2786 }
2788 return TRUE;
2789 }
2792 struct rev_stack {
2793 struct rev_stack *prev, *next, *parents;
2794 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
2795 size_t size;
2796 struct commit *commit;
2797 size_t pos;
2798 };
2800 /* Parents of the commit being visualized. */
2801 static struct rev_stack graph_parents[3];
2803 /* The current stack of revisions on the graph. */
2804 static struct rev_stack graph_stacks[3] = {
2805 { &graph_stacks[2], &graph_stacks[1], &graph_parents[0] },
2806 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
2807 { &graph_stacks[1], &graph_stacks[0], &graph_parents[2] },
2808 };
2810 static void
2811 reset_rev_graph(struct rev_stack *graph)
2812 {
2813 graph->size = graph->pos = 0;
2814 graph->commit = NULL;
2815 memset(graph->parents, 0, sizeof(*graph->parents));
2816 }
2818 static inline void
2819 append_to_rev_graph(struct rev_stack *stack, chtype symbol)
2820 {
2821 if (stack->commit->graph_size < ARRAY_SIZE(stack->commit->graph) - 1)
2822 stack->commit->graph[stack->commit->graph_size++] = symbol;
2823 }
2825 static void
2826 push_rev_stack(struct rev_stack *stack, char *parent)
2827 {
2828 /* Combine duplicate parents lines. */
2829 if (stack->size > 0 &&
2830 !strncmp(stack->rev[stack->size - 1], parent, SIZEOF_REV))
2831 return;
2833 if (stack->size < SIZEOF_REVITEMS) {
2834 string_ncopy(stack->rev[stack->size++], parent, SIZEOF_REV);
2835 }
2836 }
2838 static void
2839 draw_rev_graph(struct rev_stack *graph)
2840 {
2841 chtype symbol, separator, line;
2842 size_t i;
2844 /* Place the symbol for this commit. */
2845 if (graph->parents->size == 0)
2846 symbol = REVGRAPH_INIT;
2847 else if (graph->parents->size > 1)
2848 symbol = REVGRAPH_MERGE;
2849 else if (graph->pos >= graph->size)
2850 symbol = REVGRAPH_BRANCH;
2851 else
2852 symbol = REVGRAPH_COMMIT;
2854 separator = ' ';
2855 line = REVGRAPH_LINE;
2857 for (i = 0; i < graph->pos; i++) {
2858 append_to_rev_graph(graph, line);
2859 if (graph->prev->parents->size > 1 &&
2860 graph->prev->pos == i) {
2861 separator = '`';
2862 line = '.';
2863 }
2864 append_to_rev_graph(graph, separator);
2865 }
2867 append_to_rev_graph(graph, symbol);
2869 if (graph->prev->size > graph->size) {
2870 separator = '\'';
2871 line = ' ';
2872 } else {
2873 separator = ' ';
2874 line = REVGRAPH_LINE;
2875 }
2876 i++;
2878 for (; i < graph->size; i++) {
2879 append_to_rev_graph(graph, separator);
2880 append_to_rev_graph(graph, line);
2881 if (graph->prev->parents->size > 1) {
2882 if (i < graph->prev->pos + graph->parents->size) {
2883 separator = '`';
2884 line = '.';
2885 }
2886 }
2887 if (graph->prev->size > graph->size) {
2888 separator = '/';
2889 line = ' ';
2890 }
2891 }
2893 if (graph->prev->size > graph->size) {
2894 append_to_rev_graph(graph, separator);
2895 if (line != ' ')
2896 append_to_rev_graph(graph, line);
2897 }
2898 }
2900 void
2901 update_rev_graph(struct rev_stack *graph)
2902 {
2903 size_t i;
2905 /* First traverse all lines of revisions up to the active one. */
2906 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
2907 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
2908 break;
2910 push_rev_stack(graph->next, graph->rev[graph->pos]);
2911 }
2913 for (i = 0; i < graph->parents->size; i++)
2914 push_rev_stack(graph->next, graph->parents->rev[i]);
2916 /* FIXME: Moving branches left and right when collapsing a branch. */
2917 for (i = graph->pos + 1; i < graph->size; i++)
2918 push_rev_stack(graph->next, graph->rev[i]);
2920 draw_rev_graph(graph);
2921 if (graph->prev->parents->size > 1 &&
2922 graph->prev->pos < graph->prev->size - 1 &&
2923 graph->size == graph->prev->size + graph->prev->parents->size - 1) {
2924 i = graph->prev->pos + graph->prev->parents->size - 1;
2925 graph->prev->commit->graph_size = i * 2;
2926 while (i < graph->size - 1) {
2927 append_to_rev_graph(graph->prev, ' ');
2928 append_to_rev_graph(graph->prev, '\\');
2929 i++;
2930 }
2931 }
2932 reset_rev_graph(graph->prev);
2933 }
2935 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2936 static bool
2937 main_read(struct view *view, char *line)
2938 {
2939 static struct rev_stack *graph = graph_stacks;
2940 enum line_type type = get_line_type(line);
2941 struct commit *commit = view->lines
2942 ? view->line[view->lines - 1].data : NULL;
2944 switch (type) {
2945 case LINE_COMMIT:
2946 commit = calloc(1, sizeof(struct commit));
2947 if (!commit)
2948 return FALSE;
2950 line += STRING_SIZE("commit ");
2952 view->line[view->lines++].data = commit;
2953 string_copy(commit->id, line);
2954 commit->refs = get_refs(commit->id);
2955 graph->commit = commit;
2956 break;
2958 case LINE_PARENT:
2959 if (commit) {
2960 line += STRING_SIZE("parent ");
2961 push_rev_stack(graph->parents, line);
2962 }
2963 break;
2965 case LINE_AUTHOR:
2966 {
2967 char *ident = line + STRING_SIZE("author ");
2968 char *end = strchr(ident, '<');
2970 if (!commit)
2971 break;
2973 update_rev_graph(graph);
2974 graph = graph->next;
2976 if (end) {
2977 char *email = end + 1;
2979 for (; end > ident && isspace(end[-1]); end--) ;
2981 if (end == ident && *email) {
2982 ident = email;
2983 end = strchr(ident, '>');
2984 for (; end > ident && isspace(end[-1]); end--) ;
2985 }
2986 *end = 0;
2987 }
2989 /* End is NULL or ident meaning there's no author. */
2990 if (end <= ident)
2991 ident = "Unknown";
2993 string_copy(commit->author, ident);
2995 /* Parse epoch and timezone */
2996 if (end) {
2997 char *secs = strchr(end + 1, '>');
2998 char *zone;
2999 time_t time;
3001 if (!secs || secs[1] != ' ')
3002 break;
3004 secs += 2;
3005 time = (time_t) atol(secs);
3006 zone = strchr(secs, ' ');
3007 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3008 long tz;
3010 zone++;
3011 tz = ('0' - zone[1]) * 60 * 60 * 10;
3012 tz += ('0' - zone[2]) * 60 * 60;
3013 tz += ('0' - zone[3]) * 60;
3014 tz += ('0' - zone[4]) * 60;
3016 if (zone[0] == '-')
3017 tz = -tz;
3019 time -= tz;
3020 }
3021 gmtime_r(&time, &commit->time);
3022 }
3023 break;
3024 }
3025 default:
3026 if (!commit)
3027 break;
3029 /* Fill in the commit title if it has not already been set. */
3030 if (commit->title[0])
3031 break;
3033 /* Require titles to start with a non-space character at the
3034 * offset used by git log. */
3035 /* FIXME: More gracefull handling of titles; append "..." to
3036 * shortened titles, etc. */
3037 if (strncmp(line, " ", 4) ||
3038 isspace(line[4]))
3039 break;
3041 string_copy(commit->title, line + 4);
3042 }
3044 return TRUE;
3045 }
3047 static bool
3048 main_enter(struct view *view, struct line *line)
3049 {
3050 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3052 open_view(view, REQ_VIEW_DIFF, flags);
3053 return TRUE;
3054 }
3056 static bool
3057 main_grep(struct view *view, struct line *line)
3058 {
3059 struct commit *commit = line->data;
3060 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3061 char buf[DATE_COLS + 1];
3062 regmatch_t pmatch;
3064 for (state = S_TITLE; state < S_END; state++) {
3065 char *text;
3067 switch (state) {
3068 case S_TITLE: text = commit->title; break;
3069 case S_AUTHOR: text = commit->author; break;
3070 case S_DATE:
3071 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3072 continue;
3073 text = buf;
3074 break;
3076 default:
3077 return FALSE;
3078 }
3080 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3081 return TRUE;
3082 }
3084 return FALSE;
3085 }
3087 static void
3088 main_select(struct view *view, struct line *line)
3089 {
3090 struct commit *commit = line->data;
3092 string_copy(view->ref, commit->id);
3093 string_copy(ref_commit, view->ref);
3094 }
3096 static struct view_ops main_ops = {
3097 "commit",
3098 main_draw,
3099 main_read,
3100 main_enter,
3101 main_grep,
3102 main_select,
3103 };
3106 /*
3107 * Unicode / UTF-8 handling
3108 *
3109 * NOTE: Much of the following code for dealing with unicode is derived from
3110 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3111 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3112 */
3114 /* I've (over)annotated a lot of code snippets because I am not entirely
3115 * confident that the approach taken by this small UTF-8 interface is correct.
3116 * --jonas */
3118 static inline int
3119 unicode_width(unsigned long c)
3120 {
3121 if (c >= 0x1100 &&
3122 (c <= 0x115f /* Hangul Jamo */
3123 || c == 0x2329
3124 || c == 0x232a
3125 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
3126 /* CJK ... Yi */
3127 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
3128 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
3129 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
3130 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
3131 || (c >= 0xffe0 && c <= 0xffe6)
3132 || (c >= 0x20000 && c <= 0x2fffd)
3133 || (c >= 0x30000 && c <= 0x3fffd)))
3134 return 2;
3136 return 1;
3137 }
3139 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3140 * Illegal bytes are set one. */
3141 static const unsigned char utf8_bytes[256] = {
3142 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,
3143 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,
3144 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,
3145 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,
3146 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,
3147 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,
3148 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,
3149 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,
3150 };
3152 /* Decode UTF-8 multi-byte representation into a unicode character. */
3153 static inline unsigned long
3154 utf8_to_unicode(const char *string, size_t length)
3155 {
3156 unsigned long unicode;
3158 switch (length) {
3159 case 1:
3160 unicode = string[0];
3161 break;
3162 case 2:
3163 unicode = (string[0] & 0x1f) << 6;
3164 unicode += (string[1] & 0x3f);
3165 break;
3166 case 3:
3167 unicode = (string[0] & 0x0f) << 12;
3168 unicode += ((string[1] & 0x3f) << 6);
3169 unicode += (string[2] & 0x3f);
3170 break;
3171 case 4:
3172 unicode = (string[0] & 0x0f) << 18;
3173 unicode += ((string[1] & 0x3f) << 12);
3174 unicode += ((string[2] & 0x3f) << 6);
3175 unicode += (string[3] & 0x3f);
3176 break;
3177 case 5:
3178 unicode = (string[0] & 0x0f) << 24;
3179 unicode += ((string[1] & 0x3f) << 18);
3180 unicode += ((string[2] & 0x3f) << 12);
3181 unicode += ((string[3] & 0x3f) << 6);
3182 unicode += (string[4] & 0x3f);
3183 break;
3184 case 6:
3185 unicode = (string[0] & 0x01) << 30;
3186 unicode += ((string[1] & 0x3f) << 24);
3187 unicode += ((string[2] & 0x3f) << 18);
3188 unicode += ((string[3] & 0x3f) << 12);
3189 unicode += ((string[4] & 0x3f) << 6);
3190 unicode += (string[5] & 0x3f);
3191 break;
3192 default:
3193 die("Invalid unicode length");
3194 }
3196 /* Invalid characters could return the special 0xfffd value but NUL
3197 * should be just as good. */
3198 return unicode > 0xffff ? 0 : unicode;
3199 }
3201 /* Calculates how much of string can be shown within the given maximum width
3202 * and sets trimmed parameter to non-zero value if all of string could not be
3203 * shown.
3204 *
3205 * Additionally, adds to coloffset how many many columns to move to align with
3206 * the expected position. Takes into account how multi-byte and double-width
3207 * characters will effect the cursor position.
3208 *
3209 * Returns the number of bytes to output from string to satisfy max_width. */
3210 static size_t
3211 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3212 {
3213 const char *start = string;
3214 const char *end = strchr(string, '\0');
3215 size_t mbwidth = 0;
3216 size_t width = 0;
3218 *trimmed = 0;
3220 while (string < end) {
3221 int c = *(unsigned char *) string;
3222 unsigned char bytes = utf8_bytes[c];
3223 size_t ucwidth;
3224 unsigned long unicode;
3226 if (string + bytes > end)
3227 break;
3229 /* Change representation to figure out whether
3230 * it is a single- or double-width character. */
3232 unicode = utf8_to_unicode(string, bytes);
3233 /* FIXME: Graceful handling of invalid unicode character. */
3234 if (!unicode)
3235 break;
3237 ucwidth = unicode_width(unicode);
3238 width += ucwidth;
3239 if (width > max_width) {
3240 *trimmed = 1;
3241 break;
3242 }
3244 /* The column offset collects the differences between the
3245 * number of bytes encoding a character and the number of
3246 * columns will be used for rendering said character.
3247 *
3248 * So if some character A is encoded in 2 bytes, but will be
3249 * represented on the screen using only 1 byte this will and up
3250 * adding 1 to the multi-byte column offset.
3251 *
3252 * Assumes that no double-width character can be encoding in
3253 * less than two bytes. */
3254 if (bytes > ucwidth)
3255 mbwidth += bytes - ucwidth;
3257 string += bytes;
3258 }
3260 *coloffset += mbwidth;
3262 return string - start;
3263 }
3266 /*
3267 * Status management
3268 */
3270 /* Whether or not the curses interface has been initialized. */
3271 static bool cursed = FALSE;
3273 /* The status window is used for polling keystrokes. */
3274 static WINDOW *status_win;
3276 /* Update status and title window. */
3277 static void
3278 report(const char *msg, ...)
3279 {
3280 static bool empty = TRUE;
3281 struct view *view = display[current_view];
3283 if (!empty || *msg) {
3284 va_list args;
3286 va_start(args, msg);
3288 werase(status_win);
3289 wmove(status_win, 0, 0);
3290 if (*msg) {
3291 vwprintw(status_win, msg, args);
3292 empty = FALSE;
3293 } else {
3294 empty = TRUE;
3295 }
3296 wrefresh(status_win);
3298 va_end(args);
3299 }
3301 update_view_title(view);
3302 update_display_cursor();
3303 }
3305 /* Controls when nodelay should be in effect when polling user input. */
3306 static void
3307 set_nonblocking_input(bool loading)
3308 {
3309 static unsigned int loading_views;
3311 if ((loading == FALSE && loading_views-- == 1) ||
3312 (loading == TRUE && loading_views++ == 0))
3313 nodelay(status_win, loading);
3314 }
3316 static void
3317 init_display(void)
3318 {
3319 int x, y;
3321 /* Initialize the curses library */
3322 if (isatty(STDIN_FILENO)) {
3323 cursed = !!initscr();
3324 } else {
3325 /* Leave stdin and stdout alone when acting as a pager. */
3326 FILE *io = fopen("/dev/tty", "r+");
3328 if (!io)
3329 die("Failed to open /dev/tty");
3330 cursed = !!newterm(NULL, io, io);
3331 }
3333 if (!cursed)
3334 die("Failed to initialize curses");
3336 nonl(); /* Tell curses not to do NL->CR/NL on output */
3337 cbreak(); /* Take input chars one at a time, no wait for \n */
3338 noecho(); /* Don't echo input */
3339 leaveok(stdscr, TRUE);
3341 if (has_colors())
3342 init_colors();
3344 getmaxyx(stdscr, y, x);
3345 status_win = newwin(1, 0, y - 1, 0);
3346 if (!status_win)
3347 die("Failed to create status window");
3349 /* Enable keyboard mapping */
3350 keypad(status_win, TRUE);
3351 wbkgdset(status_win, get_line_attr(LINE_STATUS));
3352 }
3354 static char *
3355 read_prompt(const char *prompt)
3356 {
3357 enum { READING, STOP, CANCEL } status = READING;
3358 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3359 int pos = 0;
3361 while (status == READING) {
3362 struct view *view;
3363 int i, key;
3365 foreach_view (view, i)
3366 update_view(view);
3368 report("%s%.*s", prompt, pos, buf);
3369 /* Refresh, accept single keystroke of input */
3370 key = wgetch(status_win);
3371 switch (key) {
3372 case KEY_RETURN:
3373 case KEY_ENTER:
3374 case '\n':
3375 status = pos ? STOP : CANCEL;
3376 break;
3378 case KEY_BACKSPACE:
3379 if (pos > 0)
3380 pos--;
3381 else
3382 status = CANCEL;
3383 break;
3385 case KEY_ESC:
3386 status = CANCEL;
3387 break;
3389 case ERR:
3390 break;
3392 default:
3393 if (pos >= sizeof(buf)) {
3394 report("Input string too long");
3395 return NULL;
3396 }
3398 if (isprint(key))
3399 buf[pos++] = (char) key;
3400 }
3401 }
3403 if (status == CANCEL) {
3404 /* Clear the status window */
3405 report("");
3406 return NULL;
3407 }
3409 buf[pos++] = 0;
3411 return buf;
3412 }
3414 /*
3415 * Repository references
3416 */
3418 static struct ref *refs;
3419 static size_t refs_size;
3421 /* Id <-> ref store */
3422 static struct ref ***id_refs;
3423 static size_t id_refs_size;
3425 static struct ref **
3426 get_refs(char *id)
3427 {
3428 struct ref ***tmp_id_refs;
3429 struct ref **ref_list = NULL;
3430 size_t ref_list_size = 0;
3431 size_t i;
3433 for (i = 0; i < id_refs_size; i++)
3434 if (!strcmp(id, id_refs[i][0]->id))
3435 return id_refs[i];
3437 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3438 if (!tmp_id_refs)
3439 return NULL;
3441 id_refs = tmp_id_refs;
3443 for (i = 0; i < refs_size; i++) {
3444 struct ref **tmp;
3446 if (strcmp(id, refs[i].id))
3447 continue;
3449 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3450 if (!tmp) {
3451 if (ref_list)
3452 free(ref_list);
3453 return NULL;
3454 }
3456 ref_list = tmp;
3457 if (ref_list_size > 0)
3458 ref_list[ref_list_size - 1]->next = 1;
3459 ref_list[ref_list_size] = &refs[i];
3461 /* XXX: The properties of the commit chains ensures that we can
3462 * safely modify the shared ref. The repo references will
3463 * always be similar for the same id. */
3464 ref_list[ref_list_size]->next = 0;
3465 ref_list_size++;
3466 }
3468 if (ref_list)
3469 id_refs[id_refs_size++] = ref_list;
3471 return ref_list;
3472 }
3474 static int
3475 read_ref(char *id, int idlen, char *name, int namelen)
3476 {
3477 struct ref *ref;
3478 bool tag = FALSE;
3480 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3481 /* Commits referenced by tags has "^{}" appended. */
3482 if (name[namelen - 1] != '}')
3483 return OK;
3485 while (namelen > 0 && name[namelen] != '^')
3486 namelen--;
3488 tag = TRUE;
3489 namelen -= STRING_SIZE("refs/tags/");
3490 name += STRING_SIZE("refs/tags/");
3492 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
3493 namelen -= STRING_SIZE("refs/heads/");
3494 name += STRING_SIZE("refs/heads/");
3496 } else if (!strcmp(name, "HEAD")) {
3497 return OK;
3498 }
3500 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3501 if (!refs)
3502 return ERR;
3504 ref = &refs[refs_size++];
3505 ref->name = malloc(namelen + 1);
3506 if (!ref->name)
3507 return ERR;
3509 strncpy(ref->name, name, namelen);
3510 ref->name[namelen] = 0;
3511 ref->tag = tag;
3512 string_copy(ref->id, id);
3514 return OK;
3515 }
3517 static int
3518 load_refs(void)
3519 {
3520 const char *cmd_env = getenv("TIG_LS_REMOTE");
3521 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
3523 return read_properties(popen(cmd, "r"), "\t", read_ref);
3524 }
3526 static int
3527 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
3528 {
3529 if (!strcmp(name, "i18n.commitencoding"))
3530 string_copy(opt_encoding, value);
3532 return OK;
3533 }
3535 static int
3536 load_repo_config(void)
3537 {
3538 return read_properties(popen("git repo-config --list", "r"),
3539 "=", read_repo_config_option);
3540 }
3542 static int
3543 read_properties(FILE *pipe, const char *separators,
3544 int (*read_property)(char *, int, char *, int))
3545 {
3546 char buffer[BUFSIZ];
3547 char *name;
3548 int state = OK;
3550 if (!pipe)
3551 return ERR;
3553 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
3554 char *value;
3555 size_t namelen;
3556 size_t valuelen;
3558 name = chomp_string(name);
3559 namelen = strcspn(name, separators);
3561 if (name[namelen]) {
3562 name[namelen] = 0;
3563 value = chomp_string(name + namelen + 1);
3564 valuelen = strlen(value);
3566 } else {
3567 value = "";
3568 valuelen = 0;
3569 }
3571 state = read_property(name, namelen, value, valuelen);
3572 }
3574 if (state != ERR && ferror(pipe))
3575 state = ERR;
3577 pclose(pipe);
3579 return state;
3580 }
3583 /*
3584 * Main
3585 */
3587 static void __NORETURN
3588 quit(int sig)
3589 {
3590 /* XXX: Restore tty modes and let the OS cleanup the rest! */
3591 if (cursed)
3592 endwin();
3593 exit(0);
3594 }
3596 static void __NORETURN
3597 die(const char *err, ...)
3598 {
3599 va_list args;
3601 endwin();
3603 va_start(args, err);
3604 fputs("tig: ", stderr);
3605 vfprintf(stderr, err, args);
3606 fputs("\n", stderr);
3607 va_end(args);
3609 exit(1);
3610 }
3612 int
3613 main(int argc, char *argv[])
3614 {
3615 struct view *view;
3616 enum request request;
3617 size_t i;
3619 signal(SIGINT, quit);
3621 if (setlocale(LC_ALL, "")) {
3622 string_copy(opt_codeset, nl_langinfo(CODESET));
3623 }
3625 if (load_options() == ERR)
3626 die("Failed to load user config.");
3628 /* Load the repo config file so options can be overwritten from
3629 * the command line. */
3630 if (load_repo_config() == ERR)
3631 die("Failed to load repo config.");
3633 if (!parse_options(argc, argv))
3634 return 0;
3636 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3637 opt_iconv = iconv_open(opt_codeset, opt_encoding);
3638 if (opt_iconv == ICONV_NONE)
3639 die("Failed to initialize character set conversion");
3640 }
3642 if (load_refs() == ERR)
3643 die("Failed to load refs.");
3645 /* Require a git repository unless when running in pager mode. */
3646 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3647 die("Not a git repository");
3649 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3650 view->cmd_env = getenv(view->cmd_env);
3652 request = opt_request;
3654 init_display();
3656 while (view_driver(display[current_view], request)) {
3657 int key;
3658 int i;
3660 foreach_view (view, i)
3661 update_view(view);
3663 /* Refresh, accept single keystroke of input */
3664 key = wgetch(status_win);
3666 request = get_keybinding(display[current_view]->keymap, key);
3668 /* Some low-level request handling. This keeps access to
3669 * status_win restricted. */
3670 switch (request) {
3671 case REQ_PROMPT:
3672 {
3673 char *cmd = read_prompt(":");
3675 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3676 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3677 opt_request = REQ_VIEW_DIFF;
3678 } else {
3679 opt_request = REQ_VIEW_PAGER;
3680 }
3681 break;
3682 }
3684 request = REQ_NONE;
3685 break;
3686 }
3687 case REQ_SEARCH:
3688 case REQ_SEARCH_BACK:
3689 {
3690 const char *prompt = request == REQ_SEARCH
3691 ? "/" : "?";
3692 char *search = read_prompt(prompt);
3694 if (search)
3695 string_copy(opt_search, search);
3696 else
3697 request = REQ_NONE;
3698 break;
3699 }
3700 case REQ_SCREEN_RESIZE:
3701 {
3702 int height, width;
3704 getmaxyx(stdscr, height, width);
3706 /* Resize the status view and let the view driver take
3707 * care of resizing the displayed views. */
3708 wresize(status_win, 1, width);
3709 mvwin(status_win, height - 1, 0);
3710 wrefresh(status_win);
3711 break;
3712 }
3713 default:
3714 break;
3715 }
3716 }
3718 quit(0);
3720 return 0;
3721 }