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