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