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