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