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