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