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