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