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