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