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