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