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