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