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