Fix string_copy macro to use sizeof(src) for the source buffer
[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 #ifndef VERSION
15 #define VERSION "unknown-version"
16 #endif
17
18 #ifndef DEBUG
19 #define NDEBUG
20 #endif
21
22 #include <assert.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <time.h>
32
33 #include <sys/types.h>
34 #include <regex.h>
35
36 #include <locale.h>
37 #include <langinfo.h>
38 #include <iconv.h>
39
40 #include <curses.h>
41
42 #if __GNUC__ >= 3
43 #define __NORETURN __attribute__((__noreturn__))
44 #else
45 #define __NORETURN
46 #endif
47
48 static void __NORETURN die(const char *err, ...);
49 static void report(const char *msg, ...);
50 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
51 static void set_nonblocking_input(bool loading);
52 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
53
54 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
55 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
56
57 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
58 #define STRING_SIZE(x)  (sizeof(x) - 1)
59
60 #define SIZEOF_STR      1024    /* Default string size. */
61 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
62 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
63
64 /* Revision graph */
65
66 #define REVGRAPH_INIT   'I'
67 #define REVGRAPH_MERGE  'M'
68 #define REVGRAPH_BRANCH '+'
69 #define REVGRAPH_COMMIT '*'
70 #define REVGRAPH_LINE   '|'
71
72 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
73
74 /* This color name can be used to refer to the default term colors. */
75 #define COLOR_DEFAULT   (-1)
76
77 #define ICONV_NONE      ((iconv_t) -1)
78
79 /* The format and size of the date column in the main view. */
80 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
81 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
82
83 #define AUTHOR_COLS     20
84
85 /* The default interval between line numbers. */
86 #define NUMBER_INTERVAL 1
87
88 #define TABSIZE         8
89
90 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
91
92 #define TIG_LS_REMOTE \
93         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
94
95 #define TIG_DIFF_CMD \
96         "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
97
98 #define TIG_LOG_CMD     \
99         "git log --cc --stat -n100 %s 2>/dev/null"
100
101 #define TIG_MAIN_CMD \
102         "git log --topo-order --pretty=raw %s 2>/dev/null"
103
104 #define TIG_TREE_CMD    \
105         "git ls-tree %s %s"
106
107 #define TIG_BLOB_CMD    \
108         "git cat-file blob %s"
109
110 /* XXX: Needs to be defined to the empty string. */
111 #define TIG_HELP_CMD    ""
112 #define TIG_PAGER_CMD   ""
113 #define TIG_STATUS_CMD  ""
114
115 /* Some ascii-shorthands fitted into the ncurses namespace. */
116 #define KEY_TAB         '\t'
117 #define KEY_RETURN      '\r'
118 #define KEY_ESC         27
119
120
121 struct ref {
122         char *name;             /* Ref name; tag or head names are shortened. */
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int tag:1;     /* Is it a tag? */
125         unsigned int remote:1;  /* Is it a remote ref? */
126         unsigned int next:1;    /* For ref lists: are there more refs? */
127 };
128
129 static struct ref **get_refs(char *id);
130
131 struct int_map {
132         const char *name;
133         int namelen;
134         int value;
135 };
136
137 static int
138 set_from_int_map(struct int_map *map, size_t map_size,
139                  int *value, const char *name, int namelen)
140 {
141
142         int i;
143
144         for (i = 0; i < map_size; i++)
145                 if (namelen == map[i].namelen &&
146                     !strncasecmp(name, map[i].name, namelen)) {
147                         *value = map[i].value;
148                         return OK;
149                 }
150
151         return ERR;
152 }
153
154
155 /*
156  * String helpers
157  */
158
159 static inline void
160 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
161 {
162         if (srclen > dstlen - 1)
163                 srclen = dstlen - 1;
164
165         strncpy(dst, src, srclen);
166         dst[srclen] = 0;
167 }
168
169 /* Shorthands for safely copying into a fixed buffer. */
170
171 #define string_copy(dst, src) \
172         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
173
174 #define string_ncopy(dst, src, srclen) \
175         string_ncopy_do(dst, sizeof(dst), src, srclen)
176
177 #define string_copy_rev(dst, src) \
178         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
179
180 static char *
181 chomp_string(char *name)
182 {
183         int namelen;
184
185         while (isspace(*name))
186                 name++;
187
188         namelen = strlen(name) - 1;
189         while (namelen > 0 && isspace(name[namelen]))
190                 name[namelen--] = 0;
191
192         return name;
193 }
194
195 static bool
196 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
197 {
198         va_list args;
199         size_t pos = bufpos ? *bufpos : 0;
200
201         va_start(args, fmt);
202         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
203         va_end(args);
204
205         if (bufpos)
206                 *bufpos = pos;
207
208         return pos >= bufsize ? FALSE : TRUE;
209 }
210
211 #define string_format(buf, fmt, args...) \
212         string_nformat(buf, sizeof(buf), NULL, fmt, args)
213
214 #define string_format_from(buf, from, fmt, args...) \
215         string_nformat(buf, sizeof(buf), from, fmt, args)
216
217 static int
218 string_enum_compare(const char *str1, const char *str2, int len)
219 {
220         size_t i;
221
222 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
223
224         /* Diff-Header == DIFF_HEADER */
225         for (i = 0; i < len; i++) {
226                 if (toupper(str1[i]) == toupper(str2[i]))
227                         continue;
228
229                 if (string_enum_sep(str1[i]) &&
230                     string_enum_sep(str2[i]))
231                         continue;
232
233                 return str1[i] - str2[i];
234         }
235
236         return 0;
237 }
238
239 /* Shell quoting
240  *
241  * NOTE: The following is a slightly modified copy of the git project's shell
242  * quoting routines found in the quote.c file.
243  *
244  * Help to copy the thing properly quoted for the shell safety.  any single
245  * quote is replaced with '\'', any exclamation point is replaced with '\!',
246  * and the whole thing is enclosed in a
247  *
248  * E.g.
249  *  original     sq_quote     result
250  *  name     ==> name      ==> 'name'
251  *  a b      ==> a b       ==> 'a b'
252  *  a'b      ==> a'\''b    ==> 'a'\''b'
253  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
254  */
255
256 static size_t
257 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
258 {
259         char c;
260
261 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
262
263         BUFPUT('\'');
264         while ((c = *src++)) {
265                 if (c == '\'' || c == '!') {
266                         BUFPUT('\'');
267                         BUFPUT('\\');
268                         BUFPUT(c);
269                         BUFPUT('\'');
270                 } else {
271                         BUFPUT(c);
272                 }
273         }
274         BUFPUT('\'');
275
276         if (bufsize < SIZEOF_STR)
277                 buf[bufsize] = 0;
278
279         return bufsize;
280 }
281
282
283 /*
284  * User requests
285  */
286
287 #define REQ_INFO \
288         /* XXX: Keep the view request first and in sync with views[]. */ \
289         REQ_GROUP("View switching") \
290         REQ_(VIEW_MAIN,         "Show main view"), \
291         REQ_(VIEW_DIFF,         "Show diff view"), \
292         REQ_(VIEW_LOG,          "Show log view"), \
293         REQ_(VIEW_TREE,         "Show tree view"), \
294         REQ_(VIEW_BLOB,         "Show blob view"), \
295         REQ_(VIEW_HELP,         "Show help page"), \
296         REQ_(VIEW_PAGER,        "Show pager view"), \
297         REQ_(VIEW_STATUS,       "Show status view"), \
298         \
299         REQ_GROUP("View manipulation") \
300         REQ_(ENTER,             "Enter current line and scroll"), \
301         REQ_(NEXT,              "Move to next"), \
302         REQ_(PREVIOUS,          "Move to previous"), \
303         REQ_(VIEW_NEXT,         "Move focus to next view"), \
304         REQ_(VIEW_CLOSE,        "Close the current view"), \
305         REQ_(QUIT,              "Close all views and quit"), \
306         \
307         REQ_GROUP("Cursor navigation") \
308         REQ_(MOVE_UP,           "Move cursor one line up"), \
309         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
310         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
311         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
312         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
313         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
314         \
315         REQ_GROUP("Scrolling") \
316         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
317         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
318         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
319         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
320         \
321         REQ_GROUP("Searching") \
322         REQ_(SEARCH,            "Search the view"), \
323         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
324         REQ_(FIND_NEXT,         "Find next search match"), \
325         REQ_(FIND_PREV,         "Find previous search match"), \
326         \
327         REQ_GROUP("Misc") \
328         REQ_(NONE,              "Do nothing"), \
329         REQ_(PROMPT,            "Bring up the prompt"), \
330         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
331         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
332         REQ_(SHOW_VERSION,      "Show version information"), \
333         REQ_(STOP_LOADING,      "Stop all loading views"), \
334         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
335         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization")
336
337
338 /* User action requests. */
339 enum request {
340 #define REQ_GROUP(help)
341 #define REQ_(req, help) REQ_##req
342
343         /* Offset all requests to avoid conflicts with ncurses getch values. */
344         REQ_OFFSET = KEY_MAX + 1,
345         REQ_INFO,
346         REQ_UNKNOWN,
347
348 #undef  REQ_GROUP
349 #undef  REQ_
350 };
351
352 struct request_info {
353         enum request request;
354         char *name;
355         int namelen;
356         char *help;
357 };
358
359 static struct request_info req_info[] = {
360 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
361 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
362         REQ_INFO
363 #undef  REQ_GROUP
364 #undef  REQ_
365 };
366
367 static enum request
368 get_request(const char *name)
369 {
370         int namelen = strlen(name);
371         int i;
372
373         for (i = 0; i < ARRAY_SIZE(req_info); i++)
374                 if (req_info[i].namelen == namelen &&
375                     !string_enum_compare(req_info[i].name, name, namelen))
376                         return req_info[i].request;
377
378         return REQ_UNKNOWN;
379 }
380
381
382 /*
383  * Options
384  */
385
386 static const char usage[] =
387 "tig " VERSION " (" __DATE__ ")\n"
388 "\n"
389 "Usage: tig [options]\n"
390 "   or: tig [options] [--] [git log options]\n"
391 "   or: tig [options] log  [git log options]\n"
392 "   or: tig [options] diff [git diff options]\n"
393 "   or: tig [options] show [git show options]\n"
394 "   or: tig [options] <    [git command output]\n"
395 "\n"
396 "Options:\n"
397 "  -l                          Start up in log view\n"
398 "  -d                          Start up in diff view\n"
399 "  -S                          Start up in status view\n"
400 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
401 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
402 "  --                          Mark end of tig options\n"
403 "  -v, --version               Show version and exit\n"
404 "  -h, --help                  Show help message and exit\n";
405
406 /* Option and state variables. */
407 static bool opt_line_number             = FALSE;
408 static bool opt_rev_graph               = FALSE;
409 static int opt_num_interval             = NUMBER_INTERVAL;
410 static int opt_tab_size                 = TABSIZE;
411 static enum request opt_request         = REQ_VIEW_MAIN;
412 static char opt_cmd[SIZEOF_STR]         = "";
413 static char opt_path[SIZEOF_STR]        = "";
414 static FILE *opt_pipe                   = NULL;
415 static char opt_encoding[20]            = "UTF-8";
416 static bool opt_utf8                    = TRUE;
417 static char opt_codeset[20]             = "UTF-8";
418 static iconv_t opt_iconv                = ICONV_NONE;
419 static char opt_search[SIZEOF_STR]      = "";
420
421 enum option_type {
422         OPT_NONE,
423         OPT_INT,
424 };
425
426 static bool
427 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
428 {
429         va_list args;
430         char *value = "";
431         int *number;
432
433         if (opt[0] != '-')
434                 return FALSE;
435
436         if (opt[1] == '-') {
437                 int namelen = strlen(name);
438
439                 opt += 2;
440
441                 if (strncmp(opt, name, namelen))
442                         return FALSE;
443
444                 if (opt[namelen] == '=')
445                         value = opt + namelen + 1;
446
447         } else {
448                 if (!short_name || opt[1] != short_name)
449                         return FALSE;
450                 value = opt + 2;
451         }
452
453         va_start(args, type);
454         if (type == OPT_INT) {
455                 number = va_arg(args, int *);
456                 if (isdigit(*value))
457                         *number = atoi(value);
458         }
459         va_end(args);
460
461         return TRUE;
462 }
463
464 /* Returns the index of log or diff command or -1 to exit. */
465 static bool
466 parse_options(int argc, char *argv[])
467 {
468         int i;
469
470         for (i = 1; i < argc; i++) {
471                 char *opt = argv[i];
472
473                 if (!strcmp(opt, "log") ||
474                     !strcmp(opt, "diff") ||
475                     !strcmp(opt, "show")) {
476                         opt_request = opt[0] == 'l'
477                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
478                         break;
479                 }
480
481                 if (opt[0] && opt[0] != '-')
482                         break;
483
484                 if (!strcmp(opt, "-l")) {
485                         opt_request = REQ_VIEW_LOG;
486                         continue;
487                 }
488
489                 if (!strcmp(opt, "-d")) {
490                         opt_request = REQ_VIEW_DIFF;
491                         continue;
492                 }
493
494                 if (!strcmp(opt, "-S")) {
495                         opt_request = REQ_VIEW_STATUS;
496                         break;
497                 }
498
499                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
500                         opt_line_number = TRUE;
501                         continue;
502                 }
503
504                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
505                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
506                         continue;
507                 }
508
509                 if (check_option(opt, 'v', "version", OPT_NONE)) {
510                         printf("tig version %s\n", VERSION);
511                         return FALSE;
512                 }
513
514                 if (check_option(opt, 'h', "help", OPT_NONE)) {
515                         printf(usage);
516                         return FALSE;
517                 }
518
519                 if (!strcmp(opt, "--")) {
520                         i++;
521                         break;
522                 }
523
524                 die("unknown option '%s'\n\n%s", opt, usage);
525         }
526
527         if (!isatty(STDIN_FILENO)) {
528                 opt_request = REQ_VIEW_PAGER;
529                 opt_pipe = stdin;
530
531         } else if (i < argc) {
532                 size_t buf_size;
533
534                 if (opt_request == REQ_VIEW_MAIN)
535                         /* XXX: This is vulnerable to the user overriding
536                          * options required for the main view parser. */
537                         string_copy(opt_cmd, "git log --pretty=raw");
538                 else
539                         string_copy(opt_cmd, "git");
540                 buf_size = strlen(opt_cmd);
541
542                 while (buf_size < sizeof(opt_cmd) && i < argc) {
543                         opt_cmd[buf_size++] = ' ';
544                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
545                 }
546
547                 if (buf_size >= sizeof(opt_cmd))
548                         die("command too long");
549
550                 opt_cmd[buf_size] = 0;
551         }
552
553         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
554                 opt_utf8 = FALSE;
555
556         return TRUE;
557 }
558
559
560 /*
561  * Line-oriented content detection.
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(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
586 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
587 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
588 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
589 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
590 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
591 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
592 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
593 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
594 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
595 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
596 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
597 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
598 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
599 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
600 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
601 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
602 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
603 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
604 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
605 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
606 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
607 LINE(STAT_SECTION, "",                  COLOR_DEFAULT,  COLOR_BLUE,     A_BOLD), \
608 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
609 LINE(STAT_STAGED,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
610 LINE(STAT_UNSTAGED,"",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
611 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
612
613 enum line_type {
614 #define LINE(type, line, fg, bg, attr) \
615         LINE_##type
616         LINE_INFO
617 #undef  LINE
618 };
619
620 struct line_info {
621         const char *name;       /* Option name. */
622         int namelen;            /* Size of option name. */
623         const char *line;       /* The start of line to match. */
624         int linelen;            /* Size of string to match. */
625         int fg, bg, attr;       /* Color and text attributes for the lines. */
626 };
627
628 static struct line_info line_info[] = {
629 #define LINE(type, line, fg, bg, attr) \
630         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
631         LINE_INFO
632 #undef  LINE
633 };
634
635 static enum line_type
636 get_line_type(char *line)
637 {
638         int linelen = strlen(line);
639         enum line_type type;
640
641         for (type = 0; type < ARRAY_SIZE(line_info); type++)
642                 /* Case insensitive search matches Signed-off-by lines better. */
643                 if (linelen >= line_info[type].linelen &&
644                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
645                         return type;
646
647         return LINE_DEFAULT;
648 }
649
650 static inline int
651 get_line_attr(enum line_type type)
652 {
653         assert(type < ARRAY_SIZE(line_info));
654         return COLOR_PAIR(type) | line_info[type].attr;
655 }
656
657 static struct line_info *
658 get_line_info(char *name, int namelen)
659 {
660         enum line_type type;
661
662         for (type = 0; type < ARRAY_SIZE(line_info); type++)
663                 if (namelen == line_info[type].namelen &&
664                     !string_enum_compare(line_info[type].name, name, namelen))
665                         return &line_info[type];
666
667         return NULL;
668 }
669
670 static void
671 init_colors(void)
672 {
673         int default_bg = COLOR_BLACK;
674         int default_fg = COLOR_WHITE;
675         enum line_type type;
676
677         start_color();
678
679         if (use_default_colors() != ERR) {
680                 default_bg = -1;
681                 default_fg = -1;
682         }
683
684         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
685                 struct line_info *info = &line_info[type];
686                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
687                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
688
689                 init_pair(type, fg, bg);
690         }
691 }
692
693 struct line {
694         enum line_type type;
695
696         /* State flags */
697         unsigned int selected:1;
698
699         void *data;             /* User data */
700 };
701
702
703 /*
704  * Keys
705  */
706
707 struct keybinding {
708         int alias;
709         enum request request;
710         struct keybinding *next;
711 };
712
713 static struct keybinding default_keybindings[] = {
714         /* View switching */
715         { 'm',          REQ_VIEW_MAIN },
716         { 'd',          REQ_VIEW_DIFF },
717         { 'l',          REQ_VIEW_LOG },
718         { 't',          REQ_VIEW_TREE },
719         { 'f',          REQ_VIEW_BLOB },
720         { 'p',          REQ_VIEW_PAGER },
721         { 'h',          REQ_VIEW_HELP },
722         { 'S',          REQ_VIEW_STATUS },
723
724         /* View manipulation */
725         { 'q',          REQ_VIEW_CLOSE },
726         { KEY_TAB,      REQ_VIEW_NEXT },
727         { KEY_RETURN,   REQ_ENTER },
728         { KEY_UP,       REQ_PREVIOUS },
729         { KEY_DOWN,     REQ_NEXT },
730
731         /* Cursor navigation */
732         { 'k',          REQ_MOVE_UP },
733         { 'j',          REQ_MOVE_DOWN },
734         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
735         { KEY_END,      REQ_MOVE_LAST_LINE },
736         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
737         { ' ',          REQ_MOVE_PAGE_DOWN },
738         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
739         { 'b',          REQ_MOVE_PAGE_UP },
740         { '-',          REQ_MOVE_PAGE_UP },
741
742         /* Scrolling */
743         { KEY_IC,       REQ_SCROLL_LINE_UP },
744         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
745         { 'w',          REQ_SCROLL_PAGE_UP },
746         { 's',          REQ_SCROLL_PAGE_DOWN },
747
748         /* Searching */
749         { '/',          REQ_SEARCH },
750         { '?',          REQ_SEARCH_BACK },
751         { 'n',          REQ_FIND_NEXT },
752         { 'N',          REQ_FIND_PREV },
753
754         /* Misc */
755         { 'Q',          REQ_QUIT },
756         { 'z',          REQ_STOP_LOADING },
757         { 'v',          REQ_SHOW_VERSION },
758         { 'r',          REQ_SCREEN_REDRAW },
759         { '.',          REQ_TOGGLE_LINENO },
760         { 'g',          REQ_TOGGLE_REV_GRAPH },
761         { ':',          REQ_PROMPT },
762
763         /* Using the ncurses SIGWINCH handler. */
764         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
765 };
766
767 #define KEYMAP_INFO \
768         KEYMAP_(GENERIC), \
769         KEYMAP_(MAIN), \
770         KEYMAP_(DIFF), \
771         KEYMAP_(LOG), \
772         KEYMAP_(TREE), \
773         KEYMAP_(BLOB), \
774         KEYMAP_(PAGER), \
775         KEYMAP_(HELP), \
776         KEYMAP_(STATUS)
777
778 enum keymap {
779 #define KEYMAP_(name) KEYMAP_##name
780         KEYMAP_INFO
781 #undef  KEYMAP_
782 };
783
784 static struct int_map keymap_table[] = {
785 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
786         KEYMAP_INFO
787 #undef  KEYMAP_
788 };
789
790 #define set_keymap(map, name) \
791         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
792
793 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
794
795 static void
796 add_keybinding(enum keymap keymap, enum request request, int key)
797 {
798         struct keybinding *keybinding;
799
800         keybinding = calloc(1, sizeof(*keybinding));
801         if (!keybinding)
802                 die("Failed to allocate keybinding");
803
804         keybinding->alias = key;
805         keybinding->request = request;
806         keybinding->next = keybindings[keymap];
807         keybindings[keymap] = keybinding;
808 }
809
810 /* Looks for a key binding first in the given map, then in the generic map, and
811  * lastly in the default keybindings. */
812 static enum request
813 get_keybinding(enum keymap keymap, int key)
814 {
815         struct keybinding *kbd;
816         int i;
817
818         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
819                 if (kbd->alias == key)
820                         return kbd->request;
821
822         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
823                 if (kbd->alias == key)
824                         return kbd->request;
825
826         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
827                 if (default_keybindings[i].alias == key)
828                         return default_keybindings[i].request;
829
830         return (enum request) key;
831 }
832
833
834 struct key {
835         char *name;
836         int value;
837 };
838
839 static struct key key_table[] = {
840         { "Enter",      KEY_RETURN },
841         { "Space",      ' ' },
842         { "Backspace",  KEY_BACKSPACE },
843         { "Tab",        KEY_TAB },
844         { "Escape",     KEY_ESC },
845         { "Left",       KEY_LEFT },
846         { "Right",      KEY_RIGHT },
847         { "Up",         KEY_UP },
848         { "Down",       KEY_DOWN },
849         { "Insert",     KEY_IC },
850         { "Delete",     KEY_DC },
851         { "Hash",       '#' },
852         { "Home",       KEY_HOME },
853         { "End",        KEY_END },
854         { "PageUp",     KEY_PPAGE },
855         { "PageDown",   KEY_NPAGE },
856         { "F1",         KEY_F(1) },
857         { "F2",         KEY_F(2) },
858         { "F3",         KEY_F(3) },
859         { "F4",         KEY_F(4) },
860         { "F5",         KEY_F(5) },
861         { "F6",         KEY_F(6) },
862         { "F7",         KEY_F(7) },
863         { "F8",         KEY_F(8) },
864         { "F9",         KEY_F(9) },
865         { "F10",        KEY_F(10) },
866         { "F11",        KEY_F(11) },
867         { "F12",        KEY_F(12) },
868 };
869
870 static int
871 get_key_value(const char *name)
872 {
873         int i;
874
875         for (i = 0; i < ARRAY_SIZE(key_table); i++)
876                 if (!strcasecmp(key_table[i].name, name))
877                         return key_table[i].value;
878
879         if (strlen(name) == 1 && isprint(*name))
880                 return (int) *name;
881
882         return ERR;
883 }
884
885 static char *
886 get_key(enum request request)
887 {
888         static char buf[BUFSIZ];
889         static char key_char[] = "'X'";
890         size_t pos = 0;
891         char *sep = "";
892         int i;
893
894         buf[pos] = 0;
895
896         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
897                 struct keybinding *keybinding = &default_keybindings[i];
898                 char *seq = NULL;
899                 int key;
900
901                 if (keybinding->request != request)
902                         continue;
903
904                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
905                         if (key_table[key].value == keybinding->alias)
906                                 seq = key_table[key].name;
907
908                 if (seq == NULL &&
909                     keybinding->alias < 127 &&
910                     isprint(keybinding->alias)) {
911                         key_char[1] = (char) keybinding->alias;
912                         seq = key_char;
913                 }
914
915                 if (!seq)
916                         seq = "'?'";
917
918                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
919                         return "Too many keybindings!";
920                 sep = ", ";
921         }
922
923         return buf;
924 }
925
926
927 /*
928  * User config file handling.
929  */
930
931 static struct int_map color_map[] = {
932 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
933         COLOR_MAP(DEFAULT),
934         COLOR_MAP(BLACK),
935         COLOR_MAP(BLUE),
936         COLOR_MAP(CYAN),
937         COLOR_MAP(GREEN),
938         COLOR_MAP(MAGENTA),
939         COLOR_MAP(RED),
940         COLOR_MAP(WHITE),
941         COLOR_MAP(YELLOW),
942 };
943
944 #define set_color(color, name) \
945         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
946
947 static struct int_map attr_map[] = {
948 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
949         ATTR_MAP(NORMAL),
950         ATTR_MAP(BLINK),
951         ATTR_MAP(BOLD),
952         ATTR_MAP(DIM),
953         ATTR_MAP(REVERSE),
954         ATTR_MAP(STANDOUT),
955         ATTR_MAP(UNDERLINE),
956 };
957
958 #define set_attribute(attr, name) \
959         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
960
961 static int   config_lineno;
962 static bool  config_errors;
963 static char *config_msg;
964
965 /* Wants: object fgcolor bgcolor [attr] */
966 static int
967 option_color_command(int argc, char *argv[])
968 {
969         struct line_info *info;
970
971         if (argc != 3 && argc != 4) {
972                 config_msg = "Wrong number of arguments given to color command";
973                 return ERR;
974         }
975
976         info = get_line_info(argv[0], strlen(argv[0]));
977         if (!info) {
978                 config_msg = "Unknown color name";
979                 return ERR;
980         }
981
982         if (set_color(&info->fg, argv[1]) == ERR ||
983             set_color(&info->bg, argv[2]) == ERR) {
984                 config_msg = "Unknown color";
985                 return ERR;
986         }
987
988         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
989                 config_msg = "Unknown attribute";
990                 return ERR;
991         }
992
993         return OK;
994 }
995
996 /* Wants: name = value */
997 static int
998 option_set_command(int argc, char *argv[])
999 {
1000         if (argc != 3) {
1001                 config_msg = "Wrong number of arguments given to set command";
1002                 return ERR;
1003         }
1004
1005         if (strcmp(argv[1], "=")) {
1006                 config_msg = "No value assigned";
1007                 return ERR;
1008         }
1009
1010         if (!strcmp(argv[0], "show-rev-graph")) {
1011                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1012                                  !strcmp(argv[2], "true") ||
1013                                  !strcmp(argv[2], "yes"));
1014                 return OK;
1015         }
1016
1017         if (!strcmp(argv[0], "line-number-interval")) {
1018                 opt_num_interval = atoi(argv[2]);
1019                 return OK;
1020         }
1021
1022         if (!strcmp(argv[0], "tab-size")) {
1023                 opt_tab_size = atoi(argv[2]);
1024                 return OK;
1025         }
1026
1027         if (!strcmp(argv[0], "commit-encoding")) {
1028                 char *arg = argv[2];
1029                 int delimiter = *arg;
1030                 int i;
1031
1032                 switch (delimiter) {
1033                 case '"':
1034                 case '\'':
1035                         for (arg++, i = 0; arg[i]; i++)
1036                                 if (arg[i] == delimiter) {
1037                                         arg[i] = 0;
1038                                         break;
1039                                 }
1040                 default:
1041                         string_copy(opt_encoding, arg);
1042                         return OK;
1043                 }
1044         }
1045
1046         config_msg = "Unknown variable name";
1047         return ERR;
1048 }
1049
1050 /* Wants: mode request key */
1051 static int
1052 option_bind_command(int argc, char *argv[])
1053 {
1054         enum request request;
1055         int keymap;
1056         int key;
1057
1058         if (argc != 3) {
1059                 config_msg = "Wrong number of arguments given to bind command";
1060                 return ERR;
1061         }
1062
1063         if (set_keymap(&keymap, argv[0]) == ERR) {
1064                 config_msg = "Unknown key map";
1065                 return ERR;
1066         }
1067
1068         key = get_key_value(argv[1]);
1069         if (key == ERR) {
1070                 config_msg = "Unknown key";
1071                 return ERR;
1072         }
1073
1074         request = get_request(argv[2]);
1075         if (request == REQ_UNKNOWN) {
1076                 config_msg = "Unknown request name";
1077                 return ERR;
1078         }
1079
1080         add_keybinding(keymap, request, key);
1081
1082         return OK;
1083 }
1084
1085 static int
1086 set_option(char *opt, char *value)
1087 {
1088         char *argv[16];
1089         int valuelen;
1090         int argc = 0;
1091
1092         /* Tokenize */
1093         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1094                 argv[argc++] = value;
1095
1096                 value += valuelen;
1097                 if (!*value)
1098                         break;
1099
1100                 *value++ = 0;
1101                 while (isspace(*value))
1102                         value++;
1103         }
1104
1105         if (!strcmp(opt, "color"))
1106                 return option_color_command(argc, argv);
1107
1108         if (!strcmp(opt, "set"))
1109                 return option_set_command(argc, argv);
1110
1111         if (!strcmp(opt, "bind"))
1112                 return option_bind_command(argc, argv);
1113
1114         config_msg = "Unknown option command";
1115         return ERR;
1116 }
1117
1118 static int
1119 read_option(char *opt, int optlen, char *value, int valuelen)
1120 {
1121         int status = OK;
1122
1123         config_lineno++;
1124         config_msg = "Internal error";
1125
1126         /* Check for comment markers, since read_properties() will
1127          * only ensure opt and value are split at first " \t". */
1128         optlen = strcspn(opt, "#");
1129         if (optlen == 0)
1130                 return OK;
1131
1132         if (opt[optlen] != 0) {
1133                 config_msg = "No option value";
1134                 status = ERR;
1135
1136         }  else {
1137                 /* Look for comment endings in the value. */
1138                 int len = strcspn(value, "#");
1139
1140                 if (len < valuelen) {
1141                         valuelen = len;
1142                         value[valuelen] = 0;
1143                 }
1144
1145                 status = set_option(opt, value);
1146         }
1147
1148         if (status == ERR) {
1149                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1150                         config_lineno, optlen, opt, config_msg);
1151                 config_errors = TRUE;
1152         }
1153
1154         /* Always keep going if errors are encountered. */
1155         return OK;
1156 }
1157
1158 static int
1159 load_options(void)
1160 {
1161         char *home = getenv("HOME");
1162         char buf[SIZEOF_STR];
1163         FILE *file;
1164
1165         config_lineno = 0;
1166         config_errors = FALSE;
1167
1168         if (!home || !string_format(buf, "%s/.tigrc", home))
1169                 return ERR;
1170
1171         /* It's ok that the file doesn't exist. */
1172         file = fopen(buf, "r");
1173         if (!file)
1174                 return OK;
1175
1176         if (read_properties(file, " \t", read_option) == ERR ||
1177             config_errors == TRUE)
1178                 fprintf(stderr, "Errors while loading %s.\n", buf);
1179
1180         return OK;
1181 }
1182
1183
1184 /*
1185  * The viewer
1186  */
1187
1188 struct view;
1189 struct view_ops;
1190
1191 /* The display array of active views and the index of the current view. */
1192 static struct view *display[2];
1193 static unsigned int current_view;
1194
1195 /* Reading from the prompt? */
1196 static bool input_mode = FALSE;
1197
1198 #define foreach_displayed_view(view, i) \
1199         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1200
1201 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1202
1203 /* Current head and commit ID */
1204 static char ref_blob[SIZEOF_REF]        = "";
1205 static char ref_commit[SIZEOF_REF]      = "HEAD";
1206 static char ref_head[SIZEOF_REF]        = "HEAD";
1207
1208 struct view {
1209         const char *name;       /* View name */
1210         const char *cmd_fmt;    /* Default command line format */
1211         const char *cmd_env;    /* Command line set via environment */
1212         const char *id;         /* Points to either of ref_{head,commit,blob} */
1213
1214         struct view_ops *ops;   /* View operations */
1215
1216         enum keymap keymap;     /* What keymap does this view have */
1217
1218         char cmd[SIZEOF_STR];   /* Command buffer */
1219         char ref[SIZEOF_REF];   /* Hovered commit reference */
1220         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1221
1222         int height, width;      /* The width and height of the main window */
1223         WINDOW *win;            /* The main window */
1224         WINDOW *title;          /* The title window living below the main window */
1225
1226         /* Navigation */
1227         unsigned long offset;   /* Offset of the window top */
1228         unsigned long lineno;   /* Current line number */
1229
1230         /* Searching */
1231         char grep[SIZEOF_STR];  /* Search string */
1232         regex_t *regex;         /* Pre-compiled regex */
1233
1234         /* If non-NULL, points to the view that opened this view. If this view
1235          * is closed tig will switch back to the parent view. */
1236         struct view *parent;
1237
1238         /* Buffering */
1239         unsigned long lines;    /* Total number of lines */
1240         struct line *line;      /* Line index */
1241         unsigned long line_size;/* Total number of allocated lines */
1242         unsigned int digits;    /* Number of digits in the lines member. */
1243
1244         /* Loading */
1245         FILE *pipe;
1246         time_t start_time;
1247 };
1248
1249 struct view_ops {
1250         /* What type of content being displayed. Used in the title bar. */
1251         const char *type;
1252         /* Open and reads in all view content. */
1253         bool (*open)(struct view *view);
1254         /* Read one line; updates view->line. */
1255         bool (*read)(struct view *view, char *data);
1256         /* Draw one line; @lineno must be < view->height. */
1257         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1258         /* Depending on view, change display based on current line. */
1259         bool (*enter)(struct view *view, struct line *line);
1260         /* Search for regex in a line. */
1261         bool (*grep)(struct view *view, struct line *line);
1262         /* Select line */
1263         void (*select)(struct view *view, struct line *line);
1264 };
1265
1266 static struct view_ops pager_ops;
1267 static struct view_ops main_ops;
1268 static struct view_ops tree_ops;
1269 static struct view_ops blob_ops;
1270 static struct view_ops help_ops;
1271 static struct view_ops status_ops;
1272
1273 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1274         { name, cmd, #env, ref, ops, map}
1275
1276 #define VIEW_(id, name, ops, ref) \
1277         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1278
1279
1280 static struct view views[] = {
1281         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1282         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1283         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1284         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1285         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1286         VIEW_(HELP,   "help",   &help_ops,  ""),
1287         VIEW_(PAGER,  "pager",  &pager_ops,  ""),
1288         VIEW_(STATUS, "status", &status_ops, ""),
1289 };
1290
1291 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1292
1293 #define foreach_view(view, i) \
1294         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1295
1296 #define view_is_displayed(view) \
1297         (view == display[0] || view == display[1])
1298
1299 static bool
1300 draw_view_line(struct view *view, unsigned int lineno)
1301 {
1302         struct line *line;
1303         bool selected = (view->offset + lineno == view->lineno);
1304         bool draw_ok;
1305
1306         assert(view_is_displayed(view));
1307
1308         if (view->offset + lineno >= view->lines)
1309                 return FALSE;
1310
1311         line = &view->line[view->offset + lineno];
1312
1313         if (selected) {
1314                 line->selected = TRUE;
1315                 view->ops->select(view, line);
1316         } else if (line->selected) {
1317                 line->selected = FALSE;
1318                 wmove(view->win, lineno, 0);
1319                 wclrtoeol(view->win);
1320         }
1321
1322         scrollok(view->win, FALSE);
1323         draw_ok = view->ops->draw(view, line, lineno, selected);
1324         scrollok(view->win, TRUE);
1325
1326         return draw_ok;
1327 }
1328
1329 static void
1330 redraw_view_from(struct view *view, int lineno)
1331 {
1332         assert(0 <= lineno && lineno < view->height);
1333
1334         for (; lineno < view->height; lineno++) {
1335                 if (!draw_view_line(view, lineno))
1336                         break;
1337         }
1338
1339         redrawwin(view->win);
1340         if (input_mode)
1341                 wnoutrefresh(view->win);
1342         else
1343                 wrefresh(view->win);
1344 }
1345
1346 static void
1347 redraw_view(struct view *view)
1348 {
1349         wclear(view->win);
1350         redraw_view_from(view, 0);
1351 }
1352
1353
1354 static void
1355 update_view_title(struct view *view)
1356 {
1357         char buf[SIZEOF_STR];
1358         char state[SIZEOF_STR];
1359         size_t bufpos = 0, statelen = 0;
1360
1361         assert(view_is_displayed(view));
1362
1363         if (view->lines || view->pipe) {
1364                 unsigned int view_lines = view->offset + view->height;
1365                 unsigned int lines = view->lines
1366                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1367                                    : 0;
1368
1369                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1370                                    view->ops->type,
1371                                    view->lineno + 1,
1372                                    view->lines,
1373                                    lines);
1374
1375                 if (view->pipe) {
1376                         time_t secs = time(NULL) - view->start_time;
1377
1378                         /* Three git seconds are a long time ... */
1379                         if (secs > 2)
1380                                 string_format_from(state, &statelen, " %lds", secs);
1381                 }
1382         }
1383
1384         string_format_from(buf, &bufpos, "[%s]", view->name);
1385         if (*view->ref && bufpos < view->width) {
1386                 size_t refsize = strlen(view->ref);
1387                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1388
1389                 if (minsize < view->width)
1390                         refsize = view->width - minsize + 7;
1391                 string_format_from(buf, &bufpos, " %.*s", refsize, view->ref);
1392         }
1393
1394         if (statelen && bufpos < view->width) {
1395                 string_format_from(buf, &bufpos, " %s", state);
1396         }
1397
1398         if (view == display[current_view])
1399                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1400         else
1401                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1402
1403         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1404         wclrtoeol(view->title);
1405         wmove(view->title, 0, view->width - 1);
1406
1407         if (input_mode)
1408                 wnoutrefresh(view->title);
1409         else
1410                 wrefresh(view->title);
1411 }
1412
1413 static void
1414 resize_display(void)
1415 {
1416         int offset, i;
1417         struct view *base = display[0];
1418         struct view *view = display[1] ? display[1] : display[0];
1419
1420         /* Setup window dimensions */
1421
1422         getmaxyx(stdscr, base->height, base->width);
1423
1424         /* Make room for the status window. */
1425         base->height -= 1;
1426
1427         if (view != base) {
1428                 /* Horizontal split. */
1429                 view->width   = base->width;
1430                 view->height  = SCALE_SPLIT_VIEW(base->height);
1431                 base->height -= view->height;
1432
1433                 /* Make room for the title bar. */
1434                 view->height -= 1;
1435         }
1436
1437         /* Make room for the title bar. */
1438         base->height -= 1;
1439
1440         offset = 0;
1441
1442         foreach_displayed_view (view, i) {
1443                 if (!view->win) {
1444                         view->win = newwin(view->height, 0, offset, 0);
1445                         if (!view->win)
1446                                 die("Failed to create %s view", view->name);
1447
1448                         scrollok(view->win, TRUE);
1449
1450                         view->title = newwin(1, 0, offset + view->height, 0);
1451                         if (!view->title)
1452                                 die("Failed to create title window");
1453
1454                 } else {
1455                         wresize(view->win, view->height, view->width);
1456                         mvwin(view->win,   offset, 0);
1457                         mvwin(view->title, offset + view->height, 0);
1458                 }
1459
1460                 offset += view->height + 1;
1461         }
1462 }
1463
1464 static void
1465 redraw_display(void)
1466 {
1467         struct view *view;
1468         int i;
1469
1470         foreach_displayed_view (view, i) {
1471                 redraw_view(view);
1472                 update_view_title(view);
1473         }
1474 }
1475
1476 static void
1477 update_display_cursor(struct view *view)
1478 {
1479         /* Move the cursor to the right-most column of the cursor line.
1480          *
1481          * XXX: This could turn out to be a bit expensive, but it ensures that
1482          * the cursor does not jump around. */
1483         if (view->lines) {
1484                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1485                 wrefresh(view->win);
1486         }
1487 }
1488
1489 /*
1490  * Navigation
1491  */
1492
1493 /* Scrolling backend */
1494 static void
1495 do_scroll_view(struct view *view, int lines)
1496 {
1497         bool redraw_current_line = FALSE;
1498
1499         /* The rendering expects the new offset. */
1500         view->offset += lines;
1501
1502         assert(0 <= view->offset && view->offset < view->lines);
1503         assert(lines);
1504
1505         /* Move current line into the view. */
1506         if (view->lineno < view->offset) {
1507                 view->lineno = view->offset;
1508                 redraw_current_line = TRUE;
1509         } else if (view->lineno >= view->offset + view->height) {
1510                 view->lineno = view->offset + view->height - 1;
1511                 redraw_current_line = TRUE;
1512         }
1513
1514         assert(view->offset <= view->lineno && view->lineno < view->lines);
1515
1516         /* Redraw the whole screen if scrolling is pointless. */
1517         if (view->height < ABS(lines)) {
1518                 redraw_view(view);
1519
1520         } else {
1521                 int line = lines > 0 ? view->height - lines : 0;
1522                 int end = line + ABS(lines);
1523
1524                 wscrl(view->win, lines);
1525
1526                 for (; line < end; line++) {
1527                         if (!draw_view_line(view, line))
1528                                 break;
1529                 }
1530
1531                 if (redraw_current_line)
1532                         draw_view_line(view, view->lineno - view->offset);
1533         }
1534
1535         redrawwin(view->win);
1536         wrefresh(view->win);
1537         report("");
1538 }
1539
1540 /* Scroll frontend */
1541 static void
1542 scroll_view(struct view *view, enum request request)
1543 {
1544         int lines = 1;
1545
1546         assert(view_is_displayed(view));
1547
1548         switch (request) {
1549         case REQ_SCROLL_PAGE_DOWN:
1550                 lines = view->height;
1551         case REQ_SCROLL_LINE_DOWN:
1552                 if (view->offset + lines > view->lines)
1553                         lines = view->lines - view->offset;
1554
1555                 if (lines == 0 || view->offset + view->height >= view->lines) {
1556                         report("Cannot scroll beyond the last line");
1557                         return;
1558                 }
1559                 break;
1560
1561         case REQ_SCROLL_PAGE_UP:
1562                 lines = view->height;
1563         case REQ_SCROLL_LINE_UP:
1564                 if (lines > view->offset)
1565                         lines = view->offset;
1566
1567                 if (lines == 0) {
1568                         report("Cannot scroll beyond the first line");
1569                         return;
1570                 }
1571
1572                 lines = -lines;
1573                 break;
1574
1575         default:
1576                 die("request %d not handled in switch", request);
1577         }
1578
1579         do_scroll_view(view, lines);
1580 }
1581
1582 /* Cursor moving */
1583 static void
1584 move_view(struct view *view, enum request request)
1585 {
1586         int scroll_steps = 0;
1587         int steps;
1588
1589         switch (request) {
1590         case REQ_MOVE_FIRST_LINE:
1591                 steps = -view->lineno;
1592                 break;
1593
1594         case REQ_MOVE_LAST_LINE:
1595                 steps = view->lines - view->lineno - 1;
1596                 break;
1597
1598         case REQ_MOVE_PAGE_UP:
1599                 steps = view->height > view->lineno
1600                       ? -view->lineno : -view->height;
1601                 break;
1602
1603         case REQ_MOVE_PAGE_DOWN:
1604                 steps = view->lineno + view->height >= view->lines
1605                       ? view->lines - view->lineno - 1 : view->height;
1606                 break;
1607
1608         case REQ_MOVE_UP:
1609                 steps = -1;
1610                 break;
1611
1612         case REQ_MOVE_DOWN:
1613                 steps = 1;
1614                 break;
1615
1616         default:
1617                 die("request %d not handled in switch", request);
1618         }
1619
1620         if (steps <= 0 && view->lineno == 0) {
1621                 report("Cannot move beyond the first line");
1622                 return;
1623
1624         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1625                 report("Cannot move beyond the last line");
1626                 return;
1627         }
1628
1629         /* Move the current line */
1630         view->lineno += steps;
1631         assert(0 <= view->lineno && view->lineno < view->lines);
1632
1633         /* Check whether the view needs to be scrolled */
1634         if (view->lineno < view->offset ||
1635             view->lineno >= view->offset + view->height) {
1636                 scroll_steps = steps;
1637                 if (steps < 0 && -steps > view->offset) {
1638                         scroll_steps = -view->offset;
1639
1640                 } else if (steps > 0) {
1641                         if (view->lineno == view->lines - 1 &&
1642                             view->lines > view->height) {
1643                                 scroll_steps = view->lines - view->offset - 1;
1644                                 if (scroll_steps >= view->height)
1645                                         scroll_steps -= view->height - 1;
1646                         }
1647                 }
1648         }
1649
1650         if (!view_is_displayed(view)) {
1651                 view->offset += scroll_steps;
1652                 assert(0 <= view->offset && view->offset < view->lines);
1653                 view->ops->select(view, &view->line[view->lineno]);
1654                 return;
1655         }
1656
1657         /* Repaint the old "current" line if we be scrolling */
1658         if (ABS(steps) < view->height)
1659                 draw_view_line(view, view->lineno - steps - view->offset);
1660
1661         if (scroll_steps) {
1662                 do_scroll_view(view, scroll_steps);
1663                 return;
1664         }
1665
1666         /* Draw the current line */
1667         draw_view_line(view, view->lineno - view->offset);
1668
1669         redrawwin(view->win);
1670         wrefresh(view->win);
1671         report("");
1672 }
1673
1674
1675 /*
1676  * Searching
1677  */
1678
1679 static void search_view(struct view *view, enum request request);
1680
1681 static bool
1682 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1683 {
1684         assert(view_is_displayed(view));
1685
1686         if (!view->ops->grep(view, line))
1687                 return FALSE;
1688
1689         if (lineno - view->offset >= view->height) {
1690                 view->offset = lineno;
1691                 view->lineno = lineno;
1692                 redraw_view(view);
1693
1694         } else {
1695                 unsigned long old_lineno = view->lineno - view->offset;
1696
1697                 view->lineno = lineno;
1698                 draw_view_line(view, old_lineno);
1699
1700                 draw_view_line(view, view->lineno - view->offset);
1701                 redrawwin(view->win);
1702                 wrefresh(view->win);
1703         }
1704
1705         report("Line %ld matches '%s'", lineno + 1, view->grep);
1706         return TRUE;
1707 }
1708
1709 static void
1710 find_next(struct view *view, enum request request)
1711 {
1712         unsigned long lineno = view->lineno;
1713         int direction;
1714
1715         if (!*view->grep) {
1716                 if (!*opt_search)
1717                         report("No previous search");
1718                 else
1719                         search_view(view, request);
1720                 return;
1721         }
1722
1723         switch (request) {
1724         case REQ_SEARCH:
1725         case REQ_FIND_NEXT:
1726                 direction = 1;
1727                 break;
1728
1729         case REQ_SEARCH_BACK:
1730         case REQ_FIND_PREV:
1731                 direction = -1;
1732                 break;
1733
1734         default:
1735                 return;
1736         }
1737
1738         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1739                 lineno += direction;
1740
1741         /* Note, lineno is unsigned long so will wrap around in which case it
1742          * will become bigger than view->lines. */
1743         for (; lineno < view->lines; lineno += direction) {
1744                 struct line *line = &view->line[lineno];
1745
1746                 if (find_next_line(view, lineno, line))
1747                         return;
1748         }
1749
1750         report("No match found for '%s'", view->grep);
1751 }
1752
1753 static void
1754 search_view(struct view *view, enum request request)
1755 {
1756         int regex_err;
1757
1758         if (view->regex) {
1759                 regfree(view->regex);
1760                 *view->grep = 0;
1761         } else {
1762                 view->regex = calloc(1, sizeof(*view->regex));
1763                 if (!view->regex)
1764                         return;
1765         }
1766
1767         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1768         if (regex_err != 0) {
1769                 char buf[SIZEOF_STR] = "unknown error";
1770
1771                 regerror(regex_err, view->regex, buf, sizeof(buf));
1772                 report("Search failed: %s", buf);
1773                 return;
1774         }
1775
1776         string_copy(view->grep, opt_search);
1777
1778         find_next(view, request);
1779 }
1780
1781 /*
1782  * Incremental updating
1783  */
1784
1785 static void
1786 end_update(struct view *view)
1787 {
1788         if (!view->pipe)
1789                 return;
1790         set_nonblocking_input(FALSE);
1791         if (view->pipe == stdin)
1792                 fclose(view->pipe);
1793         else
1794                 pclose(view->pipe);
1795         view->pipe = NULL;
1796 }
1797
1798 static bool
1799 begin_update(struct view *view)
1800 {
1801         const char *id = view->id;
1802
1803         if (view->pipe)
1804                 end_update(view);
1805
1806         if (opt_cmd[0]) {
1807                 string_copy(view->cmd, opt_cmd);
1808                 opt_cmd[0] = 0;
1809                 /* When running random commands, initially show the
1810                  * command in the title. However, it maybe later be
1811                  * overwritten if a commit line is selected. */
1812                 string_copy(view->ref, view->cmd);
1813
1814         } else if (view == VIEW(REQ_VIEW_TREE)) {
1815                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1816                 char path[SIZEOF_STR];
1817
1818                 if (strcmp(view->vid, view->id))
1819                         opt_path[0] = path[0] = 0;
1820                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1821                         return FALSE;
1822
1823                 if (!string_format(view->cmd, format, id, path))
1824                         return FALSE;
1825
1826         } else {
1827                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1828
1829                 if (!string_format(view->cmd, format, id, id, id, id, id))
1830                         return FALSE;
1831
1832                 /* Put the current ref_* value to the view title ref
1833                  * member. This is needed by the blob view. Most other
1834                  * views sets it automatically after loading because the
1835                  * first line is a commit line. */
1836                 string_copy(view->ref, id);
1837         }
1838
1839         /* Special case for the pager view. */
1840         if (opt_pipe) {
1841                 view->pipe = opt_pipe;
1842                 opt_pipe = NULL;
1843         } else {
1844                 view->pipe = popen(view->cmd, "r");
1845         }
1846
1847         if (!view->pipe)
1848                 return FALSE;
1849
1850         set_nonblocking_input(TRUE);
1851
1852         view->offset = 0;
1853         view->lines  = 0;
1854         view->lineno = 0;
1855         string_copy_rev(view->vid, id);
1856
1857         if (view->line) {
1858                 int i;
1859
1860                 for (i = 0; i < view->lines; i++)
1861                         if (view->line[i].data)
1862                                 free(view->line[i].data);
1863
1864                 free(view->line);
1865                 view->line = NULL;
1866         }
1867
1868         view->start_time = time(NULL);
1869
1870         return TRUE;
1871 }
1872
1873 static struct line *
1874 realloc_lines(struct view *view, size_t line_size)
1875 {
1876         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1877
1878         if (!tmp)
1879                 return NULL;
1880
1881         view->line = tmp;
1882         view->line_size = line_size;
1883         return view->line;
1884 }
1885
1886 static bool
1887 update_view(struct view *view)
1888 {
1889         char in_buffer[BUFSIZ];
1890         char out_buffer[BUFSIZ * 2];
1891         char *line;
1892         /* The number of lines to read. If too low it will cause too much
1893          * redrawing (and possible flickering), if too high responsiveness
1894          * will suffer. */
1895         unsigned long lines = view->height;
1896         int redraw_from = -1;
1897
1898         if (!view->pipe)
1899                 return TRUE;
1900
1901         /* Only redraw if lines are visible. */
1902         if (view->offset + view->height >= view->lines)
1903                 redraw_from = view->lines - view->offset;
1904
1905         /* FIXME: This is probably not perfect for backgrounded views. */
1906         if (!realloc_lines(view, view->lines + lines))
1907                 goto alloc_error;
1908
1909         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1910                 size_t linelen = strlen(line);
1911
1912                 if (linelen)
1913                         line[linelen - 1] = 0;
1914
1915                 if (opt_iconv != ICONV_NONE) {
1916                         char *inbuf = line;
1917                         size_t inlen = linelen;
1918
1919                         char *outbuf = out_buffer;
1920                         size_t outlen = sizeof(out_buffer);
1921
1922                         size_t ret;
1923
1924                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1925                         if (ret != (size_t) -1) {
1926                                 line = out_buffer;
1927                                 linelen = strlen(out_buffer);
1928                         }
1929                 }
1930
1931                 if (!view->ops->read(view, line))
1932                         goto alloc_error;
1933
1934                 if (lines-- == 1)
1935                         break;
1936         }
1937
1938         {
1939                 int digits;
1940
1941                 lines = view->lines;
1942                 for (digits = 0; lines; digits++)
1943                         lines /= 10;
1944
1945                 /* Keep the displayed view in sync with line number scaling. */
1946                 if (digits != view->digits) {
1947                         view->digits = digits;
1948                         redraw_from = 0;
1949                 }
1950         }
1951
1952         if (!view_is_displayed(view))
1953                 goto check_pipe;
1954
1955         if (view == VIEW(REQ_VIEW_TREE)) {
1956                 /* Clear the view and redraw everything since the tree sorting
1957                  * might have rearranged things. */
1958                 redraw_view(view);
1959
1960         } else if (redraw_from >= 0) {
1961                 /* If this is an incremental update, redraw the previous line
1962                  * since for commits some members could have changed when
1963                  * loading the main view. */
1964                 if (redraw_from > 0)
1965                         redraw_from--;
1966
1967                 /* Since revision graph visualization requires knowledge
1968                  * about the parent commit, it causes a further one-off
1969                  * needed to be redrawn for incremental updates. */
1970                 if (redraw_from > 0 && opt_rev_graph)
1971                         redraw_from--;
1972
1973                 /* Incrementally draw avoids flickering. */
1974                 redraw_view_from(view, redraw_from);
1975         }
1976
1977         /* Update the title _after_ the redraw so that if the redraw picks up a
1978          * commit reference in view->ref it'll be available here. */
1979         update_view_title(view);
1980
1981 check_pipe:
1982         if (ferror(view->pipe)) {
1983                 report("Failed to read: %s", strerror(errno));
1984                 goto end;
1985
1986         } else if (feof(view->pipe)) {
1987                 report("");
1988                 goto end;
1989         }
1990
1991         return TRUE;
1992
1993 alloc_error:
1994         report("Allocation failure");
1995
1996 end:
1997         view->ops->read(view, NULL);
1998         end_update(view);
1999         return FALSE;
2000 }
2001
2002 static struct line *
2003 add_line_data(struct view *view, void *data, enum line_type type)
2004 {
2005         struct line *line = &view->line[view->lines++];
2006
2007         memset(line, 0, sizeof(*line));
2008         line->type = type;
2009         line->data = data;
2010
2011         return line;
2012 }
2013
2014 static struct line *
2015 add_line_text(struct view *view, char *data, enum line_type type)
2016 {
2017         if (data)
2018                 data = strdup(data);
2019
2020         return data ? add_line_data(view, data, type) : NULL;
2021 }
2022
2023
2024 /*
2025  * View opening
2026  */
2027
2028 enum open_flags {
2029         OPEN_DEFAULT = 0,       /* Use default view switching. */
2030         OPEN_SPLIT = 1,         /* Split current view. */
2031         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2032         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2033 };
2034
2035 static void
2036 open_view(struct view *prev, enum request request, enum open_flags flags)
2037 {
2038         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2039         bool split = !!(flags & OPEN_SPLIT);
2040         bool reload = !!(flags & OPEN_RELOAD);
2041         struct view *view = VIEW(request);
2042         int nviews = displayed_views();
2043         struct view *base_view = display[0];
2044
2045         if (view == prev && nviews == 1 && !reload) {
2046                 report("Already in %s view", view->name);
2047                 return;
2048         }
2049
2050         if (view->ops->open) {
2051                 if (!view->ops->open(view)) {
2052                         report("Failed to load %s view", view->name);
2053                         return;
2054                 }
2055
2056         } else if ((reload || strcmp(view->vid, view->id)) &&
2057                    !begin_update(view)) {
2058                 report("Failed to load %s view", view->name);
2059                 return;
2060         }
2061
2062         if (split) {
2063                 display[1] = view;
2064                 if (!backgrounded)
2065                         current_view = 1;
2066         } else {
2067                 /* Maximize the current view. */
2068                 memset(display, 0, sizeof(display));
2069                 current_view = 0;
2070                 display[current_view] = view;
2071         }
2072
2073         /* Resize the view when switching between split- and full-screen,
2074          * or when switching between two different full-screen views. */
2075         if (nviews != displayed_views() ||
2076             (nviews == 1 && base_view != display[0]))
2077                 resize_display();
2078
2079         if (split && prev->lineno - prev->offset >= prev->height) {
2080                 /* Take the title line into account. */
2081                 int lines = prev->lineno - prev->offset - prev->height + 1;
2082
2083                 /* Scroll the view that was split if the current line is
2084                  * outside the new limited view. */
2085                 do_scroll_view(prev, lines);
2086         }
2087
2088         if (prev && view != prev) {
2089                 if (split && !backgrounded) {
2090                         /* "Blur" the previous view. */
2091                         update_view_title(prev);
2092                 }
2093
2094                 view->parent = prev;
2095         }
2096
2097         if (view->pipe && view->lines == 0) {
2098                 /* Clear the old view and let the incremental updating refill
2099                  * the screen. */
2100                 wclear(view->win);
2101                 report("");
2102         } else {
2103                 redraw_view(view);
2104                 report("");
2105         }
2106
2107         /* If the view is backgrounded the above calls to report()
2108          * won't redraw the view title. */
2109         if (backgrounded)
2110                 update_view_title(view);
2111 }
2112
2113
2114 /*
2115  * User request switch noodle
2116  */
2117
2118 static int
2119 view_driver(struct view *view, enum request request)
2120 {
2121         int i;
2122
2123         switch (request) {
2124         case REQ_MOVE_UP:
2125         case REQ_MOVE_DOWN:
2126         case REQ_MOVE_PAGE_UP:
2127         case REQ_MOVE_PAGE_DOWN:
2128         case REQ_MOVE_FIRST_LINE:
2129         case REQ_MOVE_LAST_LINE:
2130                 move_view(view, request);
2131                 break;
2132
2133         case REQ_SCROLL_LINE_DOWN:
2134         case REQ_SCROLL_LINE_UP:
2135         case REQ_SCROLL_PAGE_DOWN:
2136         case REQ_SCROLL_PAGE_UP:
2137                 scroll_view(view, request);
2138                 break;
2139
2140         case REQ_VIEW_BLOB:
2141                 if (!ref_blob[0]) {
2142                         report("No file chosen, press %s to open tree view",
2143                                get_key(REQ_VIEW_TREE));
2144                         break;
2145                 }
2146                 open_view(view, request, OPEN_DEFAULT);
2147                 break;
2148
2149         case REQ_VIEW_PAGER:
2150                 if (!VIEW(REQ_VIEW_PAGER)->lines) {
2151                         report("No pager content, press %s to run command from prompt",
2152                                get_key(REQ_PROMPT));
2153                         break;
2154                 }
2155                 open_view(view, request, OPEN_DEFAULT);
2156                 break;
2157
2158         case REQ_VIEW_MAIN:
2159         case REQ_VIEW_DIFF:
2160         case REQ_VIEW_LOG:
2161         case REQ_VIEW_TREE:
2162         case REQ_VIEW_HELP:
2163         case REQ_VIEW_STATUS:
2164                 open_view(view, request, OPEN_DEFAULT);
2165                 break;
2166
2167         case REQ_NEXT:
2168         case REQ_PREVIOUS:
2169                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2170
2171                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2172                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2173                    (view == VIEW(REQ_VIEW_BLOB) &&
2174                      view->parent == VIEW(REQ_VIEW_TREE))) {
2175                         view = view->parent;
2176                         move_view(view, request);
2177                         if (view_is_displayed(view))
2178                                 update_view_title(view);
2179                 } else {
2180                         move_view(view, request);
2181                         break;
2182                 }
2183                 /* Fall-through */
2184
2185         case REQ_ENTER:
2186                 if (!view->lines) {
2187                         report("Nothing to enter");
2188                         break;
2189                 }
2190                 return view->ops->enter(view, &view->line[view->lineno]);
2191
2192         case REQ_VIEW_NEXT:
2193         {
2194                 int nviews = displayed_views();
2195                 int next_view = (current_view + 1) % nviews;
2196
2197                 if (next_view == current_view) {
2198                         report("Only one view is displayed");
2199                         break;
2200                 }
2201
2202                 current_view = next_view;
2203                 /* Blur out the title of the previous view. */
2204                 update_view_title(view);
2205                 report("");
2206                 break;
2207         }
2208         case REQ_TOGGLE_LINENO:
2209                 opt_line_number = !opt_line_number;
2210                 redraw_display();
2211                 break;
2212
2213         case REQ_TOGGLE_REV_GRAPH:
2214                 opt_rev_graph = !opt_rev_graph;
2215                 redraw_display();
2216                 break;
2217
2218         case REQ_PROMPT:
2219                 /* Always reload^Wrerun commands from the prompt. */
2220                 open_view(view, opt_request, OPEN_RELOAD);
2221                 break;
2222
2223         case REQ_SEARCH:
2224         case REQ_SEARCH_BACK:
2225                 search_view(view, request);
2226                 break;
2227
2228         case REQ_FIND_NEXT:
2229         case REQ_FIND_PREV:
2230                 find_next(view, request);
2231                 break;
2232
2233         case REQ_STOP_LOADING:
2234                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2235                         view = &views[i];
2236                         if (view->pipe)
2237                                 report("Stopped loading the %s view", view->name),
2238                         end_update(view);
2239                 }
2240                 break;
2241
2242         case REQ_SHOW_VERSION:
2243                 report("tig-%s (built %s)", VERSION, __DATE__);
2244                 return TRUE;
2245
2246         case REQ_SCREEN_RESIZE:
2247                 resize_display();
2248                 /* Fall-through */
2249         case REQ_SCREEN_REDRAW:
2250                 redraw_display();
2251                 break;
2252
2253         case REQ_NONE:
2254                 doupdate();
2255                 return TRUE;
2256
2257         case REQ_VIEW_CLOSE:
2258                 /* XXX: Mark closed views by letting view->parent point to the
2259                  * view itself. Parents to closed view should never be
2260                  * followed. */
2261                 if (view->parent &&
2262                     view->parent->parent != view->parent) {
2263                         memset(display, 0, sizeof(display));
2264                         current_view = 0;
2265                         display[current_view] = view->parent;
2266                         view->parent = view;
2267                         resize_display();
2268                         redraw_display();
2269                         break;
2270                 }
2271                 /* Fall-through */
2272         case REQ_QUIT:
2273                 return FALSE;
2274
2275         default:
2276                 /* An unknown key will show most commonly used commands. */
2277                 report("Unknown key, press 'h' for help");
2278                 return TRUE;
2279         }
2280
2281         return TRUE;
2282 }
2283
2284
2285 /*
2286  * Pager backend
2287  */
2288
2289 static bool
2290 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2291 {
2292         char *text = line->data;
2293         enum line_type type = line->type;
2294         int textlen = strlen(text);
2295         int attr;
2296
2297         wmove(view->win, lineno, 0);
2298
2299         if (selected) {
2300                 type = LINE_CURSOR;
2301                 wchgat(view->win, -1, 0, type, NULL);
2302         }
2303
2304         attr = get_line_attr(type);
2305         wattrset(view->win, attr);
2306
2307         if (opt_line_number || opt_tab_size < TABSIZE) {
2308                 static char spaces[] = "                    ";
2309                 int col_offset = 0, col = 0;
2310
2311                 if (opt_line_number) {
2312                         unsigned long real_lineno = view->offset + lineno + 1;
2313
2314                         if (real_lineno == 1 ||
2315                             (real_lineno % opt_num_interval) == 0) {
2316                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2317
2318                         } else {
2319                                 waddnstr(view->win, spaces,
2320                                          MIN(view->digits, STRING_SIZE(spaces)));
2321                         }
2322                         waddstr(view->win, ": ");
2323                         col_offset = view->digits + 2;
2324                 }
2325
2326                 while (text && col_offset + col < view->width) {
2327                         int cols_max = view->width - col_offset - col;
2328                         char *pos = text;
2329                         int cols;
2330
2331                         if (*text == '\t') {
2332                                 text++;
2333                                 assert(sizeof(spaces) > TABSIZE);
2334                                 pos = spaces;
2335                                 cols = opt_tab_size - (col % opt_tab_size);
2336
2337                         } else {
2338                                 text = strchr(text, '\t');
2339                                 cols = line ? text - pos : strlen(pos);
2340                         }
2341
2342                         waddnstr(view->win, pos, MIN(cols, cols_max));
2343                         col += cols;
2344                 }
2345
2346         } else {
2347                 int col = 0, pos = 0;
2348
2349                 for (; pos < textlen && col < view->width; pos++, col++)
2350                         if (text[pos] == '\t')
2351                                 col += TABSIZE - (col % TABSIZE) - 1;
2352
2353                 waddnstr(view->win, text, pos);
2354         }
2355
2356         return TRUE;
2357 }
2358
2359 static bool
2360 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2361 {
2362         char refbuf[SIZEOF_STR];
2363         char *ref = NULL;
2364         FILE *pipe;
2365
2366         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2367                 return TRUE;
2368
2369         pipe = popen(refbuf, "r");
2370         if (!pipe)
2371                 return TRUE;
2372
2373         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2374                 ref = chomp_string(ref);
2375         pclose(pipe);
2376
2377         if (!ref || !*ref)
2378                 return TRUE;
2379
2380         /* This is the only fatal call, since it can "corrupt" the buffer. */
2381         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2382                 return FALSE;
2383
2384         return TRUE;
2385 }
2386
2387 static void
2388 add_pager_refs(struct view *view, struct line *line)
2389 {
2390         char buf[SIZEOF_STR];
2391         char *commit_id = line->data + STRING_SIZE("commit ");
2392         struct ref **refs;
2393         size_t bufpos = 0, refpos = 0;
2394         const char *sep = "Refs: ";
2395         bool is_tag = FALSE;
2396
2397         assert(line->type == LINE_COMMIT);
2398
2399         refs = get_refs(commit_id);
2400         if (!refs) {
2401                 if (view == VIEW(REQ_VIEW_DIFF))
2402                         goto try_add_describe_ref;
2403                 return;
2404         }
2405
2406         do {
2407                 struct ref *ref = refs[refpos];
2408                 char *fmt = ref->tag    ? "%s[%s]" :
2409                             ref->remote ? "%s<%s>" : "%s%s";
2410
2411                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2412                         return;
2413                 sep = ", ";
2414                 if (ref->tag)
2415                         is_tag = TRUE;
2416         } while (refs[refpos++]->next);
2417
2418         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2419 try_add_describe_ref:
2420                 /* Add <tag>-g<commit_id> "fake" reference. */
2421                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2422                         return;
2423         }
2424
2425         if (bufpos == 0)
2426                 return;
2427
2428         if (!realloc_lines(view, view->line_size + 1))
2429                 return;
2430
2431         add_line_text(view, buf, LINE_PP_REFS);
2432 }
2433
2434 static bool
2435 pager_read(struct view *view, char *data)
2436 {
2437         struct line *line;
2438
2439         if (!data)
2440                 return TRUE;
2441
2442         line = add_line_text(view, data, get_line_type(data));
2443         if (!line)
2444                 return FALSE;
2445
2446         if (line->type == LINE_COMMIT &&
2447             (view == VIEW(REQ_VIEW_DIFF) ||
2448              view == VIEW(REQ_VIEW_LOG)))
2449                 add_pager_refs(view, line);
2450
2451         return TRUE;
2452 }
2453
2454 static bool
2455 pager_enter(struct view *view, struct line *line)
2456 {
2457         int split = 0;
2458
2459         if (line->type == LINE_COMMIT &&
2460            (view == VIEW(REQ_VIEW_LOG) ||
2461             view == VIEW(REQ_VIEW_PAGER))) {
2462                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2463                 split = 1;
2464         }
2465
2466         /* Always scroll the view even if it was split. That way
2467          * you can use Enter to scroll through the log view and
2468          * split open each commit diff. */
2469         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2470
2471         /* FIXME: A minor workaround. Scrolling the view will call report("")
2472          * but if we are scrolling a non-current view this won't properly
2473          * update the view title. */
2474         if (split)
2475                 update_view_title(view);
2476
2477         return TRUE;
2478 }
2479
2480 static bool
2481 pager_grep(struct view *view, struct line *line)
2482 {
2483         regmatch_t pmatch;
2484         char *text = line->data;
2485
2486         if (!*text)
2487                 return FALSE;
2488
2489         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2490                 return FALSE;
2491
2492         return TRUE;
2493 }
2494
2495 static void
2496 pager_select(struct view *view, struct line *line)
2497 {
2498         if (line->type == LINE_COMMIT) {
2499                 char *text = line->data + STRING_SIZE("commit ");
2500
2501                 if (view != VIEW(REQ_VIEW_PAGER))
2502                         string_copy_rev(view->ref, text);
2503                 string_copy_rev(ref_commit, text);
2504         }
2505 }
2506
2507 static struct view_ops pager_ops = {
2508         "line",
2509         NULL,
2510         pager_read,
2511         pager_draw,
2512         pager_enter,
2513         pager_grep,
2514         pager_select,
2515 };
2516
2517
2518 /*
2519  * Help backend
2520  */
2521
2522 static bool
2523 help_open(struct view *view)
2524 {
2525         char buf[BUFSIZ];
2526         int lines = ARRAY_SIZE(req_info) + 2;
2527         int i;
2528
2529         if (view->lines > 0)
2530                 return TRUE;
2531
2532         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2533                 if (!req_info[i].request)
2534                         lines++;
2535
2536         view->line = calloc(lines, sizeof(*view->line));
2537         if (!view->line)
2538                 return FALSE;
2539
2540         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2541
2542         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2543                 char *key;
2544
2545                 if (!req_info[i].request) {
2546                         add_line_text(view, "", LINE_DEFAULT);
2547                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2548                         continue;
2549                 }
2550
2551                 key = get_key(req_info[i].request);
2552                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2553                         continue;
2554
2555                 add_line_text(view, buf, LINE_DEFAULT);
2556         }
2557
2558         return TRUE;
2559 }
2560
2561 static struct view_ops help_ops = {
2562         "line",
2563         help_open,
2564         NULL,
2565         pager_draw,
2566         pager_enter,
2567         pager_grep,
2568         pager_select,
2569 };
2570
2571
2572 /*
2573  * Tree backend
2574  */
2575
2576 /* Parse output from git-ls-tree(1):
2577  *
2578  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2579  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2580  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2581  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2582  */
2583
2584 #define SIZEOF_TREE_ATTR \
2585         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2586
2587 #define TREE_UP_FORMAT "040000 tree %s\t.."
2588
2589 static int
2590 tree_compare_entry(enum line_type type1, char *name1,
2591                    enum line_type type2, char *name2)
2592 {
2593         if (type1 != type2) {
2594                 if (type1 == LINE_TREE_DIR)
2595                         return -1;
2596                 return 1;
2597         }
2598
2599         return strcmp(name1, name2);
2600 }
2601
2602 static bool
2603 tree_read(struct view *view, char *text)
2604 {
2605         size_t textlen = text ? strlen(text) : 0;
2606         char buf[SIZEOF_STR];
2607         unsigned long pos;
2608         enum line_type type;
2609         bool first_read = view->lines == 0;
2610
2611         if (textlen <= SIZEOF_TREE_ATTR)
2612                 return FALSE;
2613
2614         type = text[STRING_SIZE("100644 ")] == 't'
2615              ? LINE_TREE_DIR : LINE_TREE_FILE;
2616
2617         if (first_read) {
2618                 /* Add path info line */
2619                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2620                     !realloc_lines(view, view->line_size + 1) ||
2621                     !add_line_text(view, buf, LINE_DEFAULT))
2622                         return FALSE;
2623
2624                 /* Insert "link" to parent directory. */
2625                 if (*opt_path) {
2626                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2627                             !realloc_lines(view, view->line_size + 1) ||
2628                             !add_line_text(view, buf, LINE_TREE_DIR))
2629                                 return FALSE;
2630                 }
2631         }
2632
2633         /* Strip the path part ... */
2634         if (*opt_path) {
2635                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2636                 size_t striplen = strlen(opt_path);
2637                 char *path = text + SIZEOF_TREE_ATTR;
2638
2639                 if (pathlen > striplen)
2640                         memmove(path, path + striplen,
2641                                 pathlen - striplen + 1);
2642         }
2643
2644         /* Skip "Directory ..." and ".." line. */
2645         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2646                 struct line *line = &view->line[pos];
2647                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2648                 char *path2 = text + SIZEOF_TREE_ATTR;
2649                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2650
2651                 if (cmp <= 0)
2652                         continue;
2653
2654                 text = strdup(text);
2655                 if (!text)
2656                         return FALSE;
2657
2658                 if (view->lines > pos)
2659                         memmove(&view->line[pos + 1], &view->line[pos],
2660                                 (view->lines - pos) * sizeof(*line));
2661
2662                 line = &view->line[pos];
2663                 line->data = text;
2664                 line->type = type;
2665                 view->lines++;
2666                 return TRUE;
2667         }
2668
2669         if (!add_line_text(view, text, type))
2670                 return FALSE;
2671
2672         /* Move the current line to the first tree entry. */
2673         if (first_read)
2674                 view->lineno++;
2675
2676         return TRUE;
2677 }
2678
2679 static bool
2680 tree_enter(struct view *view, struct line *line)
2681 {
2682         enum open_flags flags;
2683         enum request request;
2684
2685         switch (line->type) {
2686         case LINE_TREE_DIR:
2687                 /* Depending on whether it is a subdir or parent (updir?) link
2688                  * mangle the path buffer. */
2689                 if (line == &view->line[1] && *opt_path) {
2690                         size_t path_len = strlen(opt_path);
2691                         char *dirsep = opt_path + path_len - 1;
2692
2693                         while (dirsep > opt_path && dirsep[-1] != '/')
2694                                 dirsep--;
2695
2696                         dirsep[0] = 0;
2697
2698                 } else {
2699                         size_t pathlen = strlen(opt_path);
2700                         size_t origlen = pathlen;
2701                         char *data = line->data;
2702                         char *basename = data + SIZEOF_TREE_ATTR;
2703
2704                         if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2705                                 opt_path[origlen] = 0;
2706                                 return TRUE;
2707                         }
2708                 }
2709
2710                 /* Trees and subtrees share the same ID, so they are not not
2711                  * unique like blobs. */
2712                 flags = OPEN_RELOAD;
2713                 request = REQ_VIEW_TREE;
2714                 break;
2715
2716         case LINE_TREE_FILE:
2717                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2718                 request = REQ_VIEW_BLOB;
2719                 break;
2720
2721         default:
2722                 return TRUE;
2723         }
2724
2725         open_view(view, request, flags);
2726
2727         return TRUE;
2728 }
2729
2730 static void
2731 tree_select(struct view *view, struct line *line)
2732 {
2733         char *text = line->data + STRING_SIZE("100644 blob ");
2734
2735         if (line->type == LINE_TREE_FILE) {
2736                 string_copy_rev(ref_blob, text);
2737
2738         } else if (line->type != LINE_TREE_DIR) {
2739                 return;
2740         }
2741
2742         string_copy_rev(view->ref, text);
2743 }
2744
2745 static struct view_ops tree_ops = {
2746         "file",
2747         NULL,
2748         tree_read,
2749         pager_draw,
2750         tree_enter,
2751         pager_grep,
2752         tree_select,
2753 };
2754
2755 static bool
2756 blob_read(struct view *view, char *line)
2757 {
2758         return add_line_text(view, line, LINE_DEFAULT);
2759 }
2760
2761 static struct view_ops blob_ops = {
2762         "line",
2763         NULL,
2764         blob_read,
2765         pager_draw,
2766         pager_enter,
2767         pager_grep,
2768         pager_select,
2769 };
2770
2771
2772 /*
2773  * Status backend
2774  */
2775
2776 struct status {
2777         char status;
2778         struct {
2779                 mode_t mode;
2780                 char rev[SIZEOF_REV];
2781         } old;
2782         struct {
2783                 mode_t mode;
2784                 char rev[SIZEOF_REV];
2785         } new;
2786         char name[SIZEOF_STR];
2787 };
2788
2789 /* Get fields from the diff line:
2790  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2791  */
2792 static inline bool
2793 status_get_diff(struct status *file, char *buf, size_t bufsize)
2794 {
2795         char *old_mode = buf +  1;
2796         char *new_mode = buf +  8;
2797         char *old_rev  = buf + 15;
2798         char *new_rev  = buf + 56;
2799         char *status   = buf + 97;
2800
2801         if (bufsize != 99 ||
2802             old_mode[-1] != ':' ||
2803             new_mode[-1] != ' ' ||
2804             old_rev[-1]  != ' ' ||
2805             new_rev[-1]  != ' ' ||
2806             status[-1]   != ' ')
2807                 return FALSE;
2808
2809         file->status = *status;
2810
2811         string_copy_rev(file->old.rev, old_rev);
2812         string_copy_rev(file->new.rev, new_rev);
2813
2814         file->old.mode = strtoul(old_mode, NULL, 8);
2815         file->new.mode = strtoul(new_mode, NULL, 8);
2816
2817         file->name[0] = 0;
2818
2819         return TRUE;
2820 }
2821
2822 static bool
2823 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2824 {
2825         struct status *file = NULL;
2826         char buf[SIZEOF_STR * 4];
2827         size_t bufsize = 0;
2828         FILE *pipe;
2829
2830         pipe = popen(cmd, "r");
2831         if (!pipe)
2832                 return FALSE;
2833
2834         add_line_data(view, NULL, type);
2835
2836         while (!feof(pipe) && !ferror(pipe)) {
2837                 char *sep;
2838                 size_t readsize;
2839
2840                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
2841                 if (!readsize)
2842                         break;
2843                 bufsize += readsize;
2844
2845                 /* Process while we have NUL chars. */
2846                 while ((sep = memchr(buf, 0, bufsize))) {
2847                         size_t sepsize = sep - buf + 1;
2848
2849                         if (!file) {
2850                                 if (!realloc_lines(view, view->line_size + 1))
2851                                         goto error_out;
2852
2853                                 file = calloc(1, sizeof(*file));
2854                                 if (!file)
2855                                         goto error_out;
2856
2857                                 add_line_data(view, file, type);
2858                         }
2859
2860                         /* Parse diff info part. */
2861                         if (!diff) {
2862                                 file->status = '?';
2863
2864                         } else if (!file->status) {
2865                                 if (!status_get_diff(file, buf, sepsize))
2866                                         goto error_out;
2867
2868                                 bufsize -= sepsize;
2869                                 memmove(buf, sep + 1, bufsize);
2870
2871                                 sep = memchr(buf, 0, bufsize);
2872                                 if (!sep)
2873                                         break;
2874                                 sepsize = sep - buf + 1;
2875                         }
2876
2877                         /* git-ls-files just delivers a NUL separated
2878                          * list of file names similar to the second half
2879                          * of the git-diff-* output. */
2880                         string_ncopy(file->name, buf, sepsize);
2881                         bufsize -= sepsize;
2882                         memmove(buf, sep + 1, bufsize);
2883                         file = NULL;
2884                 }
2885         }
2886
2887         if (ferror(pipe)) {
2888 error_out:
2889                 pclose(pipe);
2890                 return FALSE;
2891         }
2892
2893         if (!view->line[view->lines - 1].data)
2894                 add_line_data(view, NULL, LINE_STAT_NONE);
2895
2896         pclose(pipe);
2897         return TRUE;
2898 }
2899
2900 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
2901 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
2902 #define STATUS_LIST_OTHER_CMD \
2903         "_git_exclude=$(git rev-parse --git-dir)/info/exclude;" \
2904         "test -f \"$_git_exclude\" && exclude=\"--exclude-from=$_git_exclude\";" \
2905         "git ls-files -z --others --exclude-per-directory=.gitignore \"$exclude\"" \
2906
2907 /* First parse staged info using git-diff-index(1), then parse unstaged
2908  * info using git-diff-files(1), and finally untracked files using
2909  * git-ls-files(1). */
2910 static bool
2911 status_open(struct view *view)
2912 {
2913         size_t i;
2914
2915         for (i = 0; i < view->lines; i++)
2916                 free(view->line[i].data);
2917         free(view->line);
2918         view->lines = view->line_size = 0;
2919         view->line = NULL;
2920
2921         if (!realloc_lines(view, view->line_size + 6))
2922                 return FALSE;
2923
2924         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
2925             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
2926             !status_run(view, STATUS_LIST_OTHER_CMD, FALSE, LINE_STAT_UNTRACKED))
2927                 return FALSE;
2928
2929         return TRUE;
2930 }
2931
2932 static bool
2933 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2934 {
2935         struct status *status = line->data;
2936
2937         wmove(view->win, lineno, 0);
2938
2939         if (selected) {
2940                 wattrset(view->win, get_line_attr(LINE_CURSOR));
2941                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
2942
2943         } else if (!status && line->type != LINE_STAT_NONE) {
2944                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
2945                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
2946
2947         } else {
2948                 wattrset(view->win, get_line_attr(line->type));
2949         }
2950
2951         if (!status) {
2952                 char *text;
2953
2954                 switch (line->type) {
2955                 case LINE_STAT_STAGED:
2956                         text = "Changes to be committed:";
2957                         break;
2958
2959                 case LINE_STAT_UNSTAGED:
2960                         text = "Changed but not updated:";
2961                         break;
2962
2963                 case LINE_STAT_UNTRACKED:
2964                         text = "Untracked files:";
2965                         break;
2966
2967                 case LINE_STAT_NONE:
2968                         text = "    (no files)";
2969                         break;
2970
2971                 default:
2972                         return FALSE;
2973                 }
2974
2975                 waddstr(view->win, text);
2976                 return TRUE;
2977         }
2978
2979         waddch(view->win, status->status);
2980         if (!selected)
2981                 wattrset(view->win, A_NORMAL);
2982         wmove(view->win, lineno, 4);
2983         waddstr(view->win, status->name);
2984
2985         return TRUE;
2986 }
2987
2988 static bool
2989 status_enter(struct view *view, struct line *line)
2990 {
2991         struct status *status = line->data;
2992         const char *cmd;
2993         char buf[SIZEOF_STR];
2994         size_t bufsize = 0;
2995         size_t written = 0;
2996         FILE *pipe;
2997
2998         if (!status)
2999                 return TRUE;
3000
3001         switch (line->type) {
3002         case LINE_STAT_STAGED:
3003                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3004                                         status->old.mode,
3005                                         status->old.rev,
3006                                         status->name, 0))
3007                         return FALSE;
3008                 cmd = "git update-index -z --index-info";
3009                 break;
3010
3011         case LINE_STAT_UNSTAGED:
3012         case LINE_STAT_UNTRACKED:
3013                 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3014                         return FALSE;
3015                 cmd = "git update-index -z --add --remove --stdin";
3016                 break;
3017
3018         default:
3019                 die("w00t");
3020         }
3021
3022         pipe = popen(cmd, "w");
3023         if (!pipe)
3024                 return FALSE;
3025
3026         while (!ferror(pipe) && written < bufsize) {
3027                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3028         }
3029
3030         pclose(pipe);
3031
3032         if (written != bufsize)
3033                 return FALSE;
3034
3035         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3036         return TRUE;
3037 }
3038
3039 static void
3040 status_select(struct view *view, struct line *line)
3041 {
3042         char *text;
3043
3044         switch (line->type) {
3045         case LINE_STAT_STAGED:
3046                 text = "Press Enter to unstage file for commit";
3047                 break;
3048
3049         case LINE_STAT_UNSTAGED:
3050                 text = "Press Enter to stage file for commit  ";
3051                 break;
3052
3053         case LINE_STAT_UNTRACKED:
3054                 text = "Press Enter to stage file for addition";
3055                 break;
3056
3057         case LINE_STAT_NONE:
3058                 return;
3059
3060         default:
3061                 die("w00t");
3062         }
3063
3064         string_ncopy(view->ref, text, strlen(text));
3065 }
3066
3067 static bool
3068 status_grep(struct view *view, struct line *line)
3069 {
3070         struct status *status = line->data;
3071         enum { S_STATUS, S_NAME, S_END } state;
3072         char buf[2] = "?";
3073         regmatch_t pmatch;
3074
3075         if (!status)
3076                 return FALSE;
3077
3078         for (state = S_STATUS; state < S_END; state++) {
3079                 char *text;
3080
3081                 switch (state) {
3082                 case S_NAME:    text = status->name;    break;
3083                 case S_STATUS:
3084                         buf[0] = status->status;
3085                         text = buf;
3086                         break;
3087
3088                 default:
3089                         return FALSE;
3090                 }
3091
3092                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3093                         return TRUE;
3094         }
3095
3096         return FALSE;
3097 }
3098
3099 static struct view_ops status_ops = {
3100         "file",
3101         status_open,
3102         NULL,
3103         status_draw,
3104         status_enter,
3105         status_grep,
3106         status_select,
3107 };
3108
3109
3110 /*
3111  * Revision graph
3112  */
3113
3114 struct commit {
3115         char id[SIZEOF_REV];            /* SHA1 ID. */
3116         char title[128];                /* First line of the commit message. */
3117         char author[75];                /* Author of the commit. */
3118         struct tm time;                 /* Date from the author ident. */
3119         struct ref **refs;              /* Repository references. */
3120         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3121         size_t graph_size;              /* The width of the graph array. */
3122 };
3123
3124 /* Size of rev graph with no  "padding" columns */
3125 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3126
3127 struct rev_graph {
3128         struct rev_graph *prev, *next, *parents;
3129         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3130         size_t size;
3131         struct commit *commit;
3132         size_t pos;
3133 };
3134
3135 /* Parents of the commit being visualized. */
3136 static struct rev_graph graph_parents[4];
3137
3138 /* The current stack of revisions on the graph. */
3139 static struct rev_graph graph_stacks[4] = {
3140         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3141         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3142         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3143         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3144 };
3145
3146 static inline bool
3147 graph_parent_is_merge(struct rev_graph *graph)
3148 {
3149         return graph->parents->size > 1;
3150 }
3151
3152 static inline void
3153 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3154 {
3155         struct commit *commit = graph->commit;
3156
3157         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3158                 commit->graph[commit->graph_size++] = symbol;
3159 }
3160
3161 static void
3162 done_rev_graph(struct rev_graph *graph)
3163 {
3164         if (graph_parent_is_merge(graph) &&
3165             graph->pos < graph->size - 1 &&
3166             graph->next->size == graph->size + graph->parents->size - 1) {
3167                 size_t i = graph->pos + graph->parents->size - 1;
3168
3169                 graph->commit->graph_size = i * 2;
3170                 while (i < graph->next->size - 1) {
3171                         append_to_rev_graph(graph, ' ');
3172                         append_to_rev_graph(graph, '\\');
3173                         i++;
3174                 }
3175         }
3176
3177         graph->size = graph->pos = 0;
3178         graph->commit = NULL;
3179         memset(graph->parents, 0, sizeof(*graph->parents));
3180 }
3181
3182 static void
3183 push_rev_graph(struct rev_graph *graph, char *parent)
3184 {
3185         int i;
3186
3187         /* "Collapse" duplicate parents lines.
3188          *
3189          * FIXME: This needs to also update update the drawn graph but
3190          * for now it just serves as a method for pruning graph lines. */
3191         for (i = 0; i < graph->size; i++)
3192                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3193                         return;
3194
3195         if (graph->size < SIZEOF_REVITEMS) {
3196                 string_ncopy(graph->rev[graph->size++], parent, SIZEOF_REV);
3197         }
3198 }
3199
3200 static chtype
3201 get_rev_graph_symbol(struct rev_graph *graph)
3202 {
3203         chtype symbol;
3204
3205         if (graph->parents->size == 0)
3206                 symbol = REVGRAPH_INIT;
3207         else if (graph_parent_is_merge(graph))
3208                 symbol = REVGRAPH_MERGE;
3209         else if (graph->pos >= graph->size)
3210                 symbol = REVGRAPH_BRANCH;
3211         else
3212                 symbol = REVGRAPH_COMMIT;
3213
3214         return symbol;
3215 }
3216
3217 static void
3218 draw_rev_graph(struct rev_graph *graph)
3219 {
3220         struct rev_filler {
3221                 chtype separator, line;
3222         };
3223         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3224         static struct rev_filler fillers[] = {
3225                 { ' ',  REVGRAPH_LINE },
3226                 { '`',  '.' },
3227                 { '\'', ' ' },
3228                 { '/',  ' ' },
3229         };
3230         chtype symbol = get_rev_graph_symbol(graph);
3231         struct rev_filler *filler;
3232         size_t i;
3233
3234         filler = &fillers[DEFAULT];
3235
3236         for (i = 0; i < graph->pos; i++) {
3237                 append_to_rev_graph(graph, filler->line);
3238                 if (graph_parent_is_merge(graph->prev) &&
3239                     graph->prev->pos == i)
3240                         filler = &fillers[RSHARP];
3241
3242                 append_to_rev_graph(graph, filler->separator);
3243         }
3244
3245         /* Place the symbol for this revision. */
3246         append_to_rev_graph(graph, symbol);
3247
3248         if (graph->prev->size > graph->size)
3249                 filler = &fillers[RDIAG];
3250         else
3251                 filler = &fillers[DEFAULT];
3252
3253         i++;
3254
3255         for (; i < graph->size; i++) {
3256                 append_to_rev_graph(graph, filler->separator);
3257                 append_to_rev_graph(graph, filler->line);
3258                 if (graph_parent_is_merge(graph->prev) &&
3259                     i < graph->prev->pos + graph->parents->size)
3260                         filler = &fillers[RSHARP];
3261                 if (graph->prev->size > graph->size)
3262                         filler = &fillers[LDIAG];
3263         }
3264
3265         if (graph->prev->size > graph->size) {
3266                 append_to_rev_graph(graph, filler->separator);
3267                 if (filler->line != ' ')
3268                         append_to_rev_graph(graph, filler->line);
3269         }
3270 }
3271
3272 /* Prepare the next rev graph */
3273 static void
3274 prepare_rev_graph(struct rev_graph *graph)
3275 {
3276         size_t i;
3277
3278         /* First, traverse all lines of revisions up to the active one. */
3279         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3280                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3281                         break;
3282
3283                 push_rev_graph(graph->next, graph->rev[graph->pos]);
3284         }
3285
3286         /* Interleave the new revision parent(s). */
3287         for (i = 0; i < graph->parents->size; i++)
3288                 push_rev_graph(graph->next, graph->parents->rev[i]);
3289
3290         /* Lastly, put any remaining revisions. */
3291         for (i = graph->pos + 1; i < graph->size; i++)
3292                 push_rev_graph(graph->next, graph->rev[i]);
3293 }
3294
3295 static void
3296 update_rev_graph(struct rev_graph *graph)
3297 {
3298         /* If this is the finalizing update ... */
3299         if (graph->commit)
3300                 prepare_rev_graph(graph);
3301
3302         /* Graph visualization needs a one rev look-ahead,
3303          * so the first update doesn't visualize anything. */
3304         if (!graph->prev->commit)
3305                 return;
3306
3307         draw_rev_graph(graph->prev);
3308         done_rev_graph(graph->prev->prev);
3309 }
3310
3311
3312 /*
3313  * Main view backend
3314  */
3315
3316 static bool
3317 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3318 {
3319         char buf[DATE_COLS + 1];
3320         struct commit *commit = line->data;
3321         enum line_type type;
3322         int col = 0;
3323         size_t timelen;
3324         size_t authorlen;
3325         int trimmed = 1;
3326
3327         if (!*commit->author)
3328                 return FALSE;
3329
3330         wmove(view->win, lineno, col);
3331
3332         if (selected) {
3333                 type = LINE_CURSOR;
3334                 wattrset(view->win, get_line_attr(type));
3335                 wchgat(view->win, -1, 0, type, NULL);
3336
3337         } else {
3338                 type = LINE_MAIN_COMMIT;
3339                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3340         }
3341
3342         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3343         waddnstr(view->win, buf, timelen);
3344         waddstr(view->win, " ");
3345
3346         col += DATE_COLS;
3347         wmove(view->win, lineno, col);
3348         if (type != LINE_CURSOR)
3349                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3350
3351         if (opt_utf8) {
3352                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3353         } else {
3354                 authorlen = strlen(commit->author);
3355                 if (authorlen > AUTHOR_COLS - 2) {
3356                         authorlen = AUTHOR_COLS - 2;
3357                         trimmed = 1;
3358                 }
3359         }
3360
3361         if (trimmed) {
3362                 waddnstr(view->win, commit->author, authorlen);
3363                 if (type != LINE_CURSOR)
3364                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3365                 waddch(view->win, '~');
3366         } else {
3367                 waddstr(view->win, commit->author);
3368         }
3369
3370         col += AUTHOR_COLS;
3371         if (type != LINE_CURSOR)
3372                 wattrset(view->win, A_NORMAL);
3373
3374         if (opt_rev_graph && commit->graph_size) {
3375                 size_t i;
3376
3377                 wmove(view->win, lineno, col);
3378                 /* Using waddch() instead of waddnstr() ensures that
3379                  * they'll be rendered correctly for the cursor line. */
3380                 for (i = 0; i < commit->graph_size; i++)
3381                         waddch(view->win, commit->graph[i]);
3382
3383                 waddch(view->win, ' ');
3384                 col += commit->graph_size + 1;
3385         }
3386
3387         wmove(view->win, lineno, col);
3388
3389         if (commit->refs) {
3390                 size_t i = 0;
3391
3392                 do {
3393                         if (type == LINE_CURSOR)
3394                                 ;
3395                         else if (commit->refs[i]->tag)
3396                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3397                         else if (commit->refs[i]->remote)
3398                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3399                         else
3400                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3401                         waddstr(view->win, "[");
3402                         waddstr(view->win, commit->refs[i]->name);
3403                         waddstr(view->win, "]");
3404                         if (type != LINE_CURSOR)
3405                                 wattrset(view->win, A_NORMAL);
3406                         waddstr(view->win, " ");
3407                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3408                 } while (commit->refs[i++]->next);
3409         }
3410
3411         if (type != LINE_CURSOR)
3412                 wattrset(view->win, get_line_attr(type));
3413
3414         {
3415                 int titlelen = strlen(commit->title);
3416
3417                 if (col + titlelen > view->width)
3418                         titlelen = view->width - col;
3419
3420                 waddnstr(view->win, commit->title, titlelen);
3421         }
3422
3423         return TRUE;
3424 }
3425
3426 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3427 static bool
3428 main_read(struct view *view, char *line)
3429 {
3430         static struct rev_graph *graph = graph_stacks;
3431         enum line_type type;
3432         struct commit *commit;
3433
3434         if (!line) {
3435                 update_rev_graph(graph);
3436                 return TRUE;
3437         }
3438
3439         type = get_line_type(line);
3440         if (type == LINE_COMMIT) {
3441                 commit = calloc(1, sizeof(struct commit));
3442                 if (!commit)
3443                         return FALSE;
3444
3445                 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3446                 commit->refs = get_refs(commit->id);
3447                 graph->commit = commit;
3448                 add_line_data(view, commit, LINE_MAIN_COMMIT);
3449                 return TRUE;
3450         }
3451
3452         if (!view->lines)
3453                 return TRUE;
3454         commit = view->line[view->lines - 1].data;
3455
3456         switch (type) {
3457         case LINE_PARENT:
3458                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3459                 break;
3460
3461         case LINE_AUTHOR:
3462         {
3463                 /* Parse author lines where the name may be empty:
3464                  *      author  <email@address.tld> 1138474660 +0100
3465                  */
3466                 char *ident = line + STRING_SIZE("author ");
3467                 char *nameend = strchr(ident, '<');
3468                 char *emailend = strchr(ident, '>');
3469
3470                 if (!nameend || !emailend)
3471                         break;
3472
3473                 update_rev_graph(graph);
3474                 graph = graph->next;
3475
3476                 *nameend = *emailend = 0;
3477                 ident = chomp_string(ident);
3478                 if (!*ident) {
3479                         ident = chomp_string(nameend + 1);
3480                         if (!*ident)
3481                                 ident = "Unknown";
3482                 }
3483
3484                 string_copy(commit->author, ident);
3485
3486                 /* Parse epoch and timezone */
3487                 if (emailend[1] == ' ') {
3488                         char *secs = emailend + 2;
3489                         char *zone = strchr(secs, ' ');
3490                         time_t time = (time_t) atol(secs);
3491
3492                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3493                                 long tz;
3494
3495                                 zone++;
3496                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
3497                                 tz += ('0' - zone[2]) * 60 * 60;
3498                                 tz += ('0' - zone[3]) * 60;
3499                                 tz += ('0' - zone[4]) * 60;
3500
3501                                 if (zone[0] == '-')
3502                                         tz = -tz;
3503
3504                                 time -= tz;
3505                         }
3506
3507                         gmtime_r(&time, &commit->time);
3508                 }
3509                 break;
3510         }
3511         default:
3512                 /* Fill in the commit title if it has not already been set. */
3513                 if (commit->title[0])
3514                         break;
3515
3516                 /* Require titles to start with a non-space character at the
3517                  * offset used by git log. */
3518                 if (strncmp(line, "    ", 4))
3519                         break;
3520                 line += 4;
3521                 /* Well, if the title starts with a whitespace character,
3522                  * try to be forgiving.  Otherwise we end up with no title. */
3523                 while (isspace(*line))
3524                         line++;
3525                 if (*line == '\0')
3526                         break;
3527                 /* FIXME: More graceful handling of titles; append "..." to
3528                  * shortened titles, etc. */
3529
3530                 string_copy(commit->title, line);
3531         }
3532
3533         return TRUE;
3534 }
3535
3536 static bool
3537 main_enter(struct view *view, struct line *line)
3538 {
3539         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3540
3541         open_view(view, REQ_VIEW_DIFF, flags);
3542         return TRUE;
3543 }
3544
3545 static bool
3546 main_grep(struct view *view, struct line *line)
3547 {
3548         struct commit *commit = line->data;
3549         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3550         char buf[DATE_COLS + 1];
3551         regmatch_t pmatch;
3552
3553         for (state = S_TITLE; state < S_END; state++) {
3554                 char *text;
3555
3556                 switch (state) {
3557                 case S_TITLE:   text = commit->title;   break;
3558                 case S_AUTHOR:  text = commit->author;  break;
3559                 case S_DATE:
3560                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3561                                 continue;
3562                         text = buf;
3563                         break;
3564
3565                 default:
3566                         return FALSE;
3567                 }
3568
3569                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3570                         return TRUE;
3571         }
3572
3573         return FALSE;
3574 }
3575
3576 static void
3577 main_select(struct view *view, struct line *line)
3578 {
3579         struct commit *commit = line->data;
3580
3581         string_copy_rev(view->ref, commit->id);
3582         string_copy_rev(ref_commit, view->ref);
3583 }
3584
3585 static struct view_ops main_ops = {
3586         "commit",
3587         NULL,
3588         main_read,
3589         main_draw,
3590         main_enter,
3591         main_grep,
3592         main_select,
3593 };
3594
3595
3596 /*
3597  * Unicode / UTF-8 handling
3598  *
3599  * NOTE: Much of the following code for dealing with unicode is derived from
3600  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3601  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3602  */
3603
3604 /* I've (over)annotated a lot of code snippets because I am not entirely
3605  * confident that the approach taken by this small UTF-8 interface is correct.
3606  * --jonas */
3607
3608 static inline int
3609 unicode_width(unsigned long c)
3610 {
3611         if (c >= 0x1100 &&
3612            (c <= 0x115f                         /* Hangul Jamo */
3613             || c == 0x2329
3614             || c == 0x232a
3615             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
3616                                                 /* CJK ... Yi */
3617             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
3618             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
3619             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
3620             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
3621             || (c >= 0xffe0  && c <= 0xffe6)
3622             || (c >= 0x20000 && c <= 0x2fffd)
3623             || (c >= 0x30000 && c <= 0x3fffd)))
3624                 return 2;
3625
3626         return 1;
3627 }
3628
3629 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3630  * Illegal bytes are set one. */
3631 static const unsigned char utf8_bytes[256] = {
3632         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,
3633         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,
3634         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,
3635         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,
3636         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,
3637         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,
3638         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,
3639         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,
3640 };
3641
3642 /* Decode UTF-8 multi-byte representation into a unicode character. */
3643 static inline unsigned long
3644 utf8_to_unicode(const char *string, size_t length)
3645 {
3646         unsigned long unicode;
3647
3648         switch (length) {
3649         case 1:
3650                 unicode  =   string[0];
3651                 break;
3652         case 2:
3653                 unicode  =  (string[0] & 0x1f) << 6;
3654                 unicode +=  (string[1] & 0x3f);
3655                 break;
3656         case 3:
3657                 unicode  =  (string[0] & 0x0f) << 12;
3658                 unicode += ((string[1] & 0x3f) << 6);
3659                 unicode +=  (string[2] & 0x3f);
3660                 break;
3661         case 4:
3662                 unicode  =  (string[0] & 0x0f) << 18;
3663                 unicode += ((string[1] & 0x3f) << 12);
3664                 unicode += ((string[2] & 0x3f) << 6);
3665                 unicode +=  (string[3] & 0x3f);
3666                 break;
3667         case 5:
3668                 unicode  =  (string[0] & 0x0f) << 24;
3669                 unicode += ((string[1] & 0x3f) << 18);
3670                 unicode += ((string[2] & 0x3f) << 12);
3671                 unicode += ((string[3] & 0x3f) << 6);
3672                 unicode +=  (string[4] & 0x3f);
3673                 break;
3674         case 6:
3675                 unicode  =  (string[0] & 0x01) << 30;
3676                 unicode += ((string[1] & 0x3f) << 24);
3677                 unicode += ((string[2] & 0x3f) << 18);
3678                 unicode += ((string[3] & 0x3f) << 12);
3679                 unicode += ((string[4] & 0x3f) << 6);
3680                 unicode +=  (string[5] & 0x3f);
3681                 break;
3682         default:
3683                 die("Invalid unicode length");
3684         }
3685
3686         /* Invalid characters could return the special 0xfffd value but NUL
3687          * should be just as good. */
3688         return unicode > 0xffff ? 0 : unicode;
3689 }
3690
3691 /* Calculates how much of string can be shown within the given maximum width
3692  * and sets trimmed parameter to non-zero value if all of string could not be
3693  * shown.
3694  *
3695  * Additionally, adds to coloffset how many many columns to move to align with
3696  * the expected position. Takes into account how multi-byte and double-width
3697  * characters will effect the cursor position.
3698  *
3699  * Returns the number of bytes to output from string to satisfy max_width. */
3700 static size_t
3701 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3702 {
3703         const char *start = string;
3704         const char *end = strchr(string, '\0');
3705         size_t mbwidth = 0;
3706         size_t width = 0;
3707
3708         *trimmed = 0;
3709
3710         while (string < end) {
3711                 int c = *(unsigned char *) string;
3712                 unsigned char bytes = utf8_bytes[c];
3713                 size_t ucwidth;
3714                 unsigned long unicode;
3715
3716                 if (string + bytes > end)
3717                         break;
3718
3719                 /* Change representation to figure out whether
3720                  * it is a single- or double-width character. */
3721
3722                 unicode = utf8_to_unicode(string, bytes);
3723                 /* FIXME: Graceful handling of invalid unicode character. */
3724                 if (!unicode)
3725                         break;
3726
3727                 ucwidth = unicode_width(unicode);
3728                 width  += ucwidth;
3729                 if (width > max_width) {
3730                         *trimmed = 1;
3731                         break;
3732                 }
3733
3734                 /* The column offset collects the differences between the
3735                  * number of bytes encoding a character and the number of
3736                  * columns will be used for rendering said character.
3737                  *
3738                  * So if some character A is encoded in 2 bytes, but will be
3739                  * represented on the screen using only 1 byte this will and up
3740                  * adding 1 to the multi-byte column offset.
3741                  *
3742                  * Assumes that no double-width character can be encoding in
3743                  * less than two bytes. */
3744                 if (bytes > ucwidth)
3745                         mbwidth += bytes - ucwidth;
3746
3747                 string  += bytes;
3748         }
3749
3750         *coloffset += mbwidth;
3751
3752         return string - start;
3753 }
3754
3755
3756 /*
3757  * Status management
3758  */
3759
3760 /* Whether or not the curses interface has been initialized. */
3761 static bool cursed = FALSE;
3762
3763 /* The status window is used for polling keystrokes. */
3764 static WINDOW *status_win;
3765
3766 static bool status_empty = TRUE;
3767
3768 /* Update status and title window. */
3769 static void
3770 report(const char *msg, ...)
3771 {
3772         struct view *view = display[current_view];
3773
3774         if (input_mode)
3775                 return;
3776
3777         if (!status_empty || *msg) {
3778                 va_list args;
3779
3780                 va_start(args, msg);
3781
3782                 wmove(status_win, 0, 0);
3783                 if (*msg) {
3784                         vwprintw(status_win, msg, args);
3785                         status_empty = FALSE;
3786                 } else {
3787                         status_empty = TRUE;
3788                 }
3789                 wclrtoeol(status_win);
3790                 wrefresh(status_win);
3791
3792                 va_end(args);
3793         }
3794
3795         update_view_title(view);
3796         update_display_cursor(view);
3797 }
3798
3799 /* Controls when nodelay should be in effect when polling user input. */
3800 static void
3801 set_nonblocking_input(bool loading)
3802 {
3803         static unsigned int loading_views;
3804
3805         if ((loading == FALSE && loading_views-- == 1) ||
3806             (loading == TRUE  && loading_views++ == 0))
3807                 nodelay(status_win, loading);
3808 }
3809
3810 static void
3811 init_display(void)
3812 {
3813         int x, y;
3814
3815         /* Initialize the curses library */
3816         if (isatty(STDIN_FILENO)) {
3817                 cursed = !!initscr();
3818         } else {
3819                 /* Leave stdin and stdout alone when acting as a pager. */
3820                 FILE *io = fopen("/dev/tty", "r+");
3821
3822                 if (!io)
3823                         die("Failed to open /dev/tty");
3824                 cursed = !!newterm(NULL, io, io);
3825         }
3826
3827         if (!cursed)
3828                 die("Failed to initialize curses");
3829
3830         nonl();         /* Tell curses not to do NL->CR/NL on output */
3831         cbreak();       /* Take input chars one at a time, no wait for \n */
3832         noecho();       /* Don't echo input */
3833         leaveok(stdscr, TRUE);
3834
3835         if (has_colors())
3836                 init_colors();
3837
3838         getmaxyx(stdscr, y, x);
3839         status_win = newwin(1, 0, y - 1, 0);
3840         if (!status_win)
3841                 die("Failed to create status window");
3842
3843         /* Enable keyboard mapping */
3844         keypad(status_win, TRUE);
3845         wbkgdset(status_win, get_line_attr(LINE_STATUS));
3846 }
3847
3848 static char *
3849 read_prompt(const char *prompt)
3850 {
3851         enum { READING, STOP, CANCEL } status = READING;
3852         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3853         int pos = 0;
3854
3855         while (status == READING) {
3856                 struct view *view;
3857                 int i, key;
3858
3859                 input_mode = TRUE;
3860
3861                 foreach_view (view, i)
3862                         update_view(view);
3863
3864                 input_mode = FALSE;
3865
3866                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
3867                 wclrtoeol(status_win);
3868
3869                 /* Refresh, accept single keystroke of input */
3870                 key = wgetch(status_win);
3871                 switch (key) {
3872                 case KEY_RETURN:
3873                 case KEY_ENTER:
3874                 case '\n':
3875                         status = pos ? STOP : CANCEL;
3876                         break;
3877
3878                 case KEY_BACKSPACE:
3879                         if (pos > 0)
3880                                 pos--;
3881                         else
3882                                 status = CANCEL;
3883                         break;
3884
3885                 case KEY_ESC:
3886                         status = CANCEL;
3887                         break;
3888
3889                 case ERR:
3890                         break;
3891
3892                 default:
3893                         if (pos >= sizeof(buf)) {
3894                                 report("Input string too long");
3895                                 return NULL;
3896                         }
3897
3898                         if (isprint(key))
3899                                 buf[pos++] = (char) key;
3900                 }
3901         }
3902
3903         /* Clear the status window */
3904         status_empty = FALSE;
3905         report("");
3906
3907         if (status == CANCEL)
3908                 return NULL;
3909
3910         buf[pos++] = 0;
3911
3912         return buf;
3913 }
3914
3915 /*
3916  * Repository references
3917  */
3918
3919 static struct ref *refs;
3920 static size_t refs_size;
3921
3922 /* Id <-> ref store */
3923 static struct ref ***id_refs;
3924 static size_t id_refs_size;
3925
3926 static struct ref **
3927 get_refs(char *id)
3928 {
3929         struct ref ***tmp_id_refs;
3930         struct ref **ref_list = NULL;
3931         size_t ref_list_size = 0;
3932         size_t i;
3933
3934         for (i = 0; i < id_refs_size; i++)
3935                 if (!strcmp(id, id_refs[i][0]->id))
3936                         return id_refs[i];
3937
3938         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3939         if (!tmp_id_refs)
3940                 return NULL;
3941
3942         id_refs = tmp_id_refs;
3943
3944         for (i = 0; i < refs_size; i++) {
3945                 struct ref **tmp;
3946
3947                 if (strcmp(id, refs[i].id))
3948                         continue;
3949
3950                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3951                 if (!tmp) {
3952                         if (ref_list)
3953                                 free(ref_list);
3954                         return NULL;
3955                 }
3956
3957                 ref_list = tmp;
3958                 if (ref_list_size > 0)
3959                         ref_list[ref_list_size - 1]->next = 1;
3960                 ref_list[ref_list_size] = &refs[i];
3961
3962                 /* XXX: The properties of the commit chains ensures that we can
3963                  * safely modify the shared ref. The repo references will
3964                  * always be similar for the same id. */
3965                 ref_list[ref_list_size]->next = 0;
3966                 ref_list_size++;
3967         }
3968
3969         if (ref_list)
3970                 id_refs[id_refs_size++] = ref_list;
3971
3972         return ref_list;
3973 }
3974
3975 static int
3976 read_ref(char *id, int idlen, char *name, int namelen)
3977 {
3978         struct ref *ref;
3979         bool tag = FALSE;
3980         bool remote = FALSE;
3981
3982         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3983                 /* Commits referenced by tags has "^{}" appended. */
3984                 if (name[namelen - 1] != '}')
3985                         return OK;
3986
3987                 while (namelen > 0 && name[namelen] != '^')
3988                         namelen--;
3989
3990                 tag = TRUE;
3991                 namelen -= STRING_SIZE("refs/tags/");
3992                 name    += STRING_SIZE("refs/tags/");
3993
3994         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
3995                 remote = TRUE;
3996                 namelen -= STRING_SIZE("refs/remotes/");
3997                 name    += STRING_SIZE("refs/remotes/");
3998
3999         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4000                 namelen -= STRING_SIZE("refs/heads/");
4001                 name    += STRING_SIZE("refs/heads/");
4002
4003         } else if (!strcmp(name, "HEAD")) {
4004                 return OK;
4005         }
4006
4007         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4008         if (!refs)
4009                 return ERR;
4010
4011         ref = &refs[refs_size++];
4012         ref->name = malloc(namelen + 1);
4013         if (!ref->name)
4014                 return ERR;
4015
4016         strncpy(ref->name, name, namelen);
4017         ref->name[namelen] = 0;
4018         ref->tag = tag;
4019         ref->remote = remote;
4020         string_copy_rev(ref->id, id);
4021
4022         return OK;
4023 }
4024
4025 static int
4026 load_refs(void)
4027 {
4028         const char *cmd_env = getenv("TIG_LS_REMOTE");
4029         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4030
4031         return read_properties(popen(cmd, "r"), "\t", read_ref);
4032 }
4033
4034 static int
4035 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
4036 {
4037         if (!strcmp(name, "i18n.commitencoding"))
4038                 string_copy(opt_encoding, value);
4039
4040         return OK;
4041 }
4042
4043 static int
4044 load_repo_config(void)
4045 {
4046         return read_properties(popen("git repo-config --list", "r"),
4047                                "=", read_repo_config_option);
4048 }
4049
4050 static int
4051 read_properties(FILE *pipe, const char *separators,
4052                 int (*read_property)(char *, int, char *, int))
4053 {
4054         char buffer[BUFSIZ];
4055         char *name;
4056         int state = OK;
4057
4058         if (!pipe)
4059                 return ERR;
4060
4061         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4062                 char *value;
4063                 size_t namelen;
4064                 size_t valuelen;
4065
4066                 name = chomp_string(name);
4067                 namelen = strcspn(name, separators);
4068
4069                 if (name[namelen]) {
4070                         name[namelen] = 0;
4071                         value = chomp_string(name + namelen + 1);
4072                         valuelen = strlen(value);
4073
4074                 } else {
4075                         value = "";
4076                         valuelen = 0;
4077                 }
4078
4079                 state = read_property(name, namelen, value, valuelen);
4080         }
4081
4082         if (state != ERR && ferror(pipe))
4083                 state = ERR;
4084
4085         pclose(pipe);
4086
4087         return state;
4088 }
4089
4090
4091 /*
4092  * Main
4093  */
4094
4095 static void __NORETURN
4096 quit(int sig)
4097 {
4098         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4099         if (cursed)
4100                 endwin();
4101         exit(0);
4102 }
4103
4104 static void __NORETURN
4105 die(const char *err, ...)
4106 {
4107         va_list args;
4108
4109         endwin();
4110
4111         va_start(args, err);
4112         fputs("tig: ", stderr);
4113         vfprintf(stderr, err, args);
4114         fputs("\n", stderr);
4115         va_end(args);
4116
4117         exit(1);
4118 }
4119
4120 int
4121 main(int argc, char *argv[])
4122 {
4123         struct view *view;
4124         enum request request;
4125         size_t i;
4126
4127         signal(SIGINT, quit);
4128
4129         if (setlocale(LC_ALL, "")) {
4130                 string_copy(opt_codeset, nl_langinfo(CODESET));
4131         }
4132
4133         if (load_options() == ERR)
4134                 die("Failed to load user config.");
4135
4136         /* Load the repo config file so options can be overwritten from
4137          * the command line.  */
4138         if (load_repo_config() == ERR)
4139                 die("Failed to load repo config.");
4140
4141         if (!parse_options(argc, argv))
4142                 return 0;
4143
4144         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4145                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4146                 if (opt_iconv == ICONV_NONE)
4147                         die("Failed to initialize character set conversion");
4148         }
4149
4150         if (load_refs() == ERR)
4151                 die("Failed to load refs.");
4152
4153         /* Require a git repository unless when running in pager mode. */
4154         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
4155                 die("Not a git repository");
4156
4157         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4158                 view->cmd_env = getenv(view->cmd_env);
4159
4160         request = opt_request;
4161
4162         init_display();
4163
4164         while (view_driver(display[current_view], request)) {
4165                 int key;
4166                 int i;
4167
4168                 foreach_view (view, i)
4169                         update_view(view);
4170
4171                 /* Refresh, accept single keystroke of input */
4172                 key = wgetch(status_win);
4173
4174                 /* wgetch() with nodelay() enabled returns ERR when there's no
4175                  * input. */
4176                 if (key == ERR) {
4177                         request = REQ_NONE;
4178                         continue;
4179                 }
4180
4181                 request = get_keybinding(display[current_view]->keymap, key);
4182
4183                 /* Some low-level request handling. This keeps access to
4184                  * status_win restricted. */
4185                 switch (request) {
4186                 case REQ_PROMPT:
4187                 {
4188                         char *cmd = read_prompt(":");
4189
4190                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4191                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4192                                         opt_request = REQ_VIEW_DIFF;
4193                                 } else {
4194                                         opt_request = REQ_VIEW_PAGER;
4195                                 }
4196                                 break;
4197                         }
4198
4199                         request = REQ_NONE;
4200                         break;
4201                 }
4202                 case REQ_SEARCH:
4203                 case REQ_SEARCH_BACK:
4204                 {
4205                         const char *prompt = request == REQ_SEARCH
4206                                            ? "/" : "?";
4207                         char *search = read_prompt(prompt);
4208
4209                         if (search)
4210                                 string_copy(opt_search, search);
4211                         else
4212                                 request = REQ_NONE;
4213                         break;
4214                 }
4215                 case REQ_SCREEN_RESIZE:
4216                 {
4217                         int height, width;
4218
4219                         getmaxyx(stdscr, height, width);
4220
4221                         /* Resize the status view and let the view driver take
4222                          * care of resizing the displayed views. */
4223                         wresize(status_win, 1, width);
4224                         mvwin(status_win, height - 1, 0);
4225                         wrefresh(status_win);
4226                         break;
4227                 }
4228                 default:
4229                         break;
4230                 }
4231         }
4232
4233         quit(0);
4234
4235         return 0;
4236 }