Refactor view->line reallocation
[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 long line_size;/* Total number of allocated lines */
866         unsigned int digits;    /* Number of digits in the lines member. */
867
868         /* Loading */
869         FILE *pipe;
870         time_t start_time;
871 };
872
873 struct view_ops {
874         /* What type of content being displayed. Used in the title bar. */
875         const char *type;
876         /* Draw one line; @lineno must be < view->height. */
877         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
878         /* Read one line; updates view->line. */
879         bool (*read)(struct view *view, struct line *prev, char *data);
880         /* Depending on view, change display based on current line. */
881         bool (*enter)(struct view *view, struct line *line);
882 };
883
884 static struct view_ops pager_ops;
885 static struct view_ops main_ops;
886
887 #define VIEW_STR(name, cmd, env, ref, ops) \
888         { name, cmd, #env, ref, ops }
889
890 #define VIEW_(id, name, ops, ref) \
891         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops)
892
893
894 static struct view views[] = {
895         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
896         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
897         VIEW_(LOG,   "log",   &pager_ops, ref_head),
898         VIEW_(HELP,  "help",  &pager_ops, "static"),
899         VIEW_(PAGER, "pager", &pager_ops, "static"),
900 };
901
902 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
903
904
905 static bool
906 draw_view_line(struct view *view, unsigned int lineno)
907 {
908         if (view->offset + lineno >= view->lines)
909                 return FALSE;
910
911         return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
912 }
913
914 static void
915 redraw_view_from(struct view *view, int lineno)
916 {
917         assert(0 <= lineno && lineno < view->height);
918
919         for (; lineno < view->height; lineno++) {
920                 if (!draw_view_line(view, lineno))
921                         break;
922         }
923
924         redrawwin(view->win);
925         wrefresh(view->win);
926 }
927
928 static void
929 redraw_view(struct view *view)
930 {
931         wclear(view->win);
932         redraw_view_from(view, 0);
933 }
934
935
936 static void
937 update_view_title(struct view *view)
938 {
939         if (view == display[current_view])
940                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
941         else
942                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
943
944         werase(view->title);
945         wmove(view->title, 0, 0);
946
947         if (*view->ref)
948                 wprintw(view->title, "[%s] %s", view->name, view->ref);
949         else
950                 wprintw(view->title, "[%s]", view->name);
951
952         if (view->lines || view->pipe) {
953                 unsigned int lines = view->lines
954                                    ? (view->lineno + 1) * 100 / view->lines
955                                    : 0;
956
957                 wprintw(view->title, " - %s %d of %d (%d%%)",
958                         view->ops->type,
959                         view->lineno + 1,
960                         view->lines,
961                         lines);
962         }
963
964         if (view->pipe) {
965                 time_t secs = time(NULL) - view->start_time;
966
967                 /* Three git seconds are a long time ... */
968                 if (secs > 2)
969                         wprintw(view->title, " %lds", secs);
970         }
971
972         wmove(view->title, 0, view->width - 1);
973         wrefresh(view->title);
974 }
975
976 static void
977 resize_display(void)
978 {
979         int offset, i;
980         struct view *base = display[0];
981         struct view *view = display[1] ? display[1] : display[0];
982
983         /* Setup window dimensions */
984
985         getmaxyx(stdscr, base->height, base->width);
986
987         /* Make room for the status window. */
988         base->height -= 1;
989
990         if (view != base) {
991                 /* Horizontal split. */
992                 view->width   = base->width;
993                 view->height  = SCALE_SPLIT_VIEW(base->height);
994                 base->height -= view->height;
995
996                 /* Make room for the title bar. */
997                 view->height -= 1;
998         }
999
1000         /* Make room for the title bar. */
1001         base->height -= 1;
1002
1003         offset = 0;
1004
1005         foreach_view (view, i) {
1006                 if (!view->win) {
1007                         view->win = newwin(view->height, 0, offset, 0);
1008                         if (!view->win)
1009                                 die("Failed to create %s view", view->name);
1010
1011                         scrollok(view->win, TRUE);
1012
1013                         view->title = newwin(1, 0, offset + view->height, 0);
1014                         if (!view->title)
1015                                 die("Failed to create title window");
1016
1017                 } else {
1018                         wresize(view->win, view->height, view->width);
1019                         mvwin(view->win,   offset, 0);
1020                         mvwin(view->title, offset + view->height, 0);
1021                 }
1022
1023                 offset += view->height + 1;
1024         }
1025 }
1026
1027 static void
1028 redraw_display(void)
1029 {
1030         struct view *view;
1031         int i;
1032
1033         foreach_view (view, i) {
1034                 redraw_view(view);
1035                 update_view_title(view);
1036         }
1037 }
1038
1039 static void
1040 update_display_cursor(void)
1041 {
1042         struct view *view = display[current_view];
1043
1044         /* Move the cursor to the right-most column of the cursor line.
1045          *
1046          * XXX: This could turn out to be a bit expensive, but it ensures that
1047          * the cursor does not jump around. */
1048         if (view->lines) {
1049                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1050                 wrefresh(view->win);
1051         }
1052 }
1053
1054 /*
1055  * Navigation
1056  */
1057
1058 /* Scrolling backend */
1059 static void
1060 do_scroll_view(struct view *view, int lines, bool redraw)
1061 {
1062         /* The rendering expects the new offset. */
1063         view->offset += lines;
1064
1065         assert(0 <= view->offset && view->offset < view->lines);
1066         assert(lines);
1067
1068         /* Redraw the whole screen if scrolling is pointless. */
1069         if (view->height < ABS(lines)) {
1070                 redraw_view(view);
1071
1072         } else {
1073                 int line = lines > 0 ? view->height - lines : 0;
1074                 int end = line + ABS(lines);
1075
1076                 wscrl(view->win, lines);
1077
1078                 for (; line < end; line++) {
1079                         if (!draw_view_line(view, line))
1080                                 break;
1081                 }
1082         }
1083
1084         /* Move current line into the view. */
1085         if (view->lineno < view->offset) {
1086                 view->lineno = view->offset;
1087                 draw_view_line(view, 0);
1088
1089         } else if (view->lineno >= view->offset + view->height) {
1090                 if (view->lineno == view->offset + view->height) {
1091                         /* Clear the hidden line so it doesn't show if the view
1092                          * is scrolled up. */
1093                         wmove(view->win, view->height, 0);
1094                         wclrtoeol(view->win);
1095                 }
1096                 view->lineno = view->offset + view->height - 1;
1097                 draw_view_line(view, view->lineno - view->offset);
1098         }
1099
1100         assert(view->offset <= view->lineno && view->lineno < view->lines);
1101
1102         if (!redraw)
1103                 return;
1104
1105         redrawwin(view->win);
1106         wrefresh(view->win);
1107         report("");
1108 }
1109
1110 /* Scroll frontend */
1111 static void
1112 scroll_view(struct view *view, enum request request)
1113 {
1114         int lines = 1;
1115
1116         switch (request) {
1117         case REQ_SCROLL_PAGE_DOWN:
1118                 lines = view->height;
1119         case REQ_SCROLL_LINE_DOWN:
1120                 if (view->offset + lines > view->lines)
1121                         lines = view->lines - view->offset;
1122
1123                 if (lines == 0 || view->offset + view->height >= view->lines) {
1124                         report("Cannot scroll beyond the last line");
1125                         return;
1126                 }
1127                 break;
1128
1129         case REQ_SCROLL_PAGE_UP:
1130                 lines = view->height;
1131         case REQ_SCROLL_LINE_UP:
1132                 if (lines > view->offset)
1133                         lines = view->offset;
1134
1135                 if (lines == 0) {
1136                         report("Cannot scroll beyond the first line");
1137                         return;
1138                 }
1139
1140                 lines = -lines;
1141                 break;
1142
1143         default:
1144                 die("request %d not handled in switch", request);
1145         }
1146
1147         do_scroll_view(view, lines, TRUE);
1148 }
1149
1150 /* Cursor moving */
1151 static void
1152 move_view(struct view *view, enum request request, bool redraw)
1153 {
1154         int steps;
1155
1156         switch (request) {
1157         case REQ_MOVE_FIRST_LINE:
1158                 steps = -view->lineno;
1159                 break;
1160
1161         case REQ_MOVE_LAST_LINE:
1162                 steps = view->lines - view->lineno - 1;
1163                 break;
1164
1165         case REQ_MOVE_PAGE_UP:
1166                 steps = view->height > view->lineno
1167                       ? -view->lineno : -view->height;
1168                 break;
1169
1170         case REQ_MOVE_PAGE_DOWN:
1171                 steps = view->lineno + view->height >= view->lines
1172                       ? view->lines - view->lineno - 1 : view->height;
1173                 break;
1174
1175         case REQ_MOVE_UP:
1176                 steps = -1;
1177                 break;
1178
1179         case REQ_MOVE_DOWN:
1180                 steps = 1;
1181                 break;
1182
1183         default:
1184                 die("request %d not handled in switch", request);
1185         }
1186
1187         if (steps <= 0 && view->lineno == 0) {
1188                 report("Cannot move beyond the first line");
1189                 return;
1190
1191         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1192                 report("Cannot move beyond the last line");
1193                 return;
1194         }
1195
1196         /* Move the current line */
1197         view->lineno += steps;
1198         assert(0 <= view->lineno && view->lineno < view->lines);
1199
1200         /* Repaint the old "current" line if we be scrolling */
1201         if (ABS(steps) < view->height) {
1202                 int prev_lineno = view->lineno - steps - view->offset;
1203
1204                 wmove(view->win, prev_lineno, 0);
1205                 wclrtoeol(view->win);
1206                 draw_view_line(view,  prev_lineno);
1207         }
1208
1209         /* Check whether the view needs to be scrolled */
1210         if (view->lineno < view->offset ||
1211             view->lineno >= view->offset + view->height) {
1212                 if (steps < 0 && -steps > view->offset) {
1213                         steps = -view->offset;
1214
1215                 } else if (steps > 0) {
1216                         if (view->lineno == view->lines - 1 &&
1217                             view->lines > view->height) {
1218                                 steps = view->lines - view->offset - 1;
1219                                 if (steps >= view->height)
1220                                         steps -= view->height - 1;
1221                         }
1222                 }
1223
1224                 do_scroll_view(view, steps, redraw);
1225                 return;
1226         }
1227
1228         /* Draw the current line */
1229         draw_view_line(view, view->lineno - view->offset);
1230
1231         if (!redraw)
1232                 return;
1233
1234         redrawwin(view->win);
1235         wrefresh(view->win);
1236         report("");
1237 }
1238
1239
1240 /*
1241  * Incremental updating
1242  */
1243
1244 static void
1245 end_update(struct view *view)
1246 {
1247         if (!view->pipe)
1248                 return;
1249         set_nonblocking_input(FALSE);
1250         if (view->pipe == stdin)
1251                 fclose(view->pipe);
1252         else
1253                 pclose(view->pipe);
1254         view->pipe = NULL;
1255 }
1256
1257 static bool
1258 begin_update(struct view *view)
1259 {
1260         const char *id = view->id;
1261
1262         if (view->pipe)
1263                 end_update(view);
1264
1265         if (opt_cmd[0]) {
1266                 string_copy(view->cmd, opt_cmd);
1267                 opt_cmd[0] = 0;
1268                 /* When running random commands, the view ref could have become
1269                  * invalid so clear it. */
1270                 view->ref[0] = 0;
1271         } else {
1272                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1273
1274                 if (snprintf(view->cmd, sizeof(view->cmd), format,
1275                              id, id, id, id, id) >= sizeof(view->cmd))
1276                         return FALSE;
1277         }
1278
1279         /* Special case for the pager view. */
1280         if (opt_pipe) {
1281                 view->pipe = opt_pipe;
1282                 opt_pipe = NULL;
1283         } else {
1284                 view->pipe = popen(view->cmd, "r");
1285         }
1286
1287         if (!view->pipe)
1288                 return FALSE;
1289
1290         set_nonblocking_input(TRUE);
1291
1292         view->offset = 0;
1293         view->lines  = 0;
1294         view->lineno = 0;
1295         string_copy(view->vid, id);
1296
1297         if (view->line) {
1298                 int i;
1299
1300                 for (i = 0; i < view->lines; i++)
1301                         if (view->line[i].data)
1302                                 free(view->line[i].data);
1303
1304                 free(view->line);
1305                 view->line = NULL;
1306         }
1307
1308         view->start_time = time(NULL);
1309
1310         return TRUE;
1311 }
1312
1313 static struct line *
1314 realloc_lines(struct view *view, size_t line_size)
1315 {
1316         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1317
1318         if (!tmp)
1319                 return NULL;
1320
1321         view->line = tmp;
1322         view->line_size = line_size;
1323         return view->line;
1324 }
1325
1326 static bool
1327 update_view(struct view *view)
1328 {
1329         char buffer[BUFSIZ];
1330         char *line;
1331         /* The number of lines to read. If too low it will cause too much
1332          * redrawing (and possible flickering), if too high responsiveness
1333          * will suffer. */
1334         unsigned long lines = view->height;
1335         int redraw_from = -1;
1336
1337         if (!view->pipe)
1338                 return TRUE;
1339
1340         /* Only redraw if lines are visible. */
1341         if (view->offset + view->height >= view->lines)
1342                 redraw_from = view->lines - view->offset;
1343
1344         if (!realloc_lines(view, view->lines + lines))
1345                 goto alloc_error;
1346
1347         while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1348                 int linelen = strlen(line);
1349
1350                 struct line *prev = view->lines
1351                                   ? &view->line[view->lines - 1]
1352                                   : NULL;
1353
1354                 if (linelen)
1355                         line[linelen - 1] = 0;
1356
1357                 if (!view->ops->read(view, prev, line))
1358                         goto alloc_error;
1359
1360                 if (lines-- == 1)
1361                         break;
1362         }
1363
1364         {
1365                 int digits;
1366
1367                 lines = view->lines;
1368                 for (digits = 0; lines; digits++)
1369                         lines /= 10;
1370
1371                 /* Keep the displayed view in sync with line number scaling. */
1372                 if (digits != view->digits) {
1373                         view->digits = digits;
1374                         redraw_from = 0;
1375                 }
1376         }
1377
1378         if (redraw_from >= 0) {
1379                 /* If this is an incremental update, redraw the previous line
1380                  * since for commits some members could have changed when
1381                  * loading the main view. */
1382                 if (redraw_from > 0)
1383                         redraw_from--;
1384
1385                 /* Incrementally draw avoids flickering. */
1386                 redraw_view_from(view, redraw_from);
1387         }
1388
1389         /* Update the title _after_ the redraw so that if the redraw picks up a
1390          * commit reference in view->ref it'll be available here. */
1391         update_view_title(view);
1392
1393         if (ferror(view->pipe)) {
1394                 report("Failed to read: %s", strerror(errno));
1395                 goto end;
1396
1397         } else if (feof(view->pipe)) {
1398                 report("");
1399                 goto end;
1400         }
1401
1402         return TRUE;
1403
1404 alloc_error:
1405         report("Allocation failure");
1406
1407 end:
1408         end_update(view);
1409         return FALSE;
1410 }
1411
1412 enum open_flags {
1413         OPEN_DEFAULT = 0,       /* Use default view switching. */
1414         OPEN_SPLIT = 1,         /* Split current view. */
1415         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1416         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1417 };
1418
1419 static void
1420 open_view(struct view *prev, enum request request, enum open_flags flags)
1421 {
1422         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1423         bool split = !!(flags & OPEN_SPLIT);
1424         bool reload = !!(flags & OPEN_RELOAD);
1425         struct view *view = VIEW(request);
1426         int nviews = displayed_views();
1427         struct view *base_view = display[0];
1428
1429         if (view == prev && nviews == 1 && !reload) {
1430                 report("Already in %s view", view->name);
1431                 return;
1432         }
1433
1434         if ((reload || strcmp(view->vid, view->id)) &&
1435             !begin_update(view)) {
1436                 report("Failed to load %s view", view->name);
1437                 return;
1438         }
1439
1440         if (split) {
1441                 display[1] = view;
1442                 if (!backgrounded)
1443                         current_view = 1;
1444         } else {
1445                 /* Maximize the current view. */
1446                 memset(display, 0, sizeof(display));
1447                 current_view = 0;
1448                 display[current_view] = view;
1449         }
1450
1451         /* Resize the view when switching between split- and full-screen,
1452          * or when switching between two different full-screen views. */
1453         if (nviews != displayed_views() ||
1454             (nviews == 1 && base_view != display[0]))
1455                 resize_display();
1456
1457         if (split && prev->lineno - prev->offset >= prev->height) {
1458                 /* Take the title line into account. */
1459                 int lines = prev->lineno - prev->offset - prev->height + 1;
1460
1461                 /* Scroll the view that was split if the current line is
1462                  * outside the new limited view. */
1463                 do_scroll_view(prev, lines, TRUE);
1464         }
1465
1466         if (prev && view != prev) {
1467                 if (split && !backgrounded) {
1468                         /* "Blur" the previous view. */
1469                         update_view_title(prev);
1470                 }
1471
1472                 view->parent = prev;
1473         }
1474
1475         if (view == VIEW(REQ_VIEW_HELP))
1476                 load_help_page();
1477
1478         if (view->pipe && view->lines == 0) {
1479                 /* Clear the old view and let the incremental updating refill
1480                  * the screen. */
1481                 wclear(view->win);
1482                 report("");
1483         } else {
1484                 redraw_view(view);
1485                 report("");
1486         }
1487
1488         /* If the view is backgrounded the above calls to report()
1489          * won't redraw the view title. */
1490         if (backgrounded)
1491                 update_view_title(view);
1492 }
1493
1494
1495 /*
1496  * User request switch noodle
1497  */
1498
1499 static int
1500 view_driver(struct view *view, enum request request)
1501 {
1502         int i;
1503
1504         switch (request) {
1505         case REQ_MOVE_UP:
1506         case REQ_MOVE_DOWN:
1507         case REQ_MOVE_PAGE_UP:
1508         case REQ_MOVE_PAGE_DOWN:
1509         case REQ_MOVE_FIRST_LINE:
1510         case REQ_MOVE_LAST_LINE:
1511                 move_view(view, request, TRUE);
1512                 break;
1513
1514         case REQ_SCROLL_LINE_DOWN:
1515         case REQ_SCROLL_LINE_UP:
1516         case REQ_SCROLL_PAGE_DOWN:
1517         case REQ_SCROLL_PAGE_UP:
1518                 scroll_view(view, request);
1519                 break;
1520
1521         case REQ_VIEW_MAIN:
1522         case REQ_VIEW_DIFF:
1523         case REQ_VIEW_LOG:
1524         case REQ_VIEW_HELP:
1525         case REQ_VIEW_PAGER:
1526                 open_view(view, request, OPEN_DEFAULT);
1527                 break;
1528
1529         case REQ_NEXT:
1530         case REQ_PREVIOUS:
1531                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1532
1533                 if (view == VIEW(REQ_VIEW_DIFF) &&
1534                     view->parent == VIEW(REQ_VIEW_MAIN)) {
1535                         bool redraw = display[1] == view;
1536
1537                         view = view->parent;
1538                         move_view(view, request, redraw);
1539                         if (redraw)
1540                                 update_view_title(view);
1541                 } else {
1542                         move_view(view, request, TRUE);
1543                         break;
1544                 }
1545                 /* Fall-through */
1546
1547         case REQ_ENTER:
1548                 if (!view->lines) {
1549                         report("Nothing to enter");
1550                         break;
1551                 }
1552                 return view->ops->enter(view, &view->line[view->lineno]);
1553
1554         case REQ_VIEW_NEXT:
1555         {
1556                 int nviews = displayed_views();
1557                 int next_view = (current_view + 1) % nviews;
1558
1559                 if (next_view == current_view) {
1560                         report("Only one view is displayed");
1561                         break;
1562                 }
1563
1564                 current_view = next_view;
1565                 /* Blur out the title of the previous view. */
1566                 update_view_title(view);
1567                 report("");
1568                 break;
1569         }
1570         case REQ_TOGGLE_LINENO:
1571                 opt_line_number = !opt_line_number;
1572                 redraw_display();
1573                 break;
1574
1575         case REQ_PROMPT:
1576                 /* Always reload^Wrerun commands from the prompt. */
1577                 open_view(view, opt_request, OPEN_RELOAD);
1578                 break;
1579
1580         case REQ_STOP_LOADING:
1581                 for (i = 0; i < ARRAY_SIZE(views); i++) {
1582                         view = &views[i];
1583                         if (view->pipe)
1584                                 report("Stopped loading the %s view", view->name),
1585                         end_update(view);
1586                 }
1587                 break;
1588
1589         case REQ_SHOW_VERSION:
1590                 report("%s (built %s)", VERSION, __DATE__);
1591                 return TRUE;
1592
1593         case REQ_SCREEN_RESIZE:
1594                 resize_display();
1595                 /* Fall-through */
1596         case REQ_SCREEN_REDRAW:
1597                 redraw_display();
1598                 break;
1599
1600         case REQ_SCREEN_UPDATE:
1601                 doupdate();
1602                 return TRUE;
1603
1604         case REQ_VIEW_CLOSE:
1605                 /* XXX: Mark closed views by letting view->parent point to the
1606                  * view itself. Parents to closed view should never be
1607                  * followed. */
1608                 if (view->parent &&
1609                     view->parent->parent != view->parent) {
1610                         memset(display, 0, sizeof(display));
1611                         current_view = 0;
1612                         display[current_view] = view->parent;
1613                         view->parent = view;
1614                         resize_display();
1615                         redraw_display();
1616                         break;
1617                 }
1618                 /* Fall-through */
1619         case REQ_QUIT:
1620                 return FALSE;
1621
1622         default:
1623                 /* An unknown key will show most commonly used commands. */
1624                 report("Unknown key, press 'h' for help");
1625                 return TRUE;
1626         }
1627
1628         return TRUE;
1629 }
1630
1631
1632 /*
1633  * Pager backend
1634  */
1635
1636 static bool
1637 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1638 {
1639         char *text = line->data;
1640         enum line_type type = line->type;
1641         int textlen = strlen(text);
1642         int attr;
1643
1644         wmove(view->win, lineno, 0);
1645
1646         if (view->offset + lineno == view->lineno) {
1647                 if (type == LINE_COMMIT) {
1648                         string_copy(view->ref, text + 7);
1649                         string_copy(ref_commit, view->ref);
1650                 }
1651
1652                 type = LINE_CURSOR;
1653                 wchgat(view->win, -1, 0, type, NULL);
1654         }
1655
1656         attr = get_line_attr(type);
1657         wattrset(view->win, attr);
1658
1659         if (opt_line_number || opt_tab_size < TABSIZE) {
1660                 static char spaces[] = "                    ";
1661                 int col_offset = 0, col = 0;
1662
1663                 if (opt_line_number) {
1664                         unsigned long real_lineno = view->offset + lineno + 1;
1665
1666                         if (real_lineno == 1 ||
1667                             (real_lineno % opt_num_interval) == 0) {
1668                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
1669
1670                         } else {
1671                                 waddnstr(view->win, spaces,
1672                                          MIN(view->digits, STRING_SIZE(spaces)));
1673                         }
1674                         waddstr(view->win, ": ");
1675                         col_offset = view->digits + 2;
1676                 }
1677
1678                 while (text && col_offset + col < view->width) {
1679                         int cols_max = view->width - col_offset - col;
1680                         char *pos = text;
1681                         int cols;
1682
1683                         if (*text == '\t') {
1684                                 text++;
1685                                 assert(sizeof(spaces) > TABSIZE);
1686                                 pos = spaces;
1687                                 cols = opt_tab_size - (col % opt_tab_size);
1688
1689                         } else {
1690                                 text = strchr(text, '\t');
1691                                 cols = line ? text - pos : strlen(pos);
1692                         }
1693
1694                         waddnstr(view->win, pos, MIN(cols, cols_max));
1695                         col += cols;
1696                 }
1697
1698         } else {
1699                 int col = 0, pos = 0;
1700
1701                 for (; pos < textlen && col < view->width; pos++, col++)
1702                         if (text[pos] == '\t')
1703                                 col += TABSIZE - (col % TABSIZE) - 1;
1704
1705                 waddnstr(view->win, text, pos);
1706         }
1707
1708         return TRUE;
1709 }
1710
1711 static bool
1712 pager_read(struct view *view, struct line *prev, char *line)
1713 {
1714         view->line[view->lines].data = strdup(line);
1715         if (!view->line[view->lines].data)
1716                 return FALSE;
1717
1718         view->line[view->lines].type = get_line_type(line);
1719
1720         view->lines++;
1721         return TRUE;
1722 }
1723
1724 static bool
1725 pager_enter(struct view *view, struct line *line)
1726 {
1727         int split = 0;
1728
1729         if (line->type == LINE_COMMIT &&
1730            (view == VIEW(REQ_VIEW_LOG) ||
1731             view == VIEW(REQ_VIEW_PAGER))) {
1732                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1733                 split = 1;
1734         }
1735
1736         /* Always scroll the view even if it was split. That way
1737          * you can use Enter to scroll through the log view and
1738          * split open each commit diff. */
1739         scroll_view(view, REQ_SCROLL_LINE_DOWN);
1740
1741         /* FIXME: A minor workaround. Scrolling the view will call report("")
1742          * but if we are scrolling a non-current view this won't properly
1743          * update the view title. */
1744         if (split)
1745                 update_view_title(view);
1746
1747         return TRUE;
1748 }
1749
1750 static struct view_ops pager_ops = {
1751         "line",
1752         pager_draw,
1753         pager_read,
1754         pager_enter,
1755 };
1756
1757
1758 /*
1759  * Main view backend
1760  */
1761
1762 struct commit {
1763         char id[41];            /* SHA1 ID. */
1764         char title[75];         /* The first line of the commit message. */
1765         char author[75];        /* The author of the commit. */
1766         struct tm time;         /* Date from the author ident. */
1767         struct ref **refs;      /* Repository references; tags & branch heads. */
1768 };
1769
1770 static bool
1771 main_draw(struct view *view, struct line *line, unsigned int lineno)
1772 {
1773         char buf[DATE_COLS + 1];
1774         struct commit *commit = line->data;
1775         enum line_type type;
1776         int col = 0;
1777         size_t timelen;
1778         size_t authorlen;
1779         int trimmed = 1;
1780
1781         if (!*commit->author)
1782                 return FALSE;
1783
1784         wmove(view->win, lineno, col);
1785
1786         if (view->offset + lineno == view->lineno) {
1787                 string_copy(view->ref, commit->id);
1788                 string_copy(ref_commit, view->ref);
1789                 type = LINE_CURSOR;
1790                 wattrset(view->win, get_line_attr(type));
1791                 wchgat(view->win, -1, 0, type, NULL);
1792
1793         } else {
1794                 type = LINE_MAIN_COMMIT;
1795                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
1796         }
1797
1798         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
1799         waddnstr(view->win, buf, timelen);
1800         waddstr(view->win, " ");
1801
1802         col += DATE_COLS;
1803         wmove(view->win, lineno, col);
1804         if (type != LINE_CURSOR)
1805                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
1806
1807         if (opt_utf8) {
1808                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1809         } else {
1810                 authorlen = strlen(commit->author);
1811                 if (authorlen > AUTHOR_COLS - 2) {
1812                         authorlen = AUTHOR_COLS - 2;
1813                         trimmed = 1;
1814                 }
1815         }
1816
1817         if (trimmed) {
1818                 waddnstr(view->win, commit->author, authorlen);
1819                 if (type != LINE_CURSOR)
1820                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
1821                 waddch(view->win, '~');
1822         } else {
1823                 waddstr(view->win, commit->author);
1824         }
1825
1826         col += AUTHOR_COLS;
1827         if (type != LINE_CURSOR)
1828                 wattrset(view->win, A_NORMAL);
1829
1830         mvwaddch(view->win, lineno, col, ACS_LTEE);
1831         wmove(view->win, lineno, col + 2);
1832         col += 2;
1833
1834         if (commit->refs) {
1835                 size_t i = 0;
1836
1837                 do {
1838                         if (type == LINE_CURSOR)
1839                                 ;
1840                         else if (commit->refs[i]->tag)
1841                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1842                         else
1843                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1844                         waddstr(view->win, "[");
1845                         waddstr(view->win, commit->refs[i]->name);
1846                         waddstr(view->win, "]");
1847                         if (type != LINE_CURSOR)
1848                                 wattrset(view->win, A_NORMAL);
1849                         waddstr(view->win, " ");
1850                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
1851                 } while (commit->refs[i++]->next);
1852         }
1853
1854         if (type != LINE_CURSOR)
1855                 wattrset(view->win, get_line_attr(type));
1856
1857         {
1858                 int titlelen = strlen(commit->title);
1859
1860                 if (col + titlelen > view->width)
1861                         titlelen = view->width - col;
1862
1863                 waddnstr(view->win, commit->title, titlelen);
1864         }
1865
1866         return TRUE;
1867 }
1868
1869 /* Reads git log --pretty=raw output and parses it into the commit struct. */
1870 static bool
1871 main_read(struct view *view, struct line *prev, char *line)
1872 {
1873         enum line_type type = get_line_type(line);
1874         struct commit *commit;
1875
1876         switch (type) {
1877         case LINE_COMMIT:
1878                 commit = calloc(1, sizeof(struct commit));
1879                 if (!commit)
1880                         return FALSE;
1881
1882                 line += STRING_SIZE("commit ");
1883
1884                 view->line[view->lines++].data = commit;
1885                 string_copy(commit->id, line);
1886                 commit->refs = get_refs(commit->id);
1887                 break;
1888
1889         case LINE_AUTHOR:
1890         {
1891                 char *ident = line + STRING_SIZE("author ");
1892                 char *end = strchr(ident, '<');
1893
1894                 if (!prev)
1895                         break;
1896
1897                 commit = prev->data;
1898
1899                 if (end) {
1900                         for (; end > ident && isspace(end[-1]); end--) ;
1901                         *end = 0;
1902                 }
1903
1904                 string_copy(commit->author, ident);
1905
1906                 /* Parse epoch and timezone */
1907                 if (end) {
1908                         char *secs = strchr(end + 1, '>');
1909                         char *zone;
1910                         time_t time;
1911
1912                         if (!secs || secs[1] != ' ')
1913                                 break;
1914
1915                         secs += 2;
1916                         time = (time_t) atol(secs);
1917                         zone = strchr(secs, ' ');
1918                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
1919                                 long tz;
1920
1921                                 zone++;
1922                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
1923                                 tz += ('0' - zone[2]) * 60 * 60;
1924                                 tz += ('0' - zone[3]) * 60;
1925                                 tz += ('0' - zone[4]) * 60;
1926
1927                                 if (zone[0] == '-')
1928                                         tz = -tz;
1929
1930                                 time -= tz;
1931                         }
1932                         gmtime_r(&time, &commit->time);
1933                 }
1934                 break;
1935         }
1936         default:
1937                 if (!prev)
1938                         break;
1939
1940                 commit = prev->data;
1941
1942                 /* Fill in the commit title if it has not already been set. */
1943                 if (commit->title[0])
1944                         break;
1945
1946                 /* Require titles to start with a non-space character at the
1947                  * offset used by git log. */
1948                 /* FIXME: More gracefull handling of titles; append "..." to
1949                  * shortened titles, etc. */
1950                 if (strncmp(line, "    ", 4) ||
1951                     isspace(line[4]))
1952                         break;
1953
1954                 string_copy(commit->title, line + 4);
1955         }
1956
1957         return TRUE;
1958 }
1959
1960 static bool
1961 main_enter(struct view *view, struct line *line)
1962 {
1963         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
1964
1965         open_view(view, REQ_VIEW_DIFF, flags);
1966         return TRUE;
1967 }
1968
1969 static struct view_ops main_ops = {
1970         "commit",
1971         main_draw,
1972         main_read,
1973         main_enter,
1974 };
1975
1976
1977 /*
1978  * Keys
1979  */
1980
1981 struct keymap {
1982         int alias;
1983         int request;
1984 };
1985
1986 static struct keymap keymap[] = {
1987         /* View switching */
1988         { 'm',          REQ_VIEW_MAIN },
1989         { 'd',          REQ_VIEW_DIFF },
1990         { 'l',          REQ_VIEW_LOG },
1991         { 'p',          REQ_VIEW_PAGER },
1992         { 'h',          REQ_VIEW_HELP },
1993         { '?',          REQ_VIEW_HELP },
1994
1995         /* View manipulation */
1996         { 'q',          REQ_VIEW_CLOSE },
1997         { KEY_TAB,      REQ_VIEW_NEXT },
1998         { KEY_RETURN,   REQ_ENTER },
1999         { KEY_UP,       REQ_PREVIOUS },
2000         { KEY_DOWN,     REQ_NEXT },
2001
2002         /* Cursor navigation */
2003         { 'k',          REQ_MOVE_UP },
2004         { 'j',          REQ_MOVE_DOWN },
2005         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
2006         { KEY_END,      REQ_MOVE_LAST_LINE },
2007         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
2008         { ' ',          REQ_MOVE_PAGE_DOWN },
2009         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
2010         { 'b',          REQ_MOVE_PAGE_UP },
2011         { '-',          REQ_MOVE_PAGE_UP },
2012
2013         /* Scrolling */
2014         { KEY_IC,       REQ_SCROLL_LINE_UP },
2015         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
2016         { 'w',          REQ_SCROLL_PAGE_UP },
2017         { 's',          REQ_SCROLL_PAGE_DOWN },
2018
2019         /* Misc */
2020         { 'Q',          REQ_QUIT },
2021         { 'z',          REQ_STOP_LOADING },
2022         { 'v',          REQ_SHOW_VERSION },
2023         { 'r',          REQ_SCREEN_REDRAW },
2024         { 'n',          REQ_TOGGLE_LINENO },
2025         { ':',          REQ_PROMPT },
2026
2027         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
2028         { ERR,          REQ_SCREEN_UPDATE },
2029
2030         /* Use the ncurses SIGWINCH handler. */
2031         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
2032 };
2033
2034 static enum request
2035 get_request(int key)
2036 {
2037         int i;
2038
2039         for (i = 0; i < ARRAY_SIZE(keymap); i++)
2040                 if (keymap[i].alias == key)
2041                         return keymap[i].request;
2042
2043         return (enum request) key;
2044 }
2045
2046 struct key {
2047         char *name;
2048         int value;
2049 };
2050
2051 static struct key key_table[] = {
2052         { "Enter",      KEY_RETURN },
2053         { "Space",      ' ' },
2054         { "Backspace",  KEY_BACKSPACE },
2055         { "Tab",        KEY_TAB },
2056         { "Escape",     KEY_ESC },
2057         { "Left",       KEY_LEFT },
2058         { "Right",      KEY_RIGHT },
2059         { "Up",         KEY_UP },
2060         { "Down",       KEY_DOWN },
2061         { "Insert",     KEY_IC },
2062         { "Delete",     KEY_DC },
2063         { "Home",       KEY_HOME },
2064         { "End",        KEY_END },
2065         { "PageUp",     KEY_PPAGE },
2066         { "PageDown",   KEY_NPAGE },
2067         { "F1",         KEY_F(1) },
2068         { "F2",         KEY_F(2) },
2069         { "F3",         KEY_F(3) },
2070         { "F4",         KEY_F(4) },
2071         { "F5",         KEY_F(5) },
2072         { "F6",         KEY_F(6) },
2073         { "F7",         KEY_F(7) },
2074         { "F8",         KEY_F(8) },
2075         { "F9",         KEY_F(9) },
2076         { "F10",        KEY_F(10) },
2077         { "F11",        KEY_F(11) },
2078         { "F12",        KEY_F(12) },
2079 };
2080
2081 static char *
2082 get_key(enum request request)
2083 {
2084         static char buf[BUFSIZ];
2085         static char key_char[] = "'X'";
2086         int pos = 0;
2087         char *sep = "    ";
2088         int i;
2089
2090         buf[pos] = 0;
2091
2092         for (i = 0; i < ARRAY_SIZE(keymap); i++) {
2093                 char *seq = NULL;
2094                 int key;
2095
2096                 if (keymap[i].request != request)
2097                         continue;
2098
2099                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
2100                         if (key_table[key].value == keymap[i].alias)
2101                                 seq = key_table[key].name;
2102
2103                 if (seq == NULL &&
2104                     keymap[i].alias < 127 &&
2105                     isprint(keymap[i].alias)) {
2106                         key_char[1] = (char) keymap[i].alias;
2107                         seq = key_char;
2108                 }
2109
2110                 if (!seq)
2111                         seq = "'?'";
2112
2113                 pos += snprintf(buf + pos, sizeof(buf) - pos, "%s%s", sep, seq);
2114                 if (pos >= sizeof(buf))
2115                         return "Too many keybindings!";
2116                 sep = ", ";
2117         }
2118
2119         return buf;
2120 }
2121
2122 static void load_help_page(void)
2123 {
2124         char buf[BUFSIZ];
2125         struct view *view = VIEW(REQ_VIEW_HELP);
2126         int lines = ARRAY_SIZE(req_info) + 2;
2127         int i;
2128
2129         if (view->lines > 0)
2130                 return;
2131
2132         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2133                 if (!req_info[i].request)
2134                         lines++;
2135
2136         view->line = calloc(lines, sizeof(*view->line));
2137         if (!view->line) {
2138                 report("Allocation failure");
2139                 return;
2140         }
2141
2142         pager_read(view, NULL, "Quick reference for tig keybindings:");
2143
2144         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2145                 char *key;
2146
2147                 if (!req_info[i].request) {
2148                         pager_read(view, NULL, "");
2149                         pager_read(view, NULL, req_info[i].help);
2150                         continue;
2151                 }
2152
2153                 key = get_key(req_info[i].request);
2154                 if (snprintf(buf, sizeof(buf), "%-25s %s", key, req_info[i].help)
2155                     >= sizeof(buf))
2156                         continue;
2157
2158                 pager_read(view, NULL, buf);
2159         }
2160 }
2161
2162
2163 /*
2164  * Unicode / UTF-8 handling
2165  *
2166  * NOTE: Much of the following code for dealing with unicode is derived from
2167  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2168  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2169  */
2170
2171 /* I've (over)annotated a lot of code snippets because I am not entirely
2172  * confident that the approach taken by this small UTF-8 interface is correct.
2173  * --jonas */
2174
2175 static inline int
2176 unicode_width(unsigned long c)
2177 {
2178         if (c >= 0x1100 &&
2179            (c <= 0x115f                         /* Hangul Jamo */
2180             || c == 0x2329
2181             || c == 0x232a
2182             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2183                                                 /* CJK ... Yi */
2184             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2185             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2186             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2187             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2188             || (c >= 0xffe0  && c <= 0xffe6)
2189             || (c >= 0x20000 && c <= 0x2fffd)
2190             || (c >= 0x30000 && c <= 0x3fffd)))
2191                 return 2;
2192
2193         return 1;
2194 }
2195
2196 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2197  * Illegal bytes are set one. */
2198 static const unsigned char utf8_bytes[256] = {
2199         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,
2200         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,
2201         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,
2202         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,
2203         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,
2204         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,
2205         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,
2206         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,
2207 };
2208
2209 /* Decode UTF-8 multi-byte representation into a unicode character. */
2210 static inline unsigned long
2211 utf8_to_unicode(const char *string, size_t length)
2212 {
2213         unsigned long unicode;
2214
2215         switch (length) {
2216         case 1:
2217                 unicode  =   string[0];
2218                 break;
2219         case 2:
2220                 unicode  =  (string[0] & 0x1f) << 6;
2221                 unicode +=  (string[1] & 0x3f);
2222                 break;
2223         case 3:
2224                 unicode  =  (string[0] & 0x0f) << 12;
2225                 unicode += ((string[1] & 0x3f) << 6);
2226                 unicode +=  (string[2] & 0x3f);
2227                 break;
2228         case 4:
2229                 unicode  =  (string[0] & 0x0f) << 18;
2230                 unicode += ((string[1] & 0x3f) << 12);
2231                 unicode += ((string[2] & 0x3f) << 6);
2232                 unicode +=  (string[3] & 0x3f);
2233                 break;
2234         case 5:
2235                 unicode  =  (string[0] & 0x0f) << 24;
2236                 unicode += ((string[1] & 0x3f) << 18);
2237                 unicode += ((string[2] & 0x3f) << 12);
2238                 unicode += ((string[3] & 0x3f) << 6);
2239                 unicode +=  (string[4] & 0x3f);
2240                 break;
2241         case 6:
2242                 unicode  =  (string[0] & 0x01) << 30;
2243                 unicode += ((string[1] & 0x3f) << 24);
2244                 unicode += ((string[2] & 0x3f) << 18);
2245                 unicode += ((string[3] & 0x3f) << 12);
2246                 unicode += ((string[4] & 0x3f) << 6);
2247                 unicode +=  (string[5] & 0x3f);
2248                 break;
2249         default:
2250                 die("Invalid unicode length");
2251         }
2252
2253         /* Invalid characters could return the special 0xfffd value but NUL
2254          * should be just as good. */
2255         return unicode > 0xffff ? 0 : unicode;
2256 }
2257
2258 /* Calculates how much of string can be shown within the given maximum width
2259  * and sets trimmed parameter to non-zero value if all of string could not be
2260  * shown.
2261  *
2262  * Additionally, adds to coloffset how many many columns to move to align with
2263  * the expected position. Takes into account how multi-byte and double-width
2264  * characters will effect the cursor position.
2265  *
2266  * Returns the number of bytes to output from string to satisfy max_width. */
2267 static size_t
2268 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2269 {
2270         const char *start = string;
2271         const char *end = strchr(string, '\0');
2272         size_t mbwidth = 0;
2273         size_t width = 0;
2274
2275         *trimmed = 0;
2276
2277         while (string < end) {
2278                 int c = *(unsigned char *) string;
2279                 unsigned char bytes = utf8_bytes[c];
2280                 size_t ucwidth;
2281                 unsigned long unicode;
2282
2283                 if (string + bytes > end)
2284                         break;
2285
2286                 /* Change representation to figure out whether
2287                  * it is a single- or double-width character. */
2288
2289                 unicode = utf8_to_unicode(string, bytes);
2290                 /* FIXME: Graceful handling of invalid unicode character. */
2291                 if (!unicode)
2292                         break;
2293
2294                 ucwidth = unicode_width(unicode);
2295                 width  += ucwidth;
2296                 if (width > max_width) {
2297                         *trimmed = 1;
2298                         break;
2299                 }
2300
2301                 /* The column offset collects the differences between the
2302                  * number of bytes encoding a character and the number of
2303                  * columns will be used for rendering said character.
2304                  *
2305                  * So if some character A is encoded in 2 bytes, but will be
2306                  * represented on the screen using only 1 byte this will and up
2307                  * adding 1 to the multi-byte column offset.
2308                  *
2309                  * Assumes that no double-width character can be encoding in
2310                  * less than two bytes. */
2311                 if (bytes > ucwidth)
2312                         mbwidth += bytes - ucwidth;
2313
2314                 string  += bytes;
2315         }
2316
2317         *coloffset += mbwidth;
2318
2319         return string - start;
2320 }
2321
2322
2323 /*
2324  * Status management
2325  */
2326
2327 /* Whether or not the curses interface has been initialized. */
2328 static bool cursed = FALSE;
2329
2330 /* The status window is used for polling keystrokes. */
2331 static WINDOW *status_win;
2332
2333 /* Update status and title window. */
2334 static void
2335 report(const char *msg, ...)
2336 {
2337         static bool empty = TRUE;
2338         struct view *view = display[current_view];
2339
2340         if (!empty || *msg) {
2341                 va_list args;
2342
2343                 va_start(args, msg);
2344
2345                 werase(status_win);
2346                 wmove(status_win, 0, 0);
2347                 if (*msg) {
2348                         vwprintw(status_win, msg, args);
2349                         empty = FALSE;
2350                 } else {
2351                         empty = TRUE;
2352                 }
2353                 wrefresh(status_win);
2354
2355                 va_end(args);
2356         }
2357
2358         update_view_title(view);
2359         update_display_cursor();
2360 }
2361
2362 /* Controls when nodelay should be in effect when polling user input. */
2363 static void
2364 set_nonblocking_input(bool loading)
2365 {
2366         static unsigned int loading_views;
2367
2368         if ((loading == FALSE && loading_views-- == 1) ||
2369             (loading == TRUE  && loading_views++ == 0))
2370                 nodelay(status_win, loading);
2371 }
2372
2373 static void
2374 init_display(void)
2375 {
2376         int x, y;
2377
2378         /* Initialize the curses library */
2379         if (isatty(STDIN_FILENO)) {
2380                 cursed = !!initscr();
2381         } else {
2382                 /* Leave stdin and stdout alone when acting as a pager. */
2383                 FILE *io = fopen("/dev/tty", "r+");
2384
2385                 cursed = !!newterm(NULL, io, io);
2386         }
2387
2388         if (!cursed)
2389                 die("Failed to initialize curses");
2390
2391         nonl();         /* Tell curses not to do NL->CR/NL on output */
2392         cbreak();       /* Take input chars one at a time, no wait for \n */
2393         noecho();       /* Don't echo input */
2394         leaveok(stdscr, TRUE);
2395
2396         if (has_colors())
2397                 init_colors();
2398
2399         getmaxyx(stdscr, y, x);
2400         status_win = newwin(1, 0, y - 1, 0);
2401         if (!status_win)
2402                 die("Failed to create status window");
2403
2404         /* Enable keyboard mapping */
2405         keypad(status_win, TRUE);
2406         wbkgdset(status_win, get_line_attr(LINE_STATUS));
2407 }
2408
2409
2410 /*
2411  * Repository references
2412  */
2413
2414 static struct ref *refs;
2415 static size_t refs_size;
2416
2417 /* Id <-> ref store */
2418 static struct ref ***id_refs;
2419 static size_t id_refs_size;
2420
2421 static struct ref **
2422 get_refs(char *id)
2423 {
2424         struct ref ***tmp_id_refs;
2425         struct ref **ref_list = NULL;
2426         size_t ref_list_size = 0;
2427         size_t i;
2428
2429         for (i = 0; i < id_refs_size; i++)
2430                 if (!strcmp(id, id_refs[i][0]->id))
2431                         return id_refs[i];
2432
2433         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2434         if (!tmp_id_refs)
2435                 return NULL;
2436
2437         id_refs = tmp_id_refs;
2438
2439         for (i = 0; i < refs_size; i++) {
2440                 struct ref **tmp;
2441
2442                 if (strcmp(id, refs[i].id))
2443                         continue;
2444
2445                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2446                 if (!tmp) {
2447                         if (ref_list)
2448                                 free(ref_list);
2449                         return NULL;
2450                 }
2451
2452                 ref_list = tmp;
2453                 if (ref_list_size > 0)
2454                         ref_list[ref_list_size - 1]->next = 1;
2455                 ref_list[ref_list_size] = &refs[i];
2456
2457                 /* XXX: The properties of the commit chains ensures that we can
2458                  * safely modify the shared ref. The repo references will
2459                  * always be similar for the same id. */
2460                 ref_list[ref_list_size]->next = 0;
2461                 ref_list_size++;
2462         }
2463
2464         if (ref_list)
2465                 id_refs[id_refs_size++] = ref_list;
2466
2467         return ref_list;
2468 }
2469
2470 static int
2471 read_ref(char *id, int idlen, char *name, int namelen)
2472 {
2473         struct ref *ref;
2474         bool tag = FALSE;
2475         bool tag_commit = FALSE;
2476
2477         /* Commits referenced by tags has "^{}" appended. */
2478         if (name[namelen - 1] == '}') {
2479                 while (namelen > 0 && name[namelen] != '^')
2480                         namelen--;
2481                 if (namelen > 0)
2482                         tag_commit = TRUE;
2483                 name[namelen] = 0;
2484         }
2485
2486         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2487                 if (!tag_commit)
2488                         return OK;
2489                 name += STRING_SIZE("refs/tags/");
2490                 tag = TRUE;
2491
2492         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2493                 name += STRING_SIZE("refs/heads/");
2494
2495         } else if (!strcmp(name, "HEAD")) {
2496                 return OK;
2497         }
2498
2499         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2500         if (!refs)
2501                 return ERR;
2502
2503         ref = &refs[refs_size++];
2504         ref->name = strdup(name);
2505         if (!ref->name)
2506                 return ERR;
2507
2508         ref->tag = tag;
2509         string_copy(ref->id, id);
2510
2511         return OK;
2512 }
2513
2514 static int
2515 load_refs(void)
2516 {
2517         const char *cmd_env = getenv("TIG_LS_REMOTE");
2518         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2519
2520         return read_properties(popen(cmd, "r"), "\t", read_ref);
2521 }
2522
2523 static int
2524 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2525 {
2526         if (!strcmp(name, "i18n.commitencoding")) {
2527                 string_copy(opt_encoding, value);
2528         }
2529
2530         return OK;
2531 }
2532
2533 static int
2534 load_repo_config(void)
2535 {
2536         return read_properties(popen("git repo-config --list", "r"),
2537                                "=", read_repo_config_option);
2538 }
2539
2540 static int
2541 read_properties(FILE *pipe, const char *separators,
2542                 int (*read_property)(char *, int, char *, int))
2543 {
2544         char buffer[BUFSIZ];
2545         char *name;
2546         int state = OK;
2547
2548         if (!pipe)
2549                 return ERR;
2550
2551         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2552                 char *value;
2553                 size_t namelen;
2554                 size_t valuelen;
2555
2556                 name = chomp_string(name);
2557                 namelen = strcspn(name, separators);
2558
2559                 if (name[namelen]) {
2560                         name[namelen] = 0;
2561                         value = chomp_string(name + namelen + 1);
2562                         valuelen = strlen(value);
2563
2564                 } else {
2565                         value = "";
2566                         valuelen = 0;
2567                 }
2568
2569                 state = read_property(name, namelen, value, valuelen);
2570         }
2571
2572         if (state != ERR && ferror(pipe))
2573                 state = ERR;
2574
2575         pclose(pipe);
2576
2577         return state;
2578 }
2579
2580
2581 /*
2582  * Main
2583  */
2584
2585 #if __GNUC__ >= 3
2586 #define __NORETURN __attribute__((__noreturn__))
2587 #else
2588 #define __NORETURN
2589 #endif
2590
2591 static void __NORETURN
2592 quit(int sig)
2593 {
2594         /* XXX: Restore tty modes and let the OS cleanup the rest! */
2595         if (cursed)
2596                 endwin();
2597         exit(0);
2598 }
2599
2600 static void __NORETURN
2601 die(const char *err, ...)
2602 {
2603         va_list args;
2604
2605         endwin();
2606
2607         va_start(args, err);
2608         fputs("tig: ", stderr);
2609         vfprintf(stderr, err, args);
2610         fputs("\n", stderr);
2611         va_end(args);
2612
2613         exit(1);
2614 }
2615
2616 int
2617 main(int argc, char *argv[])
2618 {
2619         struct view *view;
2620         enum request request;
2621         size_t i;
2622
2623         signal(SIGINT, quit);
2624
2625         if (load_options() == ERR)
2626                 die("Failed to load user config.");
2627
2628         /* Load the repo config file so options can be overwritten from
2629          * the command line.  */
2630         if (load_repo_config() == ERR)
2631                 die("Failed to load repo config.");
2632
2633         if (!parse_options(argc, argv))
2634                 return 0;
2635
2636         if (load_refs() == ERR)
2637                 die("Failed to load refs.");
2638
2639         /* Require a git repository unless when running in pager mode. */
2640         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2641                 die("Not a git repository");
2642
2643         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2644                 view->cmd_env = getenv(view->cmd_env);
2645
2646         request = opt_request;
2647
2648         init_display();
2649
2650         while (view_driver(display[current_view], request)) {
2651                 int key;
2652                 int i;
2653
2654                 foreach_view (view, i)
2655                         update_view(view);
2656
2657                 /* Refresh, accept single keystroke of input */
2658                 key = wgetch(status_win);
2659                 request = get_request(key);
2660
2661                 /* Some low-level request handling. This keeps access to
2662                  * status_win restricted. */
2663                 switch (request) {
2664                 case REQ_PROMPT:
2665                         report(":");
2666                         /* Temporarily switch to line-oriented and echoed
2667                          * input. */
2668                         nocbreak();
2669                         echo();
2670
2671                         if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2672                                 memcpy(opt_cmd, "git ", 4);
2673                                 opt_request = REQ_VIEW_PAGER;
2674                         } else {
2675                                 report("Prompt interrupted by loading view, "
2676                                        "press 'z' to stop loading views");
2677                                 request = REQ_SCREEN_UPDATE;
2678                         }
2679
2680                         noecho();
2681                         cbreak();
2682                         break;
2683
2684                 case REQ_SCREEN_RESIZE:
2685                 {
2686                         int height, width;
2687
2688                         getmaxyx(stdscr, height, width);
2689
2690                         /* Resize the status view and let the view driver take
2691                          * care of resizing the displayed views. */
2692                         wresize(status_win, 1, width);
2693                         mvwin(status_win, height - 1, 0);
2694                         wrefresh(status_win);
2695                         break;
2696                 }
2697                 default:
2698                         break;
2699                 }
2700         }
2701
2702         quit(0);
2703
2704         return 0;
2705 }
2706
2707 /**
2708  * include::BUGS[]
2709  *
2710  * COPYRIGHT
2711  * ---------
2712  * Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2713  *
2714  * This program is free software; you can redistribute it and/or modify
2715  * it under the terms of the GNU General Public License as published by
2716  * the Free Software Foundation; either version 2 of the License, or
2717  * (at your option) any later version.
2718  *
2719  * SEE ALSO
2720  * --------
2721  * - link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
2722  * - link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
2723  *
2724  * Other git repository browsers:
2725  *
2726  *  - gitk(1)
2727  *  - qgit(1)
2728  *  - gitview(1)
2729  *
2730  * Sites:
2731  *
2732  * include::SITES[]
2733  **/