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