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