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