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