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