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