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