Code

Improve title updating and remove flickering
[tig.git] / tig.c
1 /* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2  * See license info at the bottom. */
3 /**
4  * TIG(1)
5  * ======
6  *
7  * NAME
8  * ----
9  * tig - text-mode interface for git
10  *
11  * SYNOPSIS
12  * --------
13  * [verse]
14  * tig [options]
15  * tig [options] [--] [git log options]
16  * tig [options] log  [git log options]
17  * tig [options] diff [git diff options]
18  * tig [options] show [git show options]
19  * tig [options] <    [git command output]
20  *
21  * DESCRIPTION
22  * -----------
23  * Browse changes in a git repository. Additionally, tig(1) can also act
24  * as a pager for output of various git commands.
25  *
26  * When browsing repositories, tig(1) uses the underlying git commands
27  * to present the user with various views, such as summarized commit log
28  * and showing the commit with the log message, diffstat, and the diff.
29  *
30  * Using tig(1) as a pager, it will display input from stdin and try
31  * to colorize it.
32  **/
34 #ifndef VERSION
35 #define VERSION "tig-0.3"
36 #endif
38 #ifndef DEBUG
39 #define NDEBUG
40 #endif
42 #include <assert.h>
43 #include <errno.h>
44 #include <ctype.h>
45 #include <signal.h>
46 #include <stdarg.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 #include <time.h>
53 #include <curses.h>
55 static void die(const char *err, ...);
56 static void report(const char *msg, ...);
57 static void set_nonblocking_input(bool loading);
58 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
60 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
61 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
63 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
64 #define STRING_SIZE(x)  (sizeof(x) - 1)
66 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
67 #define SIZEOF_CMD      1024    /* Size of command buffer. */
69 /* This color name can be used to refer to the default term colors. */
70 #define COLOR_DEFAULT   (-1)
72 #define TIG_HELP        "(d)iff, (l)og, (m)ain, (q)uit, (h)elp"
74 /* The format and size of the date column in the main view. */
75 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
76 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
78 #define AUTHOR_COLS     20
80 /* The default interval between line numbers. */
81 #define NUMBER_INTERVAL 1
83 #define TABSIZE         8
85 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
87 /* Some ascii-shorthands fitted into the ncurses namespace. */
88 #define KEY_TAB         '\t'
89 #define KEY_RETURN      '\r'
90 #define KEY_ESC         27
93 /* User action requests. */
94 enum request {
95         /* Offset all requests to avoid conflicts with ncurses getch values. */
96         REQ_OFFSET = KEY_MAX + 1,
98         /* XXX: Keep the view request first and in sync with views[]. */
99         REQ_VIEW_MAIN,
100         REQ_VIEW_DIFF,
101         REQ_VIEW_LOG,
102         REQ_VIEW_HELP,
103         REQ_VIEW_PAGER,
105         REQ_ENTER,
106         REQ_QUIT,
107         REQ_PROMPT,
108         REQ_SCREEN_REDRAW,
109         REQ_SCREEN_RESIZE,
110         REQ_SCREEN_UPDATE,
111         REQ_SHOW_VERSION,
112         REQ_STOP_LOADING,
113         REQ_TOGGLE_LINE_NUMBERS,
114         REQ_VIEW_NEXT,
115         REQ_VIEW_CLOSE,
116         REQ_NEXT,
117         REQ_PREVIOUS,
119         REQ_MOVE_UP,
120         REQ_MOVE_DOWN,
121         REQ_MOVE_PAGE_UP,
122         REQ_MOVE_PAGE_DOWN,
123         REQ_MOVE_FIRST_LINE,
124         REQ_MOVE_LAST_LINE,
126         REQ_SCROLL_LINE_UP,
127         REQ_SCROLL_LINE_DOWN,
128         REQ_SCROLL_PAGE_UP,
129         REQ_SCROLL_PAGE_DOWN,
130 };
132 struct ref {
133         char *name;             /* Ref name; tag or head names are shortened. */
134         char id[41];            /* Commit SHA1 ID */
135         unsigned int tag:1;     /* Is it a tag? */
136         unsigned int next:1;    /* For ref lists: are there more refs? */
137 };
139 static struct ref **get_refs(char *id);
142 /*
143  * String helpers
144  */
146 static inline void
147 string_ncopy(char *dst, const char *src, int dstlen)
149         strncpy(dst, src, dstlen - 1);
150         dst[dstlen - 1] = 0;
154 /* Shorthand for safely copying into a fixed buffer. */
155 #define string_copy(dst, src) \
156         string_ncopy(dst, src, sizeof(dst))
159 /* Shell quoting
160  *
161  * NOTE: The following is a slightly modified copy of the git project's shell
162  * quoting routines found in the quote.c file.
163  *
164  * Help to copy the thing properly quoted for the shell safety.  any single
165  * quote is replaced with '\'', any exclamation point is replaced with '\!',
166  * and the whole thing is enclosed in a
167  *
168  * E.g.
169  *  original     sq_quote     result
170  *  name     ==> name      ==> 'name'
171  *  a b      ==> a b       ==> 'a b'
172  *  a'b      ==> a'\''b    ==> 'a'\''b'
173  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
174  */
176 static size_t
177 sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
179         char c;
181 #define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
183         BUFPUT('\'');
184         while ((c = *src++)) {
185                 if (c == '\'' || c == '!') {
186                         BUFPUT('\'');
187                         BUFPUT('\\');
188                         BUFPUT(c);
189                         BUFPUT('\'');
190                 } else {
191                         BUFPUT(c);
192                 }
193         }
194         BUFPUT('\'');
196         return bufsize;
200 /**
201  * OPTIONS
202  * -------
203  **/
205 static const char usage[] =
206 VERSION " (" __DATE__ ")\n"
207 "\n"
208 "Usage: tig [options]\n"
209 "   or: tig [options] [--] [git log options]\n"
210 "   or: tig [options] log  [git log options]\n"
211 "   or: tig [options] diff [git diff options]\n"
212 "   or: tig [options] show [git show options]\n"
213 "   or: tig [options] <    [git command output]\n"
214 "\n"
215 "Options:\n"
216 "  -l                          Start up in log view\n"
217 "  -d                          Start up in diff view\n"
218 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
219 "  -t[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
220 "  --                          Mark end of tig options\n"
221 "  -v, --version               Show version and exit\n"
222 "  -h, --help                  Show help message and exit\n";
224 /* Option and state variables. */
225 static bool opt_line_number     = FALSE;
226 static int opt_num_interval     = NUMBER_INTERVAL;
227 static int opt_tab_size         = TABSIZE;
228 static enum request opt_request = REQ_VIEW_MAIN;
229 static char opt_cmd[SIZEOF_CMD] = "";
230 static FILE *opt_pipe           = NULL;
232 /* Returns the index of log or diff command or -1 to exit. */
233 static bool
234 parse_options(int argc, char *argv[])
236         int i;
238         for (i = 1; i < argc; i++) {
239                 char *opt = argv[i];
241                 /**
242                  * -l::
243                  *      Start up in log view using the internal log command.
244                  **/
245                 if (!strcmp(opt, "-l")) {
246                         opt_request = REQ_VIEW_LOG;
247                         continue;
248                 }
250                 /**
251                  * -d::
252                  *      Start up in diff view using the internal diff command.
253                  **/
254                 if (!strcmp(opt, "-d")) {
255                         opt_request = REQ_VIEW_DIFF;
256                         continue;
257                 }
259                 /**
260                  * -n[INTERVAL], --line-number[=INTERVAL]::
261                  *      Prefix line numbers in log and diff view.
262                  *      Optionally, with interval different than each line.
263                  **/
264                 if (!strncmp(opt, "-n", 2) ||
265                     !strncmp(opt, "--line-number", 13)) {
266                         char *num = opt;
268                         if (opt[1] == 'n') {
269                                 num = opt + 2;
271                         } else if (opt[STRING_SIZE("--line-number")] == '=') {
272                                 num = opt + STRING_SIZE("--line-number=");
273                         }
275                         if (isdigit(*num))
276                                 opt_num_interval = atoi(num);
278                         opt_line_number = TRUE;
279                         continue;
280                 }
282                 /**
283                  * -t[NSPACES], --tab-size[=NSPACES]::
284                  *      Set the number of spaces tabs should be expanded to.
285                  **/
286                 if (!strncmp(opt, "-t", 2) ||
287                     !strncmp(opt, "--tab-size", 10)) {
288                         char *num = opt;
290                         if (opt[1] == 't') {
291                                 num = opt + 2;
293                         } else if (opt[STRING_SIZE("--tab-size")] == '=') {
294                                 num = opt + STRING_SIZE("--tab-size=");
295                         }
297                         if (isdigit(*num))
298                                 opt_tab_size = MIN(atoi(num), TABSIZE);
299                         continue;
300                 }
302                 /**
303                  * -v, --version::
304                  *      Show version and exit.
305                  **/
306                 if (!strcmp(opt, "-v") ||
307                     !strcmp(opt, "--version")) {
308                         printf("tig version %s\n", VERSION);
309                         return FALSE;
310                 }
312                 /**
313                  * -h, --help::
314                  *      Show help message and exit.
315                  **/
316                 if (!strcmp(opt, "-h") ||
317                     !strcmp(opt, "--help")) {
318                         printf(usage);
319                         return FALSE;
320                 }
322                 /**
323                  * \--::
324                  *      End of tig(1) options. Useful when specifying command
325                  *      options for the main view. Example:
326                  *
327                  *              $ tig -- --since=1.month
328                  **/
329                 if (!strcmp(opt, "--")) {
330                         i++;
331                         break;
332                 }
334                 /**
335                  * log [git log options]::
336                  *      Open log view using the given git log options.
337                  *
338                  * diff [git diff options]::
339                  *      Open diff view using the given git diff options.
340                  *
341                  * show [git show options]::
342                  *      Open diff view using the given git show options.
343                  **/
344                 if (!strcmp(opt, "log") ||
345                     !strcmp(opt, "diff") ||
346                     !strcmp(opt, "show")) {
347                         opt_request = opt[0] == 'l'
348                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
349                         break;
350                 }
352                 /**
353                  * [git log options]::
354                  *      tig(1) will stop the option parsing when the first
355                  *      command line parameter not starting with "-" is
356                  *      encountered. All options including this one will be
357                  *      passed to git log when loading the main view.
358                  *      This makes it possible to say:
359                  *
360                  *      $ tig tag-1.0..HEAD
361                  **/
362                 if (opt[0] && opt[0] != '-')
363                         break;
365                 die("unknown command '%s'", opt);
366         }
368         if (!isatty(STDIN_FILENO)) {
369                 /**
370                  * Pager mode
371                  * ~~~~~~~~~~
372                  * If stdin is a pipe, any log or diff options will be ignored and the
373                  * pager view will be opened loading data from stdin. The pager mode
374                  * can be used for colorizing output from various git commands.
375                  *
376                  * Example on how to colorize the output of git-show(1):
377                  *
378                  *      $ git show | tig
379                  **/
380                 opt_request = REQ_VIEW_PAGER;
381                 opt_pipe = stdin;
383         } else if (i < argc) {
384                 size_t buf_size;
386                 /**
387                  * Git command options
388                  * ~~~~~~~~~~~~~~~~~~~
389                  * All git command options specified on the command line will
390                  * be passed to the given command and all will be shell quoted
391                  * before they are passed to the shell.
392                  *
393                  * NOTE: If you specify options for the main view, you should
394                  * not use the `--pretty` option as this option will be set
395                  * automatically to the format expected by the main view.
396                  *
397                  * Example on how to open the log view and show both author and
398                  * committer information:
399                  *
400                  *      $ tig log --pretty=fuller
401                  *
402                  * See the <<refspec, "Specifying revisions">> section below
403                  * for an introduction to revision options supported by the git
404                  * commands. For details on specific git command options, refer
405                  * to the man page of the command in question.
406                  **/
408                 if (opt_request == REQ_VIEW_MAIN)
409                         /* XXX: This is vulnerable to the user overriding
410                          * options required for the main view parser. */
411                         string_copy(opt_cmd, "git log --stat --pretty=raw");
412                 else
413                         string_copy(opt_cmd, "git");
414                 buf_size = strlen(opt_cmd);
416                 while (buf_size < sizeof(opt_cmd) && i < argc) {
417                         opt_cmd[buf_size++] = ' ';
418                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
419                 }
421                 if (buf_size >= sizeof(opt_cmd))
422                         die("command too long");
424                 opt_cmd[buf_size] = 0;
426         }
428         return TRUE;
432 /*
433  * Line-oriented content detection.
434  */
436 #define LINE_INFO \
437 /*   Line type     String to match      Foreground      Background      Attributes
438  *   ---------     ---------------      ----------      ----------      ---------- */ \
439 /* Diff markup */ \
440 LINE(DIFF,         "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
441 LINE(DIFF_INDEX,   "index ",            COLOR_BLUE,     COLOR_DEFAULT,  0), \
442 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
443 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
444 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
445 LINE(DIFF_OLDMODE, "old file mode ",    COLOR_YELLOW,   COLOR_DEFAULT,  0), \
446 LINE(DIFF_NEWMODE, "new file mode ",    COLOR_YELLOW,   COLOR_DEFAULT,  0), \
447 LINE(DIFF_COPY,    "copy ",             COLOR_YELLOW,   COLOR_DEFAULT,  0), \
448 LINE(DIFF_RENAME,  "rename ",           COLOR_YELLOW,   COLOR_DEFAULT,  0), \
449 LINE(DIFF_SIM,     "similarity ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
450 LINE(DIFF_DISSIM,  "dissimilarity ",    COLOR_YELLOW,   COLOR_DEFAULT,  0), \
451 /* Pretty print commit header */ \
452 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
453 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
454 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
455 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
456 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
457 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
458 /* Raw commit header */ \
459 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
460 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
461 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
462 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
463 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
464 /* Misc */ \
465 LINE(DIFF_TREE,    "diff-tree ",        COLOR_BLUE,     COLOR_DEFAULT,  0), \
466 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
467 /* UI colors */ \
468 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
469 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
470 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
471 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
472 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
473 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
474 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
475 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
476 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
477 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
478 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD),
480 enum line_type {
481 #define LINE(type, line, fg, bg, attr) \
482         LINE_##type
483         LINE_INFO
484 #undef  LINE
485 };
487 struct line_info {
488         const char *line;       /* The start of line to match. */
489         int linelen;            /* Size of string to match. */
490         int fg, bg, attr;       /* Color and text attributes for the lines. */
491 };
493 static struct line_info line_info[] = {
494 #define LINE(type, line, fg, bg, attr) \
495         { (line), STRING_SIZE(line), (fg), (bg), (attr) }
496         LINE_INFO
497 #undef  LINE
498 };
500 static enum line_type
501 get_line_type(char *line)
503         int linelen = strlen(line);
504         enum line_type type;
506         for (type = 0; type < ARRAY_SIZE(line_info); type++)
507                 /* Case insensitive search matches Signed-off-by lines better. */
508                 if (linelen >= line_info[type].linelen &&
509                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
510                         return type;
512         return LINE_DEFAULT;
515 static inline int
516 get_line_attr(enum line_type type)
518         assert(type < ARRAY_SIZE(line_info));
519         return COLOR_PAIR(type) | line_info[type].attr;
522 static void
523 init_colors(void)
525         int default_bg = COLOR_BLACK;
526         int default_fg = COLOR_WHITE;
527         enum line_type type;
529         start_color();
531         if (use_default_colors() != ERR) {
532                 default_bg = -1;
533                 default_fg = -1;
534         }
536         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
537                 struct line_info *info = &line_info[type];
538                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
539                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
541                 init_pair(type, fg, bg);
542         }
546 /**
547  * ENVIRONMENT VARIABLES
548  * ---------------------
549  * Several options related to the interface with git can be configured
550  * via environment options.
551  *
552  * Repository references
553  * ~~~~~~~~~~~~~~~~~~~~~
554  * Commits that are referenced by tags and branch heads will be marked
555  * by the reference name surrounded by '[' and ']':
556  *
557  *      2006-03-26 19:42 Petr Baudis         | [cogito-0.17.1] Cogito 0.17.1
558  *
559  * If you want to filter out certain directories under `.git/refs/`, say
560  * `tmp` you can do it by setting the following variable:
561  *
562  *      $ TIG_LS_REMOTE="git ls-remote . | sed /\/tmp\//d" tig
563  *
564  * Or set the variable permanently in your environment.
565  *
566  * TIG_LS_REMOTE::
567  *      Set command for retrieving all repository references. The command
568  *      should output data in the same format as git-ls-remote(1).
569  **/
571 #define TIG_LS_REMOTE \
572         "git ls-remote . 2>/dev/null"
574 /**
575  * [[view-commands]]
576  * View commands
577  * ~~~~~~~~~~~~~
578  * It is possible to alter which commands are used for the different views.
579  * If for example you prefer commits in the main view to be sorted by date
580  * and only show 500 commits, use:
581  *
582  *      $ TIG_MAIN_CMD="git log --date-order -n500 --pretty=raw %s" tig
583  *
584  * Or set the variable permanently in your environment.
585  *
586  * Notice, how `%s` is used to specify the commit reference. There can
587  * be a maximum of 5 `%s` ref specifications.
588  *
589  * TIG_DIFF_CMD::
590  *      The command used for the diff view. By default, git show is used
591  *      as a backend.
592  *
593  * TIG_LOG_CMD::
594  *      The command used for the log view. If you prefer to have both
595  *      author and committer shown in the log view be sure to pass
596  *      `--pretty=fuller` to git log.
597  *
598  * TIG_MAIN_CMD::
599  *      The command used for the main view. Note, you must always specify
600  *      the option: `--pretty=raw` since the main view parser expects to
601  *      read that format.
602  **/
604 #define TIG_DIFF_CMD \
605         "git show --patch-with-stat --find-copies-harder -B -C %s"
607 #define TIG_LOG_CMD     \
608         "git log --cc --stat -n100 %s"
610 #define TIG_MAIN_CMD \
611         "git log --topo-order --stat --pretty=raw %s"
613 /* ... silently ignore that the following are also exported. */
615 #define TIG_HELP_CMD \
616         "man tig 2>/dev/null"
618 #define TIG_PAGER_CMD \
619         ""
622 /**
623  * The viewer
624  * ----------
625  * The display consists of a status window on the last line of the screen and
626  * one or more views. The default is to only show one view at the time but it
627  * is possible to split both the main and log view to also show the commit
628  * diff.
629  *
630  * If you are in the log view and press 'Enter' when the current line is a
631  * commit line, such as:
632  *
633  *      commit 4d55caff4cc89335192f3e566004b4ceef572521
634  *
635  * You will split the view so that the log view is displayed in the top window
636  * and the diff view in the bottom window. You can switch between the two
637  * views by pressing 'Tab'. To maximize the log view again, simply press 'l'.
638  **/
640 struct view;
642 /* The display array of active views and the index of the current view. */
643 static struct view *display[2];
644 static unsigned int current_view;
646 #define foreach_view(view, i) \
647         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
649 #define displayed_views()       (display[1] != NULL ? 2 : 1)
651 /**
652  * Current head and commit ID
653  * ~~~~~~~~~~~~~~~~~~~~~~~~~~
654  * The viewer keeps track of both what head and commit ID you are currently
655  * viewing. The commit ID will follow the cursor line and change everytime time
656  * you highlight a different commit. Whenever you reopen the diff view it
657  * will be reloaded, if the commit ID changed.
658  *
659  * The head ID is used when opening the main and log view to indicate from
660  * what revision to show history.
661  **/
663 static char ref_commit[SIZEOF_REF]      = "HEAD";
664 static char ref_head[SIZEOF_REF]        = "HEAD";
667 struct view {
668         const char *name;       /* View name */
669         const char *cmd_fmt;    /* Default command line format */
670         const char *cmd_env;    /* Command line set via environment */
671         const char *id;         /* Points to either of ref_{head,commit} */
673         struct view_ops {
674                 /* What type of content being displayed. Used in the
675                  * title bar. */
676                 const char *type;
677                 /* Draw one line; @lineno must be < view->height. */
678                 bool (*draw)(struct view *view, unsigned int lineno);
679                 /* Read one line; updates view->line. */
680                 bool (*read)(struct view *view, char *line);
681                 /* Depending on view, change display based on current line. */
682                 bool (*enter)(struct view *view);
683         } *ops;
685         char cmd[SIZEOF_CMD];   /* Command buffer */
686         char ref[SIZEOF_REF];   /* Hovered commit reference */
687         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
689         int height, width;      /* The width and height of the main window */
690         WINDOW *win;            /* The main window */
691         WINDOW *title;          /* The title window living below the main window */
693         /* Navigation */
694         unsigned long offset;   /* Offset of the window top */
695         unsigned long lineno;   /* Current line number */
697         /* If non-NULL, points to the view that opened this view. If this view
698          * is closed tig will switch back to the parent view. */
699         struct view *parent;
701         /* Buffering */
702         unsigned long lines;    /* Total number of lines */
703         void **line;            /* Line index; each line contains user data */
704         unsigned int digits;    /* Number of digits in the lines member. */
706         /* Loading */
707         FILE *pipe;
708         time_t start_time;
709 };
711 static struct view_ops pager_ops;
712 static struct view_ops main_ops;
714 #define VIEW_STR(name, cmd, env, ref, ops) \
715         { name, cmd, #env, ref, ops }
717 #define VIEW_(id, name, ops, ref) \
718         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops)
720 /**
721  * Views
722  * ~~~~~
723  * tig(1) presents various 'views' of a repository. Each view is based on output
724  * from an external command, most often 'git log', 'git diff', or 'git show'.
725  *
726  * The main view::
727  *      Is the default view, and it shows a one line summary of each commit
728  *      in the chosen list of revisions. The summary includes commit date,
729  *      author, and the first line of the log message. Additionally, any
730  *      repository references, such as tags, will be shown.
731  *
732  * The log view::
733  *      Presents a more rich view of the revision log showing the whole log
734  *      message and the diffstat.
735  *
736  * The diff view::
737  *      Shows either the diff of the current working tree, that is, what
738  *      has changed since the last commit, or the commit diff complete
739  *      with log message, diffstat and diff.
740  *
741  * The pager view::
742  *      Is used for displaying both input from stdin and output from git
743  *      commands entered in the internal prompt.
744  *
745  * The help view::
746  *      Displays the information from the tig(1) man page. For the help view
747  *      to work you need to have the tig(1) man page installed.
748  **/
750 static struct view views[] = {
751         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
752         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
753         VIEW_(LOG,   "log",   &pager_ops, ref_head),
754         VIEW_(HELP,  "help",  &pager_ops, "static"),
755         VIEW_(PAGER, "pager", &pager_ops, "static"),
756 };
758 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
761 static void
762 redraw_view_from(struct view *view, int lineno)
764         assert(0 <= lineno && lineno < view->height);
766         for (; lineno < view->height; lineno++) {
767                 if (!view->ops->draw(view, lineno))
768                         break;
769         }
771         redrawwin(view->win);
772         wrefresh(view->win);
775 static void
776 redraw_view(struct view *view)
778         wclear(view->win);
779         redraw_view_from(view, 0);
783 /**
784  * Title windows
785  * ~~~~~~~~~~~~~
786  * Each view has a title window which shows the name of the view, current
787  * commit ID if available, and where the view is positioned:
788  *
789  *      [main] c622eefaa485995320bc743431bae0d497b1d875 - commit 1 of 61 (1%)
790  *
791  * By default, the title of the current view is highlighted using bold font.
792  **/
794 static void
795 update_view_title(struct view *view)
797         if (view == display[current_view])
798                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
799         else
800                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
802         werase(view->title);
803         wmove(view->title, 0, 0);
805         if (*view->ref)
806                 wprintw(view->title, "[%s] %s", view->name, view->ref);
807         else
808                 wprintw(view->title, "[%s]", view->name);
810         if (view->lines || view->pipe) {
811                 unsigned int lines = view->lines
812                                    ? (view->lineno + 1) * 100 / view->lines
813                                    : 0;
815                 wprintw(view->title, " - %s %d of %d (%d%%)",
816                         view->ops->type,
817                         view->lineno + 1,
818                         view->lines,
819                         lines);
820         }
822         if (view->pipe) {
823                 time_t secs = time(NULL) - view->start_time;
825                 /* Three git seconds are a long time ... */
826                 if (secs > 2)
827                         wprintw(view->title, " %lds", secs);
828         }
831         wrefresh(view->title);
834 static void
835 resize_display(void)
837         int offset, i;
838         struct view *base = display[0];
839         struct view *view = display[1] ? display[1] : display[0];
841         /* Setup window dimensions */
843         getmaxyx(stdscr, base->height, base->width);
845         /* Make room for the status window. */
846         base->height -= 1;
848         if (view != base) {
849                 /* Horizontal split. */
850                 view->width   = base->width;
851                 view->height  = SCALE_SPLIT_VIEW(base->height);
852                 base->height -= view->height;
854                 /* Make room for the title bar. */
855                 view->height -= 1;
856         }
858         /* Make room for the title bar. */
859         base->height -= 1;
861         offset = 0;
863         foreach_view (view, i) {
864                 if (!view->win) {
865                         view->win = newwin(view->height, 0, offset, 0);
866                         if (!view->win)
867                                 die("Failed to create %s view", view->name);
869                         scrollok(view->win, TRUE);
871                         view->title = newwin(1, 0, offset + view->height, 0);
872                         if (!view->title)
873                                 die("Failed to create title window");
875                 } else {
876                         wresize(view->win, view->height, view->width);
877                         mvwin(view->win,   offset, 0);
878                         mvwin(view->title, offset + view->height, 0);
879                         wrefresh(view->win);
880                 }
882                 offset += view->height + 1;
883         }
886 static void
887 redraw_display(void)
889         struct view *view;
890         int i;
892         foreach_view (view, i) {
893                 redraw_view(view);
894                 update_view_title(view);
895         }
899 /*
900  * Navigation
901  */
903 /* Scrolling backend */
904 static void
905 do_scroll_view(struct view *view, int lines, bool redraw)
907         /* The rendering expects the new offset. */
908         view->offset += lines;
910         assert(0 <= view->offset && view->offset < view->lines);
911         assert(lines);
913         /* Redraw the whole screen if scrolling is pointless. */
914         if (view->height < ABS(lines)) {
915                 redraw_view(view);
917         } else {
918                 int line = lines > 0 ? view->height - lines : 0;
919                 int end = line + ABS(lines);
921                 wscrl(view->win, lines);
923                 for (; line < end; line++) {
924                         if (!view->ops->draw(view, line))
925                                 break;
926                 }
927         }
929         /* Move current line into the view. */
930         if (view->lineno < view->offset) {
931                 view->lineno = view->offset;
932                 view->ops->draw(view, 0);
934         } else if (view->lineno >= view->offset + view->height) {
935                 if (view->lineno == view->offset + view->height) {
936                         /* Clear the hidden line so it doesn't show if the view
937                          * is scrolled up. */
938                         wmove(view->win, view->height, 0);
939                         wclrtoeol(view->win);
940                 }
941                 view->lineno = view->offset + view->height - 1;
942                 view->ops->draw(view, view->lineno - view->offset);
943         }
945         assert(view->offset <= view->lineno && view->lineno < view->lines);
947         if (!redraw)
948                 return;
950         redrawwin(view->win);
951         wrefresh(view->win);
952         report("");
955 /* Scroll frontend */
956 static void
957 scroll_view(struct view *view, enum request request)
959         int lines = 1;
961         switch (request) {
962         case REQ_SCROLL_PAGE_DOWN:
963                 lines = view->height;
964         case REQ_SCROLL_LINE_DOWN:
965                 if (view->offset + lines > view->lines)
966                         lines = view->lines - view->offset;
968                 if (lines == 0 || view->offset + view->height >= view->lines) {
969                         report("Cannot scroll beyond the last line");
970                         return;
971                 }
972                 break;
974         case REQ_SCROLL_PAGE_UP:
975                 lines = view->height;
976         case REQ_SCROLL_LINE_UP:
977                 if (lines > view->offset)
978                         lines = view->offset;
980                 if (lines == 0) {
981                         report("Cannot scroll beyond the first line");
982                         return;
983                 }
985                 lines = -lines;
986                 break;
988         default:
989                 die("request %d not handled in switch", request);
990         }
992         do_scroll_view(view, lines, TRUE);
995 /* Cursor moving */
996 static void
997 move_view(struct view *view, enum request request, bool redraw)
999         int steps;
1001         switch (request) {
1002         case REQ_MOVE_FIRST_LINE:
1003                 steps = -view->lineno;
1004                 break;
1006         case REQ_MOVE_LAST_LINE:
1007                 steps = view->lines - view->lineno - 1;
1008                 break;
1010         case REQ_MOVE_PAGE_UP:
1011                 steps = view->height > view->lineno
1012                       ? -view->lineno : -view->height;
1013                 break;
1015         case REQ_MOVE_PAGE_DOWN:
1016                 steps = view->lineno + view->height >= view->lines
1017                       ? view->lines - view->lineno - 1 : view->height;
1018                 break;
1020         case REQ_MOVE_UP:
1021                 steps = -1;
1022                 break;
1024         case REQ_MOVE_DOWN:
1025                 steps = 1;
1026                 break;
1028         default:
1029                 die("request %d not handled in switch", request);
1030         }
1032         if (steps <= 0 && view->lineno == 0) {
1033                 report("Cannot move beyond the first line");
1034                 return;
1036         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1037                 report("Cannot move beyond the last line");
1038                 return;
1039         }
1041         /* Move the current line */
1042         view->lineno += steps;
1043         assert(0 <= view->lineno && view->lineno < view->lines);
1045         /* Repaint the old "current" line if we be scrolling */
1046         if (ABS(steps) < view->height) {
1047                 int prev_lineno = view->lineno - steps - view->offset;
1049                 wmove(view->win, prev_lineno, 0);
1050                 wclrtoeol(view->win);
1051                 view->ops->draw(view, prev_lineno);
1052         }
1054         /* Check whether the view needs to be scrolled */
1055         if (view->lineno < view->offset ||
1056             view->lineno >= view->offset + view->height) {
1057                 if (steps < 0 && -steps > view->offset) {
1058                         steps = -view->offset;
1060                 } else if (steps > 0) {
1061                         if (view->lineno == view->lines - 1 &&
1062                             view->lines > view->height) {
1063                                 steps = view->lines - view->offset - 1;
1064                                 if (steps >= view->height)
1065                                         steps -= view->height - 1;
1066                         }
1067                 }
1069                 do_scroll_view(view, steps, redraw);
1070                 return;
1071         }
1073         /* Draw the current line */
1074         view->ops->draw(view, view->lineno - view->offset);
1076         if (!redraw)
1077                 return;
1079         redrawwin(view->win);
1080         wrefresh(view->win);
1081         report("");
1085 /*
1086  * Incremental updating
1087  */
1089 static void
1090 end_update(struct view *view)
1092         if (!view->pipe)
1093                 return;
1094         set_nonblocking_input(FALSE);
1095         if (view->pipe == stdin)
1096                 fclose(view->pipe);
1097         else
1098                 pclose(view->pipe);
1099         view->pipe = NULL;
1102 static bool
1103 begin_update(struct view *view)
1105         const char *id = view->id;
1107         if (view->pipe)
1108                 end_update(view);
1110         if (opt_cmd[0]) {
1111                 string_copy(view->cmd, opt_cmd);
1112                 opt_cmd[0] = 0;
1113                 /* When running random commands, the view ref could have become
1114                  * invalid so clear it. */
1115                 view->ref[0] = 0;
1116         } else {
1117                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1119                 if (snprintf(view->cmd, sizeof(view->cmd), format,
1120                              id, id, id, id, id) >= sizeof(view->cmd))
1121                         return FALSE;
1122         }
1124         /* Special case for the pager view. */
1125         if (opt_pipe) {
1126                 view->pipe = opt_pipe;
1127                 opt_pipe = NULL;
1128         } else {
1129                 view->pipe = popen(view->cmd, "r");
1130         }
1132         if (!view->pipe)
1133                 return FALSE;
1135         set_nonblocking_input(TRUE);
1137         view->offset = 0;
1138         view->lines  = 0;
1139         view->lineno = 0;
1140         string_copy(view->vid, id);
1142         if (view->line) {
1143                 int i;
1145                 for (i = 0; i < view->lines; i++)
1146                         if (view->line[i])
1147                                 free(view->line[i]);
1149                 free(view->line);
1150                 view->line = NULL;
1151         }
1153         view->start_time = time(NULL);
1155         return TRUE;
1158 static bool
1159 update_view(struct view *view)
1161         char buffer[BUFSIZ];
1162         char *line;
1163         void **tmp;
1164         /* The number of lines to read. If too low it will cause too much
1165          * redrawing (and possible flickering), if too high responsiveness
1166          * will suffer. */
1167         unsigned long lines = view->height;
1168         int redraw_from = -1;
1170         if (!view->pipe)
1171                 return TRUE;
1173         /* Only redraw if lines are visible. */
1174         if (view->offset + view->height >= view->lines)
1175                 redraw_from = view->lines - view->offset;
1177         tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
1178         if (!tmp)
1179                 goto alloc_error;
1181         view->line = tmp;
1183         while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1184                 int linelen = strlen(line);
1186                 if (linelen)
1187                         line[linelen - 1] = 0;
1189                 if (!view->ops->read(view, line))
1190                         goto alloc_error;
1192                 if (lines-- == 1)
1193                         break;
1194         }
1196         {
1197                 int digits;
1199                 lines = view->lines;
1200                 for (digits = 0; lines; digits++)
1201                         lines /= 10;
1203                 /* Keep the displayed view in sync with line number scaling. */
1204                 if (digits != view->digits) {
1205                         view->digits = digits;
1206                         redraw_from = 0;
1207                 }
1208         }
1210         if (redraw_from >= 0) {
1211                 /* If this is an incremental update, redraw the previous line
1212                  * since for commits some members could have changed when
1213                  * loading the main view. */
1214                 if (redraw_from > 0)
1215                         redraw_from--;
1217                 /* Incrementally draw avoids flickering. */
1218                 redraw_view_from(view, redraw_from);
1219         }
1221         /* Update the title _after_ the redraw so that if the redraw picks up a
1222          * commit reference in view->ref it'll be available here. */
1223         update_view_title(view);
1225         if (ferror(view->pipe)) {
1226                 report("Failed to read: %s", strerror(errno));
1227                 goto end;
1229         } else if (feof(view->pipe)) {
1230                 if (view == VIEW(REQ_VIEW_HELP)) {
1231                         const char *msg = TIG_HELP;
1233                         if (view->lines == 0) {
1234                                 /* Slightly ugly, but abusing view->ref keeps
1235                                  * the error message. */
1236                                 string_copy(view->ref, "No help available");
1237                                 msg = "The tig(1) manpage is not installed";
1238                         }
1240                         report("%s", msg);
1241                         goto end;
1242                 }
1244                 report("");
1245                 goto end;
1246         }
1248         return TRUE;
1250 alloc_error:
1251         report("Allocation failure");
1253 end:
1254         end_update(view);
1255         return FALSE;
1258 enum open_flags {
1259         OPEN_DEFAULT = 0,       /* Use default view switching. */
1260         OPEN_SPLIT = 1,         /* Split current view. */
1261         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1262         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1263 };
1265 static void
1266 open_view(struct view *prev, enum request request, enum open_flags flags)
1268         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1269         bool split = !!(flags & OPEN_SPLIT);
1270         bool reload = !!(flags & OPEN_RELOAD);
1271         struct view *view = VIEW(request);
1272         int nviews = displayed_views();
1273         struct view *base_view = display[0];
1275         if (view == prev && nviews == 1 && !reload) {
1276                 report("Already in %s view", view->name);
1277                 return;
1278         }
1280         if ((reload || strcmp(view->vid, view->id)) &&
1281             !begin_update(view)) {
1282                 report("Failed to load %s view", view->name);
1283                 return;
1284         }
1286         if (split) {
1287                 display[current_view + 1] = view;
1288                 if (!backgrounded)
1289                         current_view++;
1290         } else {
1291                 /* Maximize the current view. */
1292                 memset(display, 0, sizeof(display));
1293                 current_view = 0;
1294                 display[current_view] = view;
1295         }
1297         /* Resize the view when switching between split- and full-screen,
1298          * or when switching between two different full-screen views. */
1299         if (nviews != displayed_views() ||
1300             (nviews == 1 && base_view != display[0]))
1301                 resize_display();
1303         if (split && prev->lineno - prev->offset >= prev->height) {
1304                 /* Take the title line into account. */
1305                 int lines = prev->lineno - prev->offset - prev->height + 1;
1307                 /* Scroll the view that was split if the current line is
1308                  * outside the new limited view. */
1309                 do_scroll_view(prev, lines, TRUE);
1310         }
1312         if (prev && view != prev) {
1313                 /* Continue loading split views in the background. */
1314                 if (!split)
1315                         end_update(prev);
1316                 else if (!backgrounded)
1317                         /* "Blur" the previous view. */
1318                         update_view_title(prev);
1320                 view->parent = prev;
1321         }
1323         if (view->pipe) {
1324                 /* Clear the old view and let the incremental updating refill
1325                  * the screen. */
1326                 wclear(view->win);
1327                 report("");
1328         } else {
1329                 redraw_view(view);
1330                 if (view == VIEW(REQ_VIEW_HELP))
1331                         report("%s", TIG_HELP);
1332                 else
1333                         report("");
1334         }
1336         /* If the view is backgrounded the above calls to report()
1337          * won't redraw the view title. */
1338         if (backgrounded)
1339                 update_view_title(view);
1343 /*
1344  * User request switch noodle
1345  */
1347 static int
1348 view_driver(struct view *view, enum request request)
1350         int i;
1352         switch (request) {
1353         case REQ_MOVE_UP:
1354         case REQ_MOVE_DOWN:
1355         case REQ_MOVE_PAGE_UP:
1356         case REQ_MOVE_PAGE_DOWN:
1357         case REQ_MOVE_FIRST_LINE:
1358         case REQ_MOVE_LAST_LINE:
1359                 move_view(view, request, TRUE);
1360                 break;
1362         case REQ_SCROLL_LINE_DOWN:
1363         case REQ_SCROLL_LINE_UP:
1364         case REQ_SCROLL_PAGE_DOWN:
1365         case REQ_SCROLL_PAGE_UP:
1366                 scroll_view(view, request);
1367                 break;
1369         case REQ_VIEW_MAIN:
1370         case REQ_VIEW_DIFF:
1371         case REQ_VIEW_LOG:
1372         case REQ_VIEW_HELP:
1373         case REQ_VIEW_PAGER:
1374                 open_view(view, request, OPEN_DEFAULT);
1375                 break;
1377         case REQ_NEXT:
1378         case REQ_PREVIOUS:
1379                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1381                 if (view == VIEW(REQ_VIEW_DIFF) &&
1382                     view->parent == VIEW(REQ_VIEW_MAIN)) {
1383                         bool redraw = display[1] == view;
1385                         view = view->parent;
1386                         move_view(view, request, redraw);
1387                         if (redraw)
1388                                 update_view_title(view);
1389                 } else {
1390                         move_view(view, request, TRUE);
1391                         break;
1392                 }
1393                 /* Fall-through */
1395         case REQ_ENTER:
1396                 if (!view->lines) {
1397                         report("Nothing to enter");
1398                         break;
1399                 }
1400                 return view->ops->enter(view);
1402         case REQ_VIEW_NEXT:
1403         {
1404                 int nviews = displayed_views();
1405                 int next_view = (current_view + 1) % nviews;
1407                 if (next_view == current_view) {
1408                         report("Only one view is displayed");
1409                         break;
1410                 }
1412                 current_view = next_view;
1413                 /* Blur out the title of the previous view. */
1414                 update_view_title(view);
1415                 report("");
1416                 break;
1417         }
1418         case REQ_TOGGLE_LINE_NUMBERS:
1419                 opt_line_number = !opt_line_number;
1420                 redraw_display();
1421                 break;
1423         case REQ_PROMPT:
1424                 /* Always reload^Wrerun commands from the prompt. */
1425                 open_view(view, opt_request, OPEN_RELOAD);
1426                 break;
1428         case REQ_STOP_LOADING:
1429                 foreach_view (view, i) {
1430                         if (view->pipe)
1431                                 report("Stopped loaded the %s view", view->name),
1432                         end_update(view);
1433                 }
1434                 break;
1436         case REQ_SHOW_VERSION:
1437                 report("%s (built %s)", VERSION, __DATE__);
1438                 return TRUE;
1440         case REQ_SCREEN_RESIZE:
1441                 resize_display();
1442                 /* Fall-through */
1443         case REQ_SCREEN_REDRAW:
1444                 redraw_display();
1445                 break;
1447         case REQ_SCREEN_UPDATE:
1448                 doupdate();
1449                 return TRUE;
1451         case REQ_VIEW_CLOSE:
1452                 if (view->parent) {
1453                         memset(display, 0, sizeof(display));
1454                         current_view = 0;
1455                         display[current_view] = view->parent;
1456                         view->parent = NULL;
1457                         resize_display();
1458                         redraw_display();
1459                         break;
1460                 }
1461                 /* Fall-through */
1462         case REQ_QUIT:
1463                 return FALSE;
1465         default:
1466                 /* An unknown key will show most commonly used commands. */
1467                 report("Unknown key, press 'h' for help");
1468                 return TRUE;
1469         }
1471         return TRUE;
1475 /*
1476  * Pager backend
1477  */
1479 static bool
1480 pager_draw(struct view *view, unsigned int lineno)
1482         enum line_type type;
1483         char *line;
1484         int linelen;
1485         int attr;
1487         if (view->offset + lineno >= view->lines)
1488                 return FALSE;
1490         line = view->line[view->offset + lineno];
1491         type = get_line_type(line);
1493         wmove(view->win, lineno, 0);
1495         if (view->offset + lineno == view->lineno) {
1496                 if (type == LINE_COMMIT) {
1497                         string_copy(view->ref, line + 7);
1498                         string_copy(ref_commit, view->ref);
1499                 }
1501                 type = LINE_CURSOR;
1502                 wchgat(view->win, -1, 0, type, NULL);
1503         }
1505         attr = get_line_attr(type);
1506         wattrset(view->win, attr);
1508         linelen = strlen(line);
1510         if (opt_line_number || opt_tab_size < TABSIZE) {
1511                 static char spaces[] = "                    ";
1512                 int col_offset = 0, col = 0;
1514                 if (opt_line_number) {
1515                         unsigned long real_lineno = view->offset + lineno + 1;
1517                         if (real_lineno == 1 ||
1518                             (real_lineno % opt_num_interval) == 0) {
1519                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
1521                         } else {
1522                                 waddnstr(view->win, spaces,
1523                                          MIN(view->digits, STRING_SIZE(spaces)));
1524                         }
1525                         waddstr(view->win, ": ");
1526                         col_offset = view->digits + 2;
1527                 }
1529                 while (line && col_offset + col < view->width) {
1530                         int cols_max = view->width - col_offset - col;
1531                         char *text = line;
1532                         int cols;
1534                         if (*line == '\t') {
1535                                 assert(sizeof(spaces) > TABSIZE);
1536                                 line++;
1537                                 text = spaces;
1538                                 cols = opt_tab_size - (col % opt_tab_size);
1540                         } else {
1541                                 line = strchr(line, '\t');
1542                                 cols = line ? line - text : strlen(text);
1543                         }
1545                         waddnstr(view->win, text, MIN(cols, cols_max));
1546                         col += cols;
1547                 }
1549         } else {
1550                 int col = 0, pos = 0;
1552                 for (; pos < linelen && col < view->width; pos++, col++)
1553                         if (line[pos] == '\t')
1554                                 col += TABSIZE - (col % TABSIZE) - 1;
1556                 waddnstr(view->win, line, pos);
1557         }
1559         return TRUE;
1562 static bool
1563 pager_read(struct view *view, char *line)
1565         /* Compress empty lines in the help view. */
1566         if (view == VIEW(REQ_VIEW_HELP) &&
1567             !*line &&
1568             view->lines &&
1569             !*((char *) view->line[view->lines - 1]))
1570                 return TRUE;
1572         view->line[view->lines] = strdup(line);
1573         if (!view->line[view->lines])
1574                 return FALSE;
1576         view->lines++;
1577         return TRUE;
1580 static bool
1581 pager_enter(struct view *view)
1583         char *line = view->line[view->lineno];
1584         int split = 0;
1586         if ((view == VIEW(REQ_VIEW_LOG) ||
1587              view == VIEW(REQ_VIEW_PAGER)) &&
1588             get_line_type(line) == LINE_COMMIT) {
1589                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1590                 split = 1;
1591         }
1593         /* Always scroll the view even if it was split. That way
1594          * you can use Enter to scroll through the log view and
1595          * split open each commit diff. */
1596         scroll_view(view, REQ_SCROLL_LINE_DOWN);
1598         /* FIXME: A minor workaround. Scrolling the view will call report("")
1599          * but if we are scolling a non-current view this won't properly update
1600          * the view title. */
1601         if (split)
1602                 update_view_title(view);
1604         return TRUE;
1607 static struct view_ops pager_ops = {
1608         "line",
1609         pager_draw,
1610         pager_read,
1611         pager_enter,
1612 };
1615 /*
1616  * Main view backend
1617  */
1619 struct commit {
1620         char id[41];            /* SHA1 ID. */
1621         char title[75];         /* The first line of the commit message. */
1622         char author[75];        /* The author of the commit. */
1623         struct tm time;         /* Date from the author ident. */
1624         struct ref **refs;      /* Repository references; tags & branch heads. */
1625 };
1627 static bool
1628 main_draw(struct view *view, unsigned int lineno)
1630         char buf[DATE_COLS + 1];
1631         struct commit *commit;
1632         enum line_type type;
1633         int col = 0;
1634         size_t timelen;
1635         size_t authorlen;
1636         int trimmed;
1638         if (view->offset + lineno >= view->lines)
1639                 return FALSE;
1641         commit = view->line[view->offset + lineno];
1642         if (!*commit->author)
1643                 return FALSE;
1645         wmove(view->win, lineno, col);
1647         if (view->offset + lineno == view->lineno) {
1648                 string_copy(view->ref, commit->id);
1649                 string_copy(ref_commit, view->ref);
1650                 type = LINE_CURSOR;
1651                 wattrset(view->win, get_line_attr(type));
1652                 wchgat(view->win, -1, 0, type, NULL);
1654         } else {
1655                 type = LINE_MAIN_COMMIT;
1656                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
1657         }
1659         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
1660         waddnstr(view->win, buf, timelen);
1661         waddstr(view->win, " ");
1663         col += DATE_COLS;
1664         wmove(view->win, lineno, col);
1665         if (type != LINE_CURSOR)
1666                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
1668         /* FIXME: Make this optional, and add i18n.commitEncoding support. */
1669         authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1671         if (trimmed) {
1672                 waddnstr(view->win, commit->author, authorlen);
1673                 if (type != LINE_CURSOR)
1674                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
1675                 waddch(view->win, '~');
1676         } else {
1677                 waddstr(view->win, commit->author);
1678         }
1680         col += AUTHOR_COLS;
1681         if (type != LINE_CURSOR)
1682                 wattrset(view->win, A_NORMAL);
1684         mvwaddch(view->win, lineno, col, ACS_LTEE);
1685         wmove(view->win, lineno, col + 2);
1686         col += 2;
1688         if (commit->refs) {
1689                 size_t i = 0;
1691                 do {
1692                         if (type == LINE_CURSOR)
1693                                 ;
1694                         else if (commit->refs[i]->tag)
1695                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1696                         else
1697                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1698                         waddstr(view->win, "[");
1699                         waddstr(view->win, commit->refs[i]->name);
1700                         waddstr(view->win, "]");
1701                         if (type != LINE_CURSOR)
1702                                 wattrset(view->win, A_NORMAL);
1703                         waddstr(view->win, " ");
1704                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
1705                 } while (commit->refs[i++]->next);
1706         }
1708         if (type != LINE_CURSOR)
1709                 wattrset(view->win, get_line_attr(type));
1711         {
1712                 int titlelen = strlen(commit->title);
1714                 if (col + titlelen > view->width)
1715                         titlelen = view->width - col;
1717                 waddnstr(view->win, commit->title, titlelen);
1718         }
1720         return TRUE;
1723 /* Reads git log --pretty=raw output and parses it into the commit struct. */
1724 static bool
1725 main_read(struct view *view, char *line)
1727         enum line_type type = get_line_type(line);
1728         struct commit *commit;
1730         switch (type) {
1731         case LINE_COMMIT:
1732                 commit = calloc(1, sizeof(struct commit));
1733                 if (!commit)
1734                         return FALSE;
1736                 line += STRING_SIZE("commit ");
1738                 view->line[view->lines++] = commit;
1739                 string_copy(commit->id, line);
1740                 commit->refs = get_refs(commit->id);
1741                 break;
1743         case LINE_AUTHOR:
1744         {
1745                 char *ident = line + STRING_SIZE("author ");
1746                 char *end = strchr(ident, '<');
1748                 if (end) {
1749                         for (; end > ident && isspace(end[-1]); end--) ;
1750                         *end = 0;
1751                 }
1753                 commit = view->line[view->lines - 1];
1754                 string_copy(commit->author, ident);
1756                 /* Parse epoch and timezone */
1757                 if (end) {
1758                         char *secs = strchr(end + 1, '>');
1759                         char *zone;
1760                         time_t time;
1762                         if (!secs || secs[1] != ' ')
1763                                 break;
1765                         secs += 2;
1766                         time = (time_t) atol(secs);
1767                         zone = strchr(secs, ' ');
1768                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
1769                                 long tz;
1771                                 zone++;
1772                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
1773                                 tz += ('0' - zone[2]) * 60 * 60;
1774                                 tz += ('0' - zone[3]) * 60;
1775                                 tz += ('0' - zone[4]) * 60;
1777                                 if (zone[0] == '-')
1778                                         tz = -tz;
1780                                 time -= tz;
1781                         }
1782                         gmtime_r(&time, &commit->time);
1783                 }
1784                 break;
1785         }
1786         default:
1787                 /* We should only ever end up here if there has already been a
1788                  * commit line, however, be safe. */
1789                 if (view->lines == 0)
1790                         break;
1792                 /* Fill in the commit title if it has not already been set. */
1793                 commit = view->line[view->lines - 1];
1794                 if (commit->title[0])
1795                         break;
1797                 /* Require titles to start with a non-space character at the
1798                  * offset used by git log. */
1799                 /* FIXME: More gracefull handling of titles; append "..." to
1800                  * shortened titles, etc. */
1801                 if (strncmp(line, "    ", 4) ||
1802                     isspace(line[4]))
1803                         break;
1805                 string_copy(commit->title, line + 4);
1806         }
1808         return TRUE;
1811 static bool
1812 main_enter(struct view *view)
1814         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
1816         open_view(view, REQ_VIEW_DIFF, flags);
1817         return TRUE;
1820 static struct view_ops main_ops = {
1821         "commit",
1822         main_draw,
1823         main_read,
1824         main_enter,
1825 };
1828 /**
1829  * KEYS
1830  * ----
1831  * Below the default key bindings are shown.
1832  **/
1834 struct keymap {
1835         int alias;
1836         int request;
1837 };
1839 static struct keymap keymap[] = {
1840         /**
1841          * View switching
1842          * ~~~~~~~~~~~~~~
1843          * m::
1844          *      Switch to main view.
1845          * d::
1846          *      Switch to diff view.
1847          * l::
1848          *      Switch to log view.
1849          * p::
1850          *      Switch to pager view.
1851          * h::
1852          *      Show man page.
1853          **/
1854         { 'm',          REQ_VIEW_MAIN },
1855         { 'd',          REQ_VIEW_DIFF },
1856         { 'l',          REQ_VIEW_LOG },
1857         { 'p',          REQ_VIEW_PAGER },
1858         { 'h',          REQ_VIEW_HELP },
1860         /**
1861          * View manipulation
1862          * ~~~~~~~~~~~~~~~~~
1863          * q::
1864          *      Close view, if multiple views are open it will jump back to the
1865          *      previous view in the view stack. If it is the last open view it
1866          *      will quit. Use 'Q' to quit all views at once.
1867          * Enter::
1868          *      This key is "context sensitive" depending on what view you are
1869          *      currently in. When in log view on a commit line or in the main
1870          *      view, split the view and show the commit diff. In the diff view
1871          *      pressing Enter will simply scroll the view one line down.
1872          * Tab::
1873          *      Switch to next view.
1874          * Up::
1875          *      This key is "context sensitive" and will move the cursor one
1876          *      line up. However, uf you opened a diff view from the main view
1877          *      (split- or full-screen) it will change the cursor to point to
1878          *      the previous commit in the main view and update the diff view
1879          *      to display it.
1880          * Down::
1881          *      Similar to 'Up' but will move down.
1882          **/
1883         { 'q',          REQ_VIEW_CLOSE },
1884         { KEY_TAB,      REQ_VIEW_NEXT },
1885         { KEY_RETURN,   REQ_ENTER },
1886         { KEY_UP,       REQ_PREVIOUS },
1887         { KEY_DOWN,     REQ_NEXT },
1889         /**
1890          * Cursor navigation
1891          * ~~~~~~~~~~~~~~~~~
1892          * j::
1893          *      Move cursor one line up.
1894          * k::
1895          *      Move cursor one line down.
1896          * PgUp::
1897          * b::
1898          * -::
1899          *      Move cursor one page up.
1900          * PgDown::
1901          * Space::
1902          *      Move cursor one page down.
1903          * Home::
1904          *      Jump to first line.
1905          * End::
1906          *      Jump to last line.
1907          **/
1908         { 'k',          REQ_MOVE_UP },
1909         { 'j',          REQ_MOVE_DOWN },
1910         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1911         { KEY_END,      REQ_MOVE_LAST_LINE },
1912         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1913         { ' ',          REQ_MOVE_PAGE_DOWN },
1914         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1915         { 'b',          REQ_MOVE_PAGE_UP },
1916         { '-',          REQ_MOVE_PAGE_UP },
1918         /**
1919          * Scrolling
1920          * ~~~~~~~~~
1921          * Insert::
1922          *      Scroll view one line up.
1923          * Delete::
1924          *      Scroll view one line down.
1925          * w::
1926          *      Scroll view one page up.
1927          * s::
1928          *      Scroll view one page down.
1929          **/
1930         { KEY_IC,       REQ_SCROLL_LINE_UP },
1931         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1932         { 'w',          REQ_SCROLL_PAGE_UP },
1933         { 's',          REQ_SCROLL_PAGE_DOWN },
1935         /**
1936          * Misc
1937          * ~~~~
1938          * Q::
1939          *      Quit.
1940          * r::
1941          *      Redraw screen.
1942          * z::
1943          *      Stop all background loading. This can be useful if you use
1944          *      tig(1) in a repository with a long history without limiting
1945          *      the revision log.
1946          * v::
1947          *      Show version.
1948          * n::
1949          *      Toggle line numbers on/off.
1950          * ':'::
1951          *      Open prompt. This allows you to specify what git command
1952          *      to run. Example:
1953          *
1954          *      :log -p
1955          **/
1956         { 'Q',          REQ_QUIT },
1957         { 'z',          REQ_STOP_LOADING },
1958         { 'v',          REQ_SHOW_VERSION },
1959         { 'r',          REQ_SCREEN_REDRAW },
1960         { 'n',          REQ_TOGGLE_LINE_NUMBERS },
1961         { ':',          REQ_PROMPT },
1963         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
1964         { ERR,          REQ_SCREEN_UPDATE },
1966         /* Use the ncurses SIGWINCH handler. */
1967         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
1968 };
1970 static enum request
1971 get_request(int key)
1973         int i;
1975         for (i = 0; i < ARRAY_SIZE(keymap); i++)
1976                 if (keymap[i].alias == key)
1977                         return keymap[i].request;
1979         return (enum request) key;
1983 /*
1984  * Unicode / UTF-8 handling
1985  *
1986  * NOTE: Much of the following code for dealing with unicode is derived from
1987  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
1988  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
1989  */
1991 /* I've (over)annotated a lot of code snippets because I am not entirely
1992  * confident that the approach taken by this small UTF-8 interface is correct.
1993  * --jonas */
1995 static inline int
1996 unicode_width(unsigned long c)
1998         if (c >= 0x1100 &&
1999            (c <= 0x115f                         /* Hangul Jamo */
2000             || c == 0x2329
2001             || c == 0x232a
2002             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2003                                                 /* CJK ... Yi */
2004             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2005             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2006             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2007             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2008             || (c >= 0xffe0  && c <= 0xffe6)
2009             || (c >= 0x20000 && c <= 0x2fffd)
2010             || (c >= 0x30000 && c <= 0x3fffd)))
2011                 return 2;
2013         return 1;
2016 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2017  * Illegal bytes are set one. */
2018 static const unsigned char utf8_bytes[256] = {
2019         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,
2020         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,
2021         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,
2022         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,
2023         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,
2024         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,
2025         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,
2026         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,
2027 };
2029 /* Decode UTF-8 multi-byte representation into a unicode character. */
2030 static inline unsigned long
2031 utf8_to_unicode(const char *string, size_t length)
2033         unsigned long unicode;
2035         switch (length) {
2036         case 1:
2037                 unicode  =   string[0];
2038                 break;
2039         case 2:
2040                 unicode  =  (string[0] & 0x1f) << 6;
2041                 unicode +=  (string[1] & 0x3f);
2042                 break;
2043         case 3:
2044                 unicode  =  (string[0] & 0x0f) << 12;
2045                 unicode += ((string[1] & 0x3f) << 6);
2046                 unicode +=  (string[2] & 0x3f);
2047                 break;
2048         case 4:
2049                 unicode  =  (string[0] & 0x0f) << 18;
2050                 unicode += ((string[1] & 0x3f) << 12);
2051                 unicode += ((string[2] & 0x3f) << 6);
2052                 unicode +=  (string[3] & 0x3f);
2053                 break;
2054         case 5:
2055                 unicode  =  (string[0] & 0x0f) << 24;
2056                 unicode += ((string[1] & 0x3f) << 18);
2057                 unicode += ((string[2] & 0x3f) << 12);
2058                 unicode += ((string[3] & 0x3f) << 6);
2059                 unicode +=  (string[4] & 0x3f);
2060                 break;
2061         case 6:
2062                 unicode  =  (string[0] & 0x01) << 30;
2063                 unicode += ((string[1] & 0x3f) << 24);
2064                 unicode += ((string[2] & 0x3f) << 18);
2065                 unicode += ((string[3] & 0x3f) << 12);
2066                 unicode += ((string[4] & 0x3f) << 6);
2067                 unicode +=  (string[5] & 0x3f);
2068                 break;
2069         default:
2070                 die("Invalid unicode length");
2071         }
2073         /* Invalid characters could return the special 0xfffd value but NUL
2074          * should be just as good. */
2075         return unicode > 0xffff ? 0 : unicode;
2078 /* Calculates how much of string can be shown within the given maximum width
2079  * and sets trimmed parameter to non-zero value if all of string could not be
2080  * shown.
2081  *
2082  * Additionally, adds to coloffset how many many columns to move to align with
2083  * the expected position. Takes into account how multi-byte and double-width
2084  * characters will effect the cursor position.
2085  *
2086  * Returns the number of bytes to output from string to satisfy max_width. */
2087 static size_t
2088 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2090         const char *start = string;
2091         const char *end = strchr(string, '\0');
2092         size_t mbwidth = 0;
2093         size_t width = 0;
2095         *trimmed = 0;
2097         while (string < end) {
2098                 int c = *(unsigned char *) string;
2099                 unsigned char bytes = utf8_bytes[c];
2100                 size_t ucwidth;
2101                 unsigned long unicode;
2103                 if (string + bytes > end)
2104                         break;
2106                 /* Change representation to figure out whether
2107                  * it is a single- or double-width character. */
2109                 unicode = utf8_to_unicode(string, bytes);
2110                 /* FIXME: Graceful handling of invalid unicode character. */
2111                 if (!unicode)
2112                         break;
2114                 ucwidth = unicode_width(unicode);
2115                 width  += ucwidth;
2116                 if (width > max_width) {
2117                         *trimmed = 1;
2118                         break;
2119                 }
2121                 /* The column offset collects the differences between the
2122                  * number of bytes encoding a character and the number of
2123                  * columns will be used for rendering said character.
2124                  *
2125                  * So if some character A is encoded in 2 bytes, but will be
2126                  * represented on the screen using only 1 byte this will and up
2127                  * adding 1 to the multi-byte column offset.
2128                  *
2129                  * Assumes that no double-width character can be encoding in
2130                  * less than two bytes. */
2131                 if (bytes > ucwidth)
2132                         mbwidth += bytes - ucwidth;
2134                 string  += bytes;
2135         }
2137         *coloffset += mbwidth;
2139         return string - start;
2143 /*
2144  * Status management
2145  */
2147 /* Whether or not the curses interface has been initialized. */
2148 static bool cursed = FALSE;
2150 /* The status window is used for polling keystrokes. */
2151 static WINDOW *status_win;
2153 /* Update status and title window. */
2154 static void
2155 report(const char *msg, ...)
2157         static bool empty = TRUE;
2158         struct view *view = display[current_view];
2160         if (!empty || *msg) {
2161                 va_list args;
2163                 va_start(args, msg);
2165                 werase(status_win);
2166                 wmove(status_win, 0, 0);
2167                 if (*msg) {
2168                         vwprintw(status_win, msg, args);
2169                         empty = FALSE;
2170                 } else {
2171                         empty = TRUE;
2172                 }
2173                 wrefresh(status_win);
2175                 va_end(args);
2176         }
2178         update_view_title(view);
2180         /* Move the cursor to the right-most column of the cursor line.
2181          *
2182          * XXX: This could turn out to be a bit expensive, but it ensures that
2183          * the cursor does not jump around. */
2184         if (view->lines) {
2185                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2186                 wrefresh(view->win);
2187         }
2190 /* Controls when nodelay should be in effect when polling user input. */
2191 static void
2192 set_nonblocking_input(bool loading)
2194         static unsigned int loading_views;
2196         if ((loading == FALSE && loading_views-- == 1) ||
2197             (loading == TRUE  && loading_views++ == 0))
2198                 nodelay(status_win, loading);
2201 static void
2202 init_display(void)
2204         int x, y;
2206         /* Initialize the curses library */
2207         if (isatty(STDIN_FILENO)) {
2208                 cursed = !!initscr();
2209         } else {
2210                 /* Leave stdin and stdout alone when acting as a pager. */
2211                 FILE *io = fopen("/dev/tty", "r+");
2213                 cursed = !!newterm(NULL, io, io);
2214         }
2216         if (!cursed)
2217                 die("Failed to initialize curses");
2219         nonl();         /* Tell curses not to do NL->CR/NL on output */
2220         cbreak();       /* Take input chars one at a time, no wait for \n */
2221         noecho();       /* Don't echo input */
2222         leaveok(stdscr, TRUE);
2224         if (has_colors())
2225                 init_colors();
2227         getmaxyx(stdscr, y, x);
2228         status_win = newwin(1, 0, y - 1, 0);
2229         if (!status_win)
2230                 die("Failed to create status window");
2232         /* Enable keyboard mapping */
2233         keypad(status_win, TRUE);
2234         wbkgdset(status_win, get_line_attr(LINE_STATUS));
2238 /*
2239  * Repository references
2240  */
2242 static struct ref *refs;
2243 static size_t refs_size;
2245 /* Id <-> ref store */
2246 static struct ref ***id_refs;
2247 static size_t id_refs_size;
2249 static struct ref **
2250 get_refs(char *id)
2252         struct ref ***tmp_id_refs;
2253         struct ref **ref_list = NULL;
2254         size_t ref_list_size = 0;
2255         size_t i;
2257         for (i = 0; i < id_refs_size; i++)
2258                 if (!strcmp(id, id_refs[i][0]->id))
2259                         return id_refs[i];
2261         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2262         if (!tmp_id_refs)
2263                 return NULL;
2265         id_refs = tmp_id_refs;
2267         for (i = 0; i < refs_size; i++) {
2268                 struct ref **tmp;
2270                 if (strcmp(id, refs[i].id))
2271                         continue;
2273                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2274                 if (!tmp) {
2275                         if (ref_list)
2276                                 free(ref_list);
2277                         return NULL;
2278                 }
2280                 ref_list = tmp;
2281                 if (ref_list_size > 0)
2282                         ref_list[ref_list_size - 1]->next = 1;
2283                 ref_list[ref_list_size] = &refs[i];
2285                 /* XXX: The properties of the commit chains ensures that we can
2286                  * safely modify the shared ref. The repo references will
2287                  * always be similar for the same id. */
2288                 ref_list[ref_list_size]->next = 0;
2289                 ref_list_size++;
2290         }
2292         if (ref_list)
2293                 id_refs[id_refs_size++] = ref_list;
2295         return ref_list;
2298 static int
2299 load_refs(void)
2301         const char *cmd_env = getenv("TIG_LS_REMOTE");
2302         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2303         FILE *pipe = popen(cmd, "r");
2304         char buffer[BUFSIZ];
2305         char *line;
2307         if (!pipe)
2308                 return ERR;
2310         while ((line = fgets(buffer, sizeof(buffer), pipe))) {
2311                 char *name = strchr(line, '\t');
2312                 struct ref *ref;
2313                 int namelen;
2314                 bool tag = FALSE;
2315                 bool tag_commit = FALSE;
2317                 if (!name)
2318                         continue;
2320                 *name++ = 0;
2321                 namelen = strlen(name) - 1;
2323                 /* Commits referenced by tags has "^{}" appended. */
2324                 if (name[namelen - 1] == '}') {
2325                         while (namelen > 0 && name[namelen] != '^')
2326                                 namelen--;
2327                         if (namelen > 0)
2328                                 tag_commit = TRUE;
2329                 }
2330                 name[namelen] = 0;
2332                 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2333                         if (!tag_commit)
2334                                 continue;
2335                         name += STRING_SIZE("refs/tags/");
2336                         tag = TRUE;
2338                 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2339                         name += STRING_SIZE("refs/heads/");
2341                 } else if (!strcmp(name, "HEAD")) {
2342                         continue;
2343                 }
2345                 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2346                 if (!refs)
2347                         return ERR;
2349                 ref = &refs[refs_size++];
2350                 ref->tag = tag;
2351                 ref->name = strdup(name);
2352                 if (!ref->name)
2353                         return ERR;
2355                 string_copy(ref->id, line);
2356         }
2358         if (ferror(pipe))
2359                 return ERR;
2361         pclose(pipe);
2363         return OK;
2366 /*
2367  * Main
2368  */
2370 #if __GNUC__ >= 3
2371 #define __NORETURN __attribute__((__noreturn__))
2372 #else
2373 #define __NORETURN
2374 #endif
2376 static void __NORETURN
2377 quit(int sig)
2379         /* XXX: Restore tty modes and let the OS cleanup the rest! */
2380         if (cursed)
2381                 endwin();
2382         exit(0);
2385 static void __NORETURN
2386 die(const char *err, ...)
2388         va_list args;
2390         endwin();
2392         va_start(args, err);
2393         fputs("tig: ", stderr);
2394         vfprintf(stderr, err, args);
2395         fputs("\n", stderr);
2396         va_end(args);
2398         exit(1);
2401 int
2402 main(int argc, char *argv[])
2404         struct view *view;
2405         enum request request;
2406         size_t i;
2408         signal(SIGINT, quit);
2410         if (!parse_options(argc, argv))
2411                 return 0;
2413         if (load_refs() == ERR)
2414                 die("Failed to load refs.");
2416         /* Require a git repository unless when running in pager mode. */
2417         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2418                 die("Not a git repository");
2420         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2421                 view->cmd_env = getenv(view->cmd_env);
2423         request = opt_request;
2425         init_display();
2427         while (view_driver(display[current_view], request)) {
2428                 int key;
2429                 int i;
2431                 foreach_view (view, i)
2432                         update_view(view);
2434                 /* Refresh, accept single keystroke of input */
2435                 key = wgetch(status_win);
2436                 request = get_request(key);
2438                 /* Some low-level request handling. This keeps access to
2439                  * status_win restricted. */
2440                 switch (request) {
2441                 case REQ_PROMPT:
2442                         report(":");
2443                         /* Temporarily switch to line-oriented and echoed
2444                          * input. */
2445                         nocbreak();
2446                         echo();
2448                         if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2449                                 memcpy(opt_cmd, "git ", 4);
2450                                 opt_request = REQ_VIEW_PAGER;
2451                         } else {
2452                                 request = ERR;
2453                         }
2455                         noecho();
2456                         cbreak();
2457                         break;
2459                 case REQ_SCREEN_RESIZE:
2460                 {
2461                         int height, width;
2463                         getmaxyx(stdscr, height, width);
2465                         /* Resize the status view and let the view driver take
2466                          * care of resizing the displayed views. */
2467                         wresize(status_win, 1, width);
2468                         mvwin(status_win, height - 1, 0);
2469                         wrefresh(status_win);
2470                         break;
2471                 }
2472                 default:
2473                         break;
2474                 }
2475         }
2477         quit(0);
2479         return 0;
2482 /**
2483  * [[refspec]]
2484  * Revision specification
2485  * ----------------------
2486  * This section describes various ways to specify what revisions to display
2487  * or otherwise limit the view to. tig(1) does not itself parse the described
2488  * revision options so refer to the relevant git man pages for futher
2489  * information. Relevant man pages besides git-log(1) are git-diff(1) and
2490  * git-rev-list(1).
2491  *
2492  * You can tune the interaction with git by making use of the options
2493  * explained in this section. For example, by configuring the environment
2494  * variables described in the  <<view-commands, "View commands">> section.
2495  *
2496  * Limit by path name
2497  * ~~~~~~~~~~~~~~~~~~
2498  * If you are interested only in those revisions that made changes to a
2499  * specific file (or even several files) list the files like this:
2500  *
2501  *      $ tig log Makefile README
2502  *
2503  * To avoid ambiguity with repository references such as tag name, be sure
2504  * to separate file names from other git options using "\--". So if you
2505  * have a file named 'master' it will clash with the reference named
2506  * 'master', and thus you will have to use:
2507  *
2508  *      $ tig log -- master
2509  *
2510  * NOTE: For the main view, avoiding ambiguity will in some cases require
2511  * you to specify two "\--" options. The first will make tig(1) stop
2512  * option processing and the latter will be passed to git log.
2513  *
2514  * Limit by date or number
2515  * ~~~~~~~~~~~~~~~~~~~~~~~
2516  * To speed up interaction with git, you can limit the amount of commits
2517  * to show both for the log and main view. Either limit by date using
2518  * e.g. `--since=1.month` or limit by the number of commits using `-n400`.
2519  *
2520  * If you are only interested in changed that happened between two dates
2521  * you can use:
2522  *
2523  *      $ tig -- --after="May 5th" --before="2006-05-16 15:44"
2524  *
2525  * NOTE: If you want to avoid having to quote dates containing spaces you
2526  * can use "." instead, e.g. `--after=May.5th`.
2527  *
2528  * Limiting by commit ranges
2529  * ~~~~~~~~~~~~~~~~~~~~~~~~~
2530  * Alternatively, commits can be limited to a specific range, such as
2531  * "all commits between 'tag-1.0' and 'tag-2.0'". For example:
2532  *
2533  *      $ tig log tag-1.0..tag-2.0
2534  *
2535  * This way of commit limiting makes it trivial to only browse the commits
2536  * which haven't been pushed to a remote branch. Assuming 'origin' is your
2537  * upstream remote branch, using:
2538  *
2539  *      $ tig log origin..HEAD
2540  *
2541  * will list what will be pushed to the remote branch. Optionally, the ending
2542  * 'HEAD' can be left out since it is implied.
2543  *
2544  * Limiting by reachability
2545  * ~~~~~~~~~~~~~~~~~~~~~~~~
2546  * Git interprets the range specifier "tag-1.0..tag-2.0" as
2547  * "all commits reachable from 'tag-2.0' but not from 'tag-1.0'".
2548  * Where reachability refers to what commits are ancestors (or part of the
2549  * history) of the branch or tagged revision in question.
2550  *
2551  * If you prefer to specify which commit to preview in this way use the
2552  * following:
2553  *
2554  *      $ tig log tag-2.0 ^tag-1.0
2555  *
2556  * You can think of '^' as a negation operator. Using this alternate syntax,
2557  * it is possible to further prune commits by specifying multiple branch
2558  * cut offs.
2559  *
2560  * Combining revisions specification
2561  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2562  * Revisions options can to some degree be combined, which makes it possible
2563  * to say "show at most 20 commits from within the last month that changed
2564  * files under the Documentation/ directory."
2565  *
2566  *      $ tig -- --since=1.month -n20 -- Documentation/
2567  *
2568  * Examining all repository references
2569  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2570  * In some cases, it can be useful to query changes across all references
2571  * in a repository. An example is to ask "did any line of development in
2572  * this repository change a particular file within the last week". This
2573  * can be accomplished using:
2574  *
2575  *      $ tig -- --all --since=1.week -- Makefile
2576  *
2577  * BUGS
2578  * ----
2579  * Known bugs and problems:
2580  *
2581  * - In it's current state tig is pretty much UTF-8 only.
2582  *
2583  * - If the screen width is very small the main view can draw
2584  *   outside the current view causing bad wrapping. Same goes
2585  *   for title and status windows.
2586  *
2587  * - The cursor can wrap-around on the last line and cause the
2588  *   window to scroll.
2589  *
2590  * TODO
2591  * ----
2592  * Features that should be explored.
2593  *
2594  * - Searching.
2595  *
2596  * - Locale support.
2597  *
2598  * COPYRIGHT
2599  * ---------
2600  * Copyright (c) Jonas Fonseca <fonseca@diku.dk>, 2006
2601  *
2602  * This program is free software; you can redistribute it and/or modify
2603  * it under the terms of the GNU General Public License as published by
2604  * the Free Software Foundation; either version 2 of the License, or
2605  * (at your option) any later version.
2606  *
2607  * SEE ALSO
2608  * --------
2609  * [verse]
2610  * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
2611  * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
2612  * gitk(1): git repository browser written using tcl/tk,
2613  * qgit(1): git repository browser written using c++/Qt,
2614  * gitview(1): git repository browser written using python/gtk.
2615  **/