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