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_(PROMPT, "Bring up the prompt"), \
347 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
348 REQ_(SCREEN_RESIZE, "Resize the screen"), \
349 REQ_(SHOW_VERSION, "Show version information"), \
350 REQ_(STOP_LOADING, "Stop all loading views"), \
351 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
352 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
353 REQ_(STATUS_UPDATE, "Update file status"), \
354 REQ_(STATUS_MERGE, "Merge file using external tool"), \
355 REQ_(EDIT, "Open in editor"), \
356 REQ_(CHERRY_PICK, "Cherry-pick commit to current branch"), \
357 REQ_(NONE, "Do nothing")
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
369 #undef REQ_GROUP
370 #undef REQ_
371 };
373 struct request_info {
374 enum request request;
375 char *name;
376 int namelen;
377 char *help;
378 };
380 static struct request_info req_info[] = {
381 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
382 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
383 REQ_INFO
384 #undef REQ_GROUP
385 #undef REQ_
386 };
388 static enum request
389 get_request(const char *name)
390 {
391 int namelen = strlen(name);
392 int i;
394 for (i = 0; i < ARRAY_SIZE(req_info); i++)
395 if (req_info[i].namelen == namelen &&
396 !string_enum_compare(req_info[i].name, name, namelen))
397 return req_info[i].request;
399 return REQ_NONE;
400 }
403 /*
404 * Options
405 */
407 static const char usage[] =
408 "tig " TIG_VERSION " (" __DATE__ ")\n"
409 "\n"
410 "Usage: tig [options]\n"
411 " or: tig [options] [--] [git log options]\n"
412 " or: tig [options] log [git log options]\n"
413 " or: tig [options] diff [git diff options]\n"
414 " or: tig [options] show [git show options]\n"
415 " or: tig [options] < [git command output]\n"
416 "\n"
417 "Options:\n"
418 " -l Start up in log view\n"
419 " -d Start up in diff view\n"
420 " -S Start up in status view\n"
421 " -n[I], --line-number[=I] Show line numbers with given interval\n"
422 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
423 " -- Mark end of tig options\n"
424 " -v, --version Show version and exit\n"
425 " -h, --help Show help message and exit\n";
427 /* Option and state variables. */
428 static bool opt_line_number = FALSE;
429 static bool opt_rev_graph = FALSE;
430 static int opt_num_interval = NUMBER_INTERVAL;
431 static int opt_tab_size = TABSIZE;
432 static enum request opt_request = REQ_VIEW_MAIN;
433 static char opt_cmd[SIZEOF_STR] = "";
434 static char opt_path[SIZEOF_STR] = "";
435 static FILE *opt_pipe = NULL;
436 static char opt_encoding[20] = "UTF-8";
437 static bool opt_utf8 = TRUE;
438 static char opt_codeset[20] = "UTF-8";
439 static iconv_t opt_iconv = ICONV_NONE;
440 static char opt_search[SIZEOF_STR] = "";
441 static char opt_cdup[SIZEOF_STR] = "";
442 static char opt_git_dir[SIZEOF_STR] = "";
443 static char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
444 static char opt_editor[SIZEOF_STR] = "";
446 enum option_type {
447 OPT_NONE,
448 OPT_INT,
449 };
451 static bool
452 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
453 {
454 va_list args;
455 char *value = "";
456 int *number;
458 if (opt[0] != '-')
459 return FALSE;
461 if (opt[1] == '-') {
462 int namelen = strlen(name);
464 opt += 2;
466 if (strncmp(opt, name, namelen))
467 return FALSE;
469 if (opt[namelen] == '=')
470 value = opt + namelen + 1;
472 } else {
473 if (!short_name || opt[1] != short_name)
474 return FALSE;
475 value = opt + 2;
476 }
478 va_start(args, type);
479 if (type == OPT_INT) {
480 number = va_arg(args, int *);
481 if (isdigit(*value))
482 *number = atoi(value);
483 }
484 va_end(args);
486 return TRUE;
487 }
489 /* Returns the index of log or diff command or -1 to exit. */
490 static bool
491 parse_options(int argc, char *argv[])
492 {
493 int i;
495 for (i = 1; i < argc; i++) {
496 char *opt = argv[i];
498 if (!strcmp(opt, "log") ||
499 !strcmp(opt, "diff") ||
500 !strcmp(opt, "show")) {
501 opt_request = opt[0] == 'l'
502 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
503 break;
504 }
506 if (opt[0] && opt[0] != '-')
507 break;
509 if (!strcmp(opt, "-l")) {
510 opt_request = REQ_VIEW_LOG;
511 continue;
512 }
514 if (!strcmp(opt, "-d")) {
515 opt_request = REQ_VIEW_DIFF;
516 continue;
517 }
519 if (!strcmp(opt, "-S")) {
520 opt_request = REQ_VIEW_STATUS;
521 continue;
522 }
524 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
525 opt_line_number = TRUE;
526 continue;
527 }
529 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
530 opt_tab_size = MIN(opt_tab_size, TABSIZE);
531 continue;
532 }
534 if (check_option(opt, 'v', "version", OPT_NONE)) {
535 printf("tig version %s\n", TIG_VERSION);
536 return FALSE;
537 }
539 if (check_option(opt, 'h', "help", OPT_NONE)) {
540 printf(usage);
541 return FALSE;
542 }
544 if (!strcmp(opt, "--")) {
545 i++;
546 break;
547 }
549 die("unknown option '%s'\n\n%s", opt, usage);
550 }
552 if (!isatty(STDIN_FILENO)) {
553 opt_request = REQ_VIEW_PAGER;
554 opt_pipe = stdin;
556 } else if (i < argc) {
557 size_t buf_size;
559 if (opt_request == REQ_VIEW_MAIN)
560 /* XXX: This is vulnerable to the user overriding
561 * options required for the main view parser. */
562 string_copy(opt_cmd, "git log --pretty=raw");
563 else
564 string_copy(opt_cmd, "git");
565 buf_size = strlen(opt_cmd);
567 while (buf_size < sizeof(opt_cmd) && i < argc) {
568 opt_cmd[buf_size++] = ' ';
569 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
570 }
572 if (buf_size >= sizeof(opt_cmd))
573 die("command too long");
575 opt_cmd[buf_size] = 0;
576 }
578 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
579 opt_utf8 = FALSE;
581 return TRUE;
582 }
585 /*
586 * Line-oriented content detection.
587 */
589 #define LINE_INFO \
590 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
591 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
592 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
593 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
594 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
595 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
596 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
597 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
598 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
599 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
600 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
601 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
602 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
603 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
604 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
605 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
606 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
607 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
608 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
609 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
610 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
611 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
612 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
613 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
614 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
615 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
616 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
617 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
618 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
619 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
620 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
621 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
622 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
623 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
624 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
625 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
626 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
627 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
628 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
629 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
630 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
631 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
632 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
633 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
634 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
635 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
636 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
638 enum line_type {
639 #define LINE(type, line, fg, bg, attr) \
640 LINE_##type
641 LINE_INFO
642 #undef LINE
643 };
645 struct line_info {
646 const char *name; /* Option name. */
647 int namelen; /* Size of option name. */
648 const char *line; /* The start of line to match. */
649 int linelen; /* Size of string to match. */
650 int fg, bg, attr; /* Color and text attributes for the lines. */
651 };
653 static struct line_info line_info[] = {
654 #define LINE(type, line, fg, bg, attr) \
655 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
656 LINE_INFO
657 #undef LINE
658 };
660 static enum line_type
661 get_line_type(char *line)
662 {
663 int linelen = strlen(line);
664 enum line_type type;
666 for (type = 0; type < ARRAY_SIZE(line_info); type++)
667 /* Case insensitive search matches Signed-off-by lines better. */
668 if (linelen >= line_info[type].linelen &&
669 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
670 return type;
672 return LINE_DEFAULT;
673 }
675 static inline int
676 get_line_attr(enum line_type type)
677 {
678 assert(type < ARRAY_SIZE(line_info));
679 return COLOR_PAIR(type) | line_info[type].attr;
680 }
682 static struct line_info *
683 get_line_info(char *name, int namelen)
684 {
685 enum line_type type;
687 for (type = 0; type < ARRAY_SIZE(line_info); type++)
688 if (namelen == line_info[type].namelen &&
689 !string_enum_compare(line_info[type].name, name, namelen))
690 return &line_info[type];
692 return NULL;
693 }
695 static void
696 init_colors(void)
697 {
698 int default_bg = COLOR_BLACK;
699 int default_fg = COLOR_WHITE;
700 enum line_type type;
702 start_color();
704 if (use_default_colors() != ERR) {
705 default_bg = -1;
706 default_fg = -1;
707 }
709 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
710 struct line_info *info = &line_info[type];
711 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
712 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
714 init_pair(type, fg, bg);
715 }
716 }
718 struct line {
719 enum line_type type;
721 /* State flags */
722 unsigned int selected:1;
724 void *data; /* User data */
725 };
728 /*
729 * Keys
730 */
732 struct keybinding {
733 int alias;
734 enum request request;
735 struct keybinding *next;
736 };
738 static struct keybinding default_keybindings[] = {
739 /* View switching */
740 { 'm', REQ_VIEW_MAIN },
741 { 'd', REQ_VIEW_DIFF },
742 { 'l', REQ_VIEW_LOG },
743 { 't', REQ_VIEW_TREE },
744 { 'f', REQ_VIEW_BLOB },
745 { 'p', REQ_VIEW_PAGER },
746 { 'h', REQ_VIEW_HELP },
747 { 'S', REQ_VIEW_STATUS },
748 { 'c', REQ_VIEW_STAGE },
750 /* View manipulation */
751 { 'q', REQ_VIEW_CLOSE },
752 { KEY_TAB, REQ_VIEW_NEXT },
753 { KEY_RETURN, REQ_ENTER },
754 { KEY_UP, REQ_PREVIOUS },
755 { KEY_DOWN, REQ_NEXT },
756 { 'R', REQ_REFRESH },
758 /* Cursor navigation */
759 { 'k', REQ_MOVE_UP },
760 { 'j', REQ_MOVE_DOWN },
761 { KEY_HOME, REQ_MOVE_FIRST_LINE },
762 { KEY_END, REQ_MOVE_LAST_LINE },
763 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
764 { ' ', REQ_MOVE_PAGE_DOWN },
765 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
766 { 'b', REQ_MOVE_PAGE_UP },
767 { '-', REQ_MOVE_PAGE_UP },
769 /* Scrolling */
770 { KEY_IC, REQ_SCROLL_LINE_UP },
771 { KEY_DC, REQ_SCROLL_LINE_DOWN },
772 { 'w', REQ_SCROLL_PAGE_UP },
773 { 's', REQ_SCROLL_PAGE_DOWN },
775 /* Searching */
776 { '/', REQ_SEARCH },
777 { '?', REQ_SEARCH_BACK },
778 { 'n', REQ_FIND_NEXT },
779 { 'N', REQ_FIND_PREV },
781 /* Misc */
782 { 'Q', REQ_QUIT },
783 { 'z', REQ_STOP_LOADING },
784 { 'v', REQ_SHOW_VERSION },
785 { 'r', REQ_SCREEN_REDRAW },
786 { '.', REQ_TOGGLE_LINENO },
787 { 'g', REQ_TOGGLE_REV_GRAPH },
788 { ':', REQ_PROMPT },
789 { 'u', REQ_STATUS_UPDATE },
790 { 'M', REQ_STATUS_MERGE },
791 { 'e', REQ_EDIT },
792 { 'C', REQ_CHERRY_PICK },
794 /* Using the ncurses SIGWINCH handler. */
795 { KEY_RESIZE, REQ_SCREEN_RESIZE },
796 };
798 #define KEYMAP_INFO \
799 KEYMAP_(GENERIC), \
800 KEYMAP_(MAIN), \
801 KEYMAP_(DIFF), \
802 KEYMAP_(LOG), \
803 KEYMAP_(TREE), \
804 KEYMAP_(BLOB), \
805 KEYMAP_(PAGER), \
806 KEYMAP_(HELP), \
807 KEYMAP_(STATUS), \
808 KEYMAP_(STAGE)
810 enum keymap {
811 #define KEYMAP_(name) KEYMAP_##name
812 KEYMAP_INFO
813 #undef KEYMAP_
814 };
816 static struct int_map keymap_table[] = {
817 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
818 KEYMAP_INFO
819 #undef KEYMAP_
820 };
822 #define set_keymap(map, name) \
823 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
825 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
827 static void
828 add_keybinding(enum keymap keymap, enum request request, int key)
829 {
830 struct keybinding *keybinding;
832 keybinding = calloc(1, sizeof(*keybinding));
833 if (!keybinding)
834 die("Failed to allocate keybinding");
836 keybinding->alias = key;
837 keybinding->request = request;
838 keybinding->next = keybindings[keymap];
839 keybindings[keymap] = keybinding;
840 }
842 /* Looks for a key binding first in the given map, then in the generic map, and
843 * lastly in the default keybindings. */
844 static enum request
845 get_keybinding(enum keymap keymap, int key)
846 {
847 struct keybinding *kbd;
848 int i;
850 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
851 if (kbd->alias == key)
852 return kbd->request;
854 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
855 if (kbd->alias == key)
856 return kbd->request;
858 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
859 if (default_keybindings[i].alias == key)
860 return default_keybindings[i].request;
862 return (enum request) key;
863 }
866 struct key {
867 char *name;
868 int value;
869 };
871 static struct key key_table[] = {
872 { "Enter", KEY_RETURN },
873 { "Space", ' ' },
874 { "Backspace", KEY_BACKSPACE },
875 { "Tab", KEY_TAB },
876 { "Escape", KEY_ESC },
877 { "Left", KEY_LEFT },
878 { "Right", KEY_RIGHT },
879 { "Up", KEY_UP },
880 { "Down", KEY_DOWN },
881 { "Insert", KEY_IC },
882 { "Delete", KEY_DC },
883 { "Hash", '#' },
884 { "Home", KEY_HOME },
885 { "End", KEY_END },
886 { "PageUp", KEY_PPAGE },
887 { "PageDown", KEY_NPAGE },
888 { "F1", KEY_F(1) },
889 { "F2", KEY_F(2) },
890 { "F3", KEY_F(3) },
891 { "F4", KEY_F(4) },
892 { "F5", KEY_F(5) },
893 { "F6", KEY_F(6) },
894 { "F7", KEY_F(7) },
895 { "F8", KEY_F(8) },
896 { "F9", KEY_F(9) },
897 { "F10", KEY_F(10) },
898 { "F11", KEY_F(11) },
899 { "F12", KEY_F(12) },
900 };
902 static int
903 get_key_value(const char *name)
904 {
905 int i;
907 for (i = 0; i < ARRAY_SIZE(key_table); i++)
908 if (!strcasecmp(key_table[i].name, name))
909 return key_table[i].value;
911 if (strlen(name) == 1 && isprint(*name))
912 return (int) *name;
914 return ERR;
915 }
917 static char *
918 get_key(enum request request)
919 {
920 static char buf[BUFSIZ];
921 static char key_char[] = "'X'";
922 size_t pos = 0;
923 char *sep = "";
924 int i;
926 buf[pos] = 0;
928 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
929 struct keybinding *keybinding = &default_keybindings[i];
930 char *seq = NULL;
931 int key;
933 if (keybinding->request != request)
934 continue;
936 for (key = 0; key < ARRAY_SIZE(key_table); key++)
937 if (key_table[key].value == keybinding->alias)
938 seq = key_table[key].name;
940 if (seq == NULL &&
941 keybinding->alias < 127 &&
942 isprint(keybinding->alias)) {
943 key_char[1] = (char) keybinding->alias;
944 seq = key_char;
945 }
947 if (!seq)
948 seq = "'?'";
950 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
951 return "Too many keybindings!";
952 sep = ", ";
953 }
955 return buf;
956 }
959 /*
960 * User config file handling.
961 */
963 static struct int_map color_map[] = {
964 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
965 COLOR_MAP(DEFAULT),
966 COLOR_MAP(BLACK),
967 COLOR_MAP(BLUE),
968 COLOR_MAP(CYAN),
969 COLOR_MAP(GREEN),
970 COLOR_MAP(MAGENTA),
971 COLOR_MAP(RED),
972 COLOR_MAP(WHITE),
973 COLOR_MAP(YELLOW),
974 };
976 #define set_color(color, name) \
977 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
979 static struct int_map attr_map[] = {
980 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
981 ATTR_MAP(NORMAL),
982 ATTR_MAP(BLINK),
983 ATTR_MAP(BOLD),
984 ATTR_MAP(DIM),
985 ATTR_MAP(REVERSE),
986 ATTR_MAP(STANDOUT),
987 ATTR_MAP(UNDERLINE),
988 };
990 #define set_attribute(attr, name) \
991 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
993 static int config_lineno;
994 static bool config_errors;
995 static char *config_msg;
997 /* Wants: object fgcolor bgcolor [attr] */
998 static int
999 option_color_command(int argc, char *argv[])
1000 {
1001 struct line_info *info;
1003 if (argc != 3 && argc != 4) {
1004 config_msg = "Wrong number of arguments given to color command";
1005 return ERR;
1006 }
1008 info = get_line_info(argv[0], strlen(argv[0]));
1009 if (!info) {
1010 config_msg = "Unknown color name";
1011 return ERR;
1012 }
1014 if (set_color(&info->fg, argv[1]) == ERR ||
1015 set_color(&info->bg, argv[2]) == ERR) {
1016 config_msg = "Unknown color";
1017 return ERR;
1018 }
1020 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1021 config_msg = "Unknown attribute";
1022 return ERR;
1023 }
1025 return OK;
1026 }
1028 /* Wants: name = value */
1029 static int
1030 option_set_command(int argc, char *argv[])
1031 {
1032 if (argc != 3) {
1033 config_msg = "Wrong number of arguments given to set command";
1034 return ERR;
1035 }
1037 if (strcmp(argv[1], "=")) {
1038 config_msg = "No value assigned";
1039 return ERR;
1040 }
1042 if (!strcmp(argv[0], "show-rev-graph")) {
1043 opt_rev_graph = (!strcmp(argv[2], "1") ||
1044 !strcmp(argv[2], "true") ||
1045 !strcmp(argv[2], "yes"));
1046 return OK;
1047 }
1049 if (!strcmp(argv[0], "line-number-interval")) {
1050 opt_num_interval = atoi(argv[2]);
1051 return OK;
1052 }
1054 if (!strcmp(argv[0], "tab-size")) {
1055 opt_tab_size = atoi(argv[2]);
1056 return OK;
1057 }
1059 if (!strcmp(argv[0], "commit-encoding")) {
1060 char *arg = argv[2];
1061 int delimiter = *arg;
1062 int i;
1064 switch (delimiter) {
1065 case '"':
1066 case '\'':
1067 for (arg++, i = 0; arg[i]; i++)
1068 if (arg[i] == delimiter) {
1069 arg[i] = 0;
1070 break;
1071 }
1072 default:
1073 string_ncopy(opt_encoding, arg, strlen(arg));
1074 return OK;
1075 }
1076 }
1078 config_msg = "Unknown variable name";
1079 return ERR;
1080 }
1082 /* Wants: mode request key */
1083 static int
1084 option_bind_command(int argc, char *argv[])
1085 {
1086 enum request request;
1087 int keymap;
1088 int key;
1090 if (argc != 3) {
1091 config_msg = "Wrong number of arguments given to bind command";
1092 return ERR;
1093 }
1095 if (set_keymap(&keymap, argv[0]) == ERR) {
1096 config_msg = "Unknown key map";
1097 return ERR;
1098 }
1100 key = get_key_value(argv[1]);
1101 if (key == ERR) {
1102 config_msg = "Unknown key";
1103 return ERR;
1104 }
1106 request = get_request(argv[2]);
1107 if (request == REQ_NONE) {
1108 config_msg = "Unknown request name";
1109 return ERR;
1110 }
1112 add_keybinding(keymap, request, key);
1114 return OK;
1115 }
1117 static int
1118 set_option(char *opt, char *value)
1119 {
1120 char *argv[16];
1121 int valuelen;
1122 int argc = 0;
1124 /* Tokenize */
1125 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1126 argv[argc++] = value;
1128 value += valuelen;
1129 if (!*value)
1130 break;
1132 *value++ = 0;
1133 while (isspace(*value))
1134 value++;
1135 }
1137 if (!strcmp(opt, "color"))
1138 return option_color_command(argc, argv);
1140 if (!strcmp(opt, "set"))
1141 return option_set_command(argc, argv);
1143 if (!strcmp(opt, "bind"))
1144 return option_bind_command(argc, argv);
1146 config_msg = "Unknown option command";
1147 return ERR;
1148 }
1150 static int
1151 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1152 {
1153 int status = OK;
1155 config_lineno++;
1156 config_msg = "Internal error";
1158 /* Check for comment markers, since read_properties() will
1159 * only ensure opt and value are split at first " \t". */
1160 optlen = strcspn(opt, "#");
1161 if (optlen == 0)
1162 return OK;
1164 if (opt[optlen] != 0) {
1165 config_msg = "No option value";
1166 status = ERR;
1168 } else {
1169 /* Look for comment endings in the value. */
1170 size_t len = strcspn(value, "#");
1172 if (len < valuelen) {
1173 valuelen = len;
1174 value[valuelen] = 0;
1175 }
1177 status = set_option(opt, value);
1178 }
1180 if (status == ERR) {
1181 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1182 config_lineno, (int) optlen, opt, config_msg);
1183 config_errors = TRUE;
1184 }
1186 /* Always keep going if errors are encountered. */
1187 return OK;
1188 }
1190 static int
1191 load_options(void)
1192 {
1193 char *home = getenv("HOME");
1194 char buf[SIZEOF_STR];
1195 FILE *file;
1197 config_lineno = 0;
1198 config_errors = FALSE;
1200 if (!home || !string_format(buf, "%s/.tigrc", home))
1201 return ERR;
1203 /* It's ok that the file doesn't exist. */
1204 file = fopen(buf, "r");
1205 if (!file)
1206 return OK;
1208 if (read_properties(file, " \t", read_option) == ERR ||
1209 config_errors == TRUE)
1210 fprintf(stderr, "Errors while loading %s.\n", buf);
1212 return OK;
1213 }
1216 /*
1217 * The viewer
1218 */
1220 struct view;
1221 struct view_ops;
1223 /* The display array of active views and the index of the current view. */
1224 static struct view *display[2];
1225 static unsigned int current_view;
1227 /* Reading from the prompt? */
1228 static bool input_mode = FALSE;
1230 #define foreach_displayed_view(view, i) \
1231 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1233 #define displayed_views() (display[1] != NULL ? 2 : 1)
1235 /* Current head and commit ID */
1236 static char ref_blob[SIZEOF_REF] = "";
1237 static char ref_commit[SIZEOF_REF] = "HEAD";
1238 static char ref_head[SIZEOF_REF] = "HEAD";
1240 struct view {
1241 const char *name; /* View name */
1242 const char *cmd_fmt; /* Default command line format */
1243 const char *cmd_env; /* Command line set via environment */
1244 const char *id; /* Points to either of ref_{head,commit,blob} */
1246 struct view_ops *ops; /* View operations */
1248 enum keymap keymap; /* What keymap does this view have */
1250 char cmd[SIZEOF_STR]; /* Command buffer */
1251 char ref[SIZEOF_REF]; /* Hovered commit reference */
1252 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1254 int height, width; /* The width and height of the main window */
1255 WINDOW *win; /* The main window */
1256 WINDOW *title; /* The title window living below the main window */
1258 /* Navigation */
1259 unsigned long offset; /* Offset of the window top */
1260 unsigned long lineno; /* Current line number */
1262 /* Searching */
1263 char grep[SIZEOF_STR]; /* Search string */
1264 regex_t *regex; /* Pre-compiled regex */
1266 /* If non-NULL, points to the view that opened this view. If this view
1267 * is closed tig will switch back to the parent view. */
1268 struct view *parent;
1270 /* Buffering */
1271 unsigned long lines; /* Total number of lines */
1272 struct line *line; /* Line index */
1273 unsigned long line_size;/* Total number of allocated lines */
1274 unsigned int digits; /* Number of digits in the lines member. */
1276 /* Loading */
1277 FILE *pipe;
1278 time_t start_time;
1279 };
1281 struct view_ops {
1282 /* What type of content being displayed. Used in the title bar. */
1283 const char *type;
1284 /* Open and reads in all view content. */
1285 bool (*open)(struct view *view);
1286 /* Read one line; updates view->line. */
1287 bool (*read)(struct view *view, char *data);
1288 /* Draw one line; @lineno must be < view->height. */
1289 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1290 /* Depending on view handle a special requests. */
1291 enum request (*request)(struct view *view, enum request request, struct line *line);
1292 /* Search for regex in a line. */
1293 bool (*grep)(struct view *view, struct line *line);
1294 /* Select line */
1295 void (*select)(struct view *view, struct line *line);
1296 };
1298 static struct view_ops pager_ops;
1299 static struct view_ops main_ops;
1300 static struct view_ops tree_ops;
1301 static struct view_ops blob_ops;
1302 static struct view_ops help_ops;
1303 static struct view_ops status_ops;
1304 static struct view_ops stage_ops;
1306 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1307 { name, cmd, #env, ref, ops, map}
1309 #define VIEW_(id, name, ops, ref) \
1310 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1313 static struct view views[] = {
1314 VIEW_(MAIN, "main", &main_ops, ref_head),
1315 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1316 VIEW_(LOG, "log", &pager_ops, ref_head),
1317 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1318 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1319 VIEW_(HELP, "help", &help_ops, ""),
1320 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1321 VIEW_(STATUS, "status", &status_ops, ""),
1322 VIEW_(STAGE, "stage", &stage_ops, ""),
1323 };
1325 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1327 #define foreach_view(view, i) \
1328 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1330 #define view_is_displayed(view) \
1331 (view == display[0] || view == display[1])
1333 static bool
1334 draw_view_line(struct view *view, unsigned int lineno)
1335 {
1336 struct line *line;
1337 bool selected = (view->offset + lineno == view->lineno);
1338 bool draw_ok;
1340 assert(view_is_displayed(view));
1342 if (view->offset + lineno >= view->lines)
1343 return FALSE;
1345 line = &view->line[view->offset + lineno];
1347 if (selected) {
1348 line->selected = TRUE;
1349 view->ops->select(view, line);
1350 } else if (line->selected) {
1351 line->selected = FALSE;
1352 wmove(view->win, lineno, 0);
1353 wclrtoeol(view->win);
1354 }
1356 scrollok(view->win, FALSE);
1357 draw_ok = view->ops->draw(view, line, lineno, selected);
1358 scrollok(view->win, TRUE);
1360 return draw_ok;
1361 }
1363 static void
1364 redraw_view_from(struct view *view, int lineno)
1365 {
1366 assert(0 <= lineno && lineno < view->height);
1368 for (; lineno < view->height; lineno++) {
1369 if (!draw_view_line(view, lineno))
1370 break;
1371 }
1373 redrawwin(view->win);
1374 if (input_mode)
1375 wnoutrefresh(view->win);
1376 else
1377 wrefresh(view->win);
1378 }
1380 static void
1381 redraw_view(struct view *view)
1382 {
1383 wclear(view->win);
1384 redraw_view_from(view, 0);
1385 }
1388 static void
1389 update_view_title(struct view *view)
1390 {
1391 char buf[SIZEOF_STR];
1392 char state[SIZEOF_STR];
1393 size_t bufpos = 0, statelen = 0;
1395 assert(view_is_displayed(view));
1397 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1398 unsigned int view_lines = view->offset + view->height;
1399 unsigned int lines = view->lines
1400 ? MIN(view_lines, view->lines) * 100 / view->lines
1401 : 0;
1403 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1404 view->ops->type,
1405 view->lineno + 1,
1406 view->lines,
1407 lines);
1409 if (view->pipe) {
1410 time_t secs = time(NULL) - view->start_time;
1412 /* Three git seconds are a long time ... */
1413 if (secs > 2)
1414 string_format_from(state, &statelen, " %lds", secs);
1415 }
1416 }
1418 string_format_from(buf, &bufpos, "[%s]", view->name);
1419 if (*view->ref && bufpos < view->width) {
1420 size_t refsize = strlen(view->ref);
1421 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1423 if (minsize < view->width)
1424 refsize = view->width - minsize + 7;
1425 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1426 }
1428 if (statelen && bufpos < view->width) {
1429 string_format_from(buf, &bufpos, " %s", state);
1430 }
1432 if (view == display[current_view])
1433 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1434 else
1435 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1437 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1438 wclrtoeol(view->title);
1439 wmove(view->title, 0, view->width - 1);
1441 if (input_mode)
1442 wnoutrefresh(view->title);
1443 else
1444 wrefresh(view->title);
1445 }
1447 static void
1448 resize_display(void)
1449 {
1450 int offset, i;
1451 struct view *base = display[0];
1452 struct view *view = display[1] ? display[1] : display[0];
1454 /* Setup window dimensions */
1456 getmaxyx(stdscr, base->height, base->width);
1458 /* Make room for the status window. */
1459 base->height -= 1;
1461 if (view != base) {
1462 /* Horizontal split. */
1463 view->width = base->width;
1464 view->height = SCALE_SPLIT_VIEW(base->height);
1465 base->height -= view->height;
1467 /* Make room for the title bar. */
1468 view->height -= 1;
1469 }
1471 /* Make room for the title bar. */
1472 base->height -= 1;
1474 offset = 0;
1476 foreach_displayed_view (view, i) {
1477 if (!view->win) {
1478 view->win = newwin(view->height, 0, offset, 0);
1479 if (!view->win)
1480 die("Failed to create %s view", view->name);
1482 scrollok(view->win, TRUE);
1484 view->title = newwin(1, 0, offset + view->height, 0);
1485 if (!view->title)
1486 die("Failed to create title window");
1488 } else {
1489 wresize(view->win, view->height, view->width);
1490 mvwin(view->win, offset, 0);
1491 mvwin(view->title, offset + view->height, 0);
1492 }
1494 offset += view->height + 1;
1495 }
1496 }
1498 static void
1499 redraw_display(void)
1500 {
1501 struct view *view;
1502 int i;
1504 foreach_displayed_view (view, i) {
1505 redraw_view(view);
1506 update_view_title(view);
1507 }
1508 }
1510 static void
1511 update_display_cursor(struct view *view)
1512 {
1513 /* Move the cursor to the right-most column of the cursor line.
1514 *
1515 * XXX: This could turn out to be a bit expensive, but it ensures that
1516 * the cursor does not jump around. */
1517 if (view->lines) {
1518 wmove(view->win, view->lineno - view->offset, view->width - 1);
1519 wrefresh(view->win);
1520 }
1521 }
1523 /*
1524 * Navigation
1525 */
1527 /* Scrolling backend */
1528 static void
1529 do_scroll_view(struct view *view, int lines)
1530 {
1531 bool redraw_current_line = FALSE;
1533 /* The rendering expects the new offset. */
1534 view->offset += lines;
1536 assert(0 <= view->offset && view->offset < view->lines);
1537 assert(lines);
1539 /* Move current line into the view. */
1540 if (view->lineno < view->offset) {
1541 view->lineno = view->offset;
1542 redraw_current_line = TRUE;
1543 } else if (view->lineno >= view->offset + view->height) {
1544 view->lineno = view->offset + view->height - 1;
1545 redraw_current_line = TRUE;
1546 }
1548 assert(view->offset <= view->lineno && view->lineno < view->lines);
1550 /* Redraw the whole screen if scrolling is pointless. */
1551 if (view->height < ABS(lines)) {
1552 redraw_view(view);
1554 } else {
1555 int line = lines > 0 ? view->height - lines : 0;
1556 int end = line + ABS(lines);
1558 wscrl(view->win, lines);
1560 for (; line < end; line++) {
1561 if (!draw_view_line(view, line))
1562 break;
1563 }
1565 if (redraw_current_line)
1566 draw_view_line(view, view->lineno - view->offset);
1567 }
1569 redrawwin(view->win);
1570 wrefresh(view->win);
1571 report("");
1572 }
1574 /* Scroll frontend */
1575 static void
1576 scroll_view(struct view *view, enum request request)
1577 {
1578 int lines = 1;
1580 assert(view_is_displayed(view));
1582 switch (request) {
1583 case REQ_SCROLL_PAGE_DOWN:
1584 lines = view->height;
1585 case REQ_SCROLL_LINE_DOWN:
1586 if (view->offset + lines > view->lines)
1587 lines = view->lines - view->offset;
1589 if (lines == 0 || view->offset + view->height >= view->lines) {
1590 report("Cannot scroll beyond the last line");
1591 return;
1592 }
1593 break;
1595 case REQ_SCROLL_PAGE_UP:
1596 lines = view->height;
1597 case REQ_SCROLL_LINE_UP:
1598 if (lines > view->offset)
1599 lines = view->offset;
1601 if (lines == 0) {
1602 report("Cannot scroll beyond the first line");
1603 return;
1604 }
1606 lines = -lines;
1607 break;
1609 default:
1610 die("request %d not handled in switch", request);
1611 }
1613 do_scroll_view(view, lines);
1614 }
1616 /* Cursor moving */
1617 static void
1618 move_view(struct view *view, enum request request)
1619 {
1620 int scroll_steps = 0;
1621 int steps;
1623 switch (request) {
1624 case REQ_MOVE_FIRST_LINE:
1625 steps = -view->lineno;
1626 break;
1628 case REQ_MOVE_LAST_LINE:
1629 steps = view->lines - view->lineno - 1;
1630 break;
1632 case REQ_MOVE_PAGE_UP:
1633 steps = view->height > view->lineno
1634 ? -view->lineno : -view->height;
1635 break;
1637 case REQ_MOVE_PAGE_DOWN:
1638 steps = view->lineno + view->height >= view->lines
1639 ? view->lines - view->lineno - 1 : view->height;
1640 break;
1642 case REQ_MOVE_UP:
1643 steps = -1;
1644 break;
1646 case REQ_MOVE_DOWN:
1647 steps = 1;
1648 break;
1650 default:
1651 die("request %d not handled in switch", request);
1652 }
1654 if (steps <= 0 && view->lineno == 0) {
1655 report("Cannot move beyond the first line");
1656 return;
1658 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1659 report("Cannot move beyond the last line");
1660 return;
1661 }
1663 /* Move the current line */
1664 view->lineno += steps;
1665 assert(0 <= view->lineno && view->lineno < view->lines);
1667 /* Check whether the view needs to be scrolled */
1668 if (view->lineno < view->offset ||
1669 view->lineno >= view->offset + view->height) {
1670 scroll_steps = steps;
1671 if (steps < 0 && -steps > view->offset) {
1672 scroll_steps = -view->offset;
1674 } else if (steps > 0) {
1675 if (view->lineno == view->lines - 1 &&
1676 view->lines > view->height) {
1677 scroll_steps = view->lines - view->offset - 1;
1678 if (scroll_steps >= view->height)
1679 scroll_steps -= view->height - 1;
1680 }
1681 }
1682 }
1684 if (!view_is_displayed(view)) {
1685 view->offset += scroll_steps;
1686 assert(0 <= view->offset && view->offset < view->lines);
1687 view->ops->select(view, &view->line[view->lineno]);
1688 return;
1689 }
1691 /* Repaint the old "current" line if we be scrolling */
1692 if (ABS(steps) < view->height)
1693 draw_view_line(view, view->lineno - steps - view->offset);
1695 if (scroll_steps) {
1696 do_scroll_view(view, scroll_steps);
1697 return;
1698 }
1700 /* Draw the current line */
1701 draw_view_line(view, view->lineno - view->offset);
1703 redrawwin(view->win);
1704 wrefresh(view->win);
1705 report("");
1706 }
1709 /*
1710 * Searching
1711 */
1713 static void search_view(struct view *view, enum request request);
1715 static bool
1716 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1717 {
1718 assert(view_is_displayed(view));
1720 if (!view->ops->grep(view, line))
1721 return FALSE;
1723 if (lineno - view->offset >= view->height) {
1724 view->offset = lineno;
1725 view->lineno = lineno;
1726 redraw_view(view);
1728 } else {
1729 unsigned long old_lineno = view->lineno - view->offset;
1731 view->lineno = lineno;
1732 draw_view_line(view, old_lineno);
1734 draw_view_line(view, view->lineno - view->offset);
1735 redrawwin(view->win);
1736 wrefresh(view->win);
1737 }
1739 report("Line %ld matches '%s'", lineno + 1, view->grep);
1740 return TRUE;
1741 }
1743 static void
1744 find_next(struct view *view, enum request request)
1745 {
1746 unsigned long lineno = view->lineno;
1747 int direction;
1749 if (!*view->grep) {
1750 if (!*opt_search)
1751 report("No previous search");
1752 else
1753 search_view(view, request);
1754 return;
1755 }
1757 switch (request) {
1758 case REQ_SEARCH:
1759 case REQ_FIND_NEXT:
1760 direction = 1;
1761 break;
1763 case REQ_SEARCH_BACK:
1764 case REQ_FIND_PREV:
1765 direction = -1;
1766 break;
1768 default:
1769 return;
1770 }
1772 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1773 lineno += direction;
1775 /* Note, lineno is unsigned long so will wrap around in which case it
1776 * will become bigger than view->lines. */
1777 for (; lineno < view->lines; lineno += direction) {
1778 struct line *line = &view->line[lineno];
1780 if (find_next_line(view, lineno, line))
1781 return;
1782 }
1784 report("No match found for '%s'", view->grep);
1785 }
1787 static void
1788 search_view(struct view *view, enum request request)
1789 {
1790 int regex_err;
1792 if (view->regex) {
1793 regfree(view->regex);
1794 *view->grep = 0;
1795 } else {
1796 view->regex = calloc(1, sizeof(*view->regex));
1797 if (!view->regex)
1798 return;
1799 }
1801 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1802 if (regex_err != 0) {
1803 char buf[SIZEOF_STR] = "unknown error";
1805 regerror(regex_err, view->regex, buf, sizeof(buf));
1806 report("Search failed: %s", buf);
1807 return;
1808 }
1810 string_copy(view->grep, opt_search);
1812 find_next(view, request);
1813 }
1815 /*
1816 * Incremental updating
1817 */
1819 static void
1820 end_update(struct view *view)
1821 {
1822 if (!view->pipe)
1823 return;
1824 set_nonblocking_input(FALSE);
1825 if (view->pipe == stdin)
1826 fclose(view->pipe);
1827 else
1828 pclose(view->pipe);
1829 view->pipe = NULL;
1830 }
1832 static bool
1833 begin_update(struct view *view)
1834 {
1835 if (view->pipe)
1836 end_update(view);
1838 if (opt_cmd[0]) {
1839 string_copy(view->cmd, opt_cmd);
1840 opt_cmd[0] = 0;
1841 /* When running random commands, initially show the
1842 * command in the title. However, it maybe later be
1843 * overwritten if a commit line is selected. */
1844 if (view == VIEW(REQ_VIEW_PAGER))
1845 string_copy(view->ref, view->cmd);
1846 else
1847 view->ref[0] = 0;
1849 } else if (view == VIEW(REQ_VIEW_TREE)) {
1850 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1851 char path[SIZEOF_STR];
1853 if (strcmp(view->vid, view->id))
1854 opt_path[0] = path[0] = 0;
1855 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1856 return FALSE;
1858 if (!string_format(view->cmd, format, view->id, path))
1859 return FALSE;
1861 } else {
1862 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1863 const char *id = view->id;
1865 if (!string_format(view->cmd, format, id, id, id, id, id))
1866 return FALSE;
1868 /* Put the current ref_* value to the view title ref
1869 * member. This is needed by the blob view. Most other
1870 * views sets it automatically after loading because the
1871 * first line is a commit line. */
1872 string_copy_rev(view->ref, view->id);
1873 }
1875 /* Special case for the pager view. */
1876 if (opt_pipe) {
1877 view->pipe = opt_pipe;
1878 opt_pipe = NULL;
1879 } else {
1880 view->pipe = popen(view->cmd, "r");
1881 }
1883 if (!view->pipe)
1884 return FALSE;
1886 set_nonblocking_input(TRUE);
1888 view->offset = 0;
1889 view->lines = 0;
1890 view->lineno = 0;
1891 string_copy_rev(view->vid, view->id);
1893 if (view->line) {
1894 int i;
1896 for (i = 0; i < view->lines; i++)
1897 if (view->line[i].data)
1898 free(view->line[i].data);
1900 free(view->line);
1901 view->line = NULL;
1902 }
1904 view->start_time = time(NULL);
1906 return TRUE;
1907 }
1909 static struct line *
1910 realloc_lines(struct view *view, size_t line_size)
1911 {
1912 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1914 if (!tmp)
1915 return NULL;
1917 view->line = tmp;
1918 view->line_size = line_size;
1919 return view->line;
1920 }
1922 static bool
1923 update_view(struct view *view)
1924 {
1925 char in_buffer[BUFSIZ];
1926 char out_buffer[BUFSIZ * 2];
1927 char *line;
1928 /* The number of lines to read. If too low it will cause too much
1929 * redrawing (and possible flickering), if too high responsiveness
1930 * will suffer. */
1931 unsigned long lines = view->height;
1932 int redraw_from = -1;
1934 if (!view->pipe)
1935 return TRUE;
1937 /* Only redraw if lines are visible. */
1938 if (view->offset + view->height >= view->lines)
1939 redraw_from = view->lines - view->offset;
1941 /* FIXME: This is probably not perfect for backgrounded views. */
1942 if (!realloc_lines(view, view->lines + lines))
1943 goto alloc_error;
1945 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1946 size_t linelen = strlen(line);
1948 if (linelen)
1949 line[linelen - 1] = 0;
1951 if (opt_iconv != ICONV_NONE) {
1952 ICONV_CONST char *inbuf = line;
1953 size_t inlen = linelen;
1955 char *outbuf = out_buffer;
1956 size_t outlen = sizeof(out_buffer);
1958 size_t ret;
1960 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1961 if (ret != (size_t) -1) {
1962 line = out_buffer;
1963 linelen = strlen(out_buffer);
1964 }
1965 }
1967 if (!view->ops->read(view, line))
1968 goto alloc_error;
1970 if (lines-- == 1)
1971 break;
1972 }
1974 {
1975 int digits;
1977 lines = view->lines;
1978 for (digits = 0; lines; digits++)
1979 lines /= 10;
1981 /* Keep the displayed view in sync with line number scaling. */
1982 if (digits != view->digits) {
1983 view->digits = digits;
1984 redraw_from = 0;
1985 }
1986 }
1988 if (!view_is_displayed(view))
1989 goto check_pipe;
1991 if (view == VIEW(REQ_VIEW_TREE)) {
1992 /* Clear the view and redraw everything since the tree sorting
1993 * might have rearranged things. */
1994 redraw_view(view);
1996 } else if (redraw_from >= 0) {
1997 /* If this is an incremental update, redraw the previous line
1998 * since for commits some members could have changed when
1999 * loading the main view. */
2000 if (redraw_from > 0)
2001 redraw_from--;
2003 /* Since revision graph visualization requires knowledge
2004 * about the parent commit, it causes a further one-off
2005 * needed to be redrawn for incremental updates. */
2006 if (redraw_from > 0 && opt_rev_graph)
2007 redraw_from--;
2009 /* Incrementally draw avoids flickering. */
2010 redraw_view_from(view, redraw_from);
2011 }
2013 /* Update the title _after_ the redraw so that if the redraw picks up a
2014 * commit reference in view->ref it'll be available here. */
2015 update_view_title(view);
2017 check_pipe:
2018 if (ferror(view->pipe)) {
2019 report("Failed to read: %s", strerror(errno));
2020 goto end;
2022 } else if (feof(view->pipe)) {
2023 report("");
2024 goto end;
2025 }
2027 return TRUE;
2029 alloc_error:
2030 report("Allocation failure");
2032 end:
2033 view->ops->read(view, NULL);
2034 end_update(view);
2035 return FALSE;
2036 }
2038 static struct line *
2039 add_line_data(struct view *view, void *data, enum line_type type)
2040 {
2041 struct line *line = &view->line[view->lines++];
2043 memset(line, 0, sizeof(*line));
2044 line->type = type;
2045 line->data = data;
2047 return line;
2048 }
2050 static struct line *
2051 add_line_text(struct view *view, char *data, enum line_type type)
2052 {
2053 if (data)
2054 data = strdup(data);
2056 return data ? add_line_data(view, data, type) : NULL;
2057 }
2060 /*
2061 * View opening
2062 */
2064 enum open_flags {
2065 OPEN_DEFAULT = 0, /* Use default view switching. */
2066 OPEN_SPLIT = 1, /* Split current view. */
2067 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2068 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2069 };
2071 static void
2072 open_view(struct view *prev, enum request request, enum open_flags flags)
2073 {
2074 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2075 bool split = !!(flags & OPEN_SPLIT);
2076 bool reload = !!(flags & OPEN_RELOAD);
2077 struct view *view = VIEW(request);
2078 int nviews = displayed_views();
2079 struct view *base_view = display[0];
2081 if (view == prev && nviews == 1 && !reload) {
2082 report("Already in %s view", view->name);
2083 return;
2084 }
2086 if (view->ops->open) {
2087 if (!view->ops->open(view)) {
2088 report("Failed to load %s view", view->name);
2089 return;
2090 }
2092 } else if ((reload || strcmp(view->vid, view->id)) &&
2093 !begin_update(view)) {
2094 report("Failed to load %s view", view->name);
2095 return;
2096 }
2098 if (split) {
2099 display[1] = view;
2100 if (!backgrounded)
2101 current_view = 1;
2102 } else {
2103 /* Maximize the current view. */
2104 memset(display, 0, sizeof(display));
2105 current_view = 0;
2106 display[current_view] = view;
2107 }
2109 /* Resize the view when switching between split- and full-screen,
2110 * or when switching between two different full-screen views. */
2111 if (nviews != displayed_views() ||
2112 (nviews == 1 && base_view != display[0]))
2113 resize_display();
2115 if (split && prev->lineno - prev->offset >= prev->height) {
2116 /* Take the title line into account. */
2117 int lines = prev->lineno - prev->offset - prev->height + 1;
2119 /* Scroll the view that was split if the current line is
2120 * outside the new limited view. */
2121 do_scroll_view(prev, lines);
2122 }
2124 if (prev && view != prev) {
2125 if (split && !backgrounded) {
2126 /* "Blur" the previous view. */
2127 update_view_title(prev);
2128 }
2130 view->parent = prev;
2131 }
2133 if (view->pipe && view->lines == 0) {
2134 /* Clear the old view and let the incremental updating refill
2135 * the screen. */
2136 wclear(view->win);
2137 report("");
2138 } else {
2139 redraw_view(view);
2140 report("");
2141 }
2143 /* If the view is backgrounded the above calls to report()
2144 * won't redraw the view title. */
2145 if (backgrounded)
2146 update_view_title(view);
2147 }
2149 static void
2150 open_external_viewer(const char *cmd)
2151 {
2152 def_prog_mode(); /* save current tty modes */
2153 endwin(); /* restore original tty modes */
2154 system(cmd);
2155 fprintf(stderr, "Press Enter to continue");
2156 getc(stdin);
2157 reset_prog_mode();
2158 redraw_display();
2159 }
2161 static void
2162 open_mergetool(const char *file)
2163 {
2164 char cmd[SIZEOF_STR];
2165 char file_sq[SIZEOF_STR];
2167 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2168 string_format(cmd, "git mergetool %s", file_sq)) {
2169 open_external_viewer(cmd);
2170 }
2171 }
2173 static void
2174 open_editor(bool from_root, const char *file)
2175 {
2176 char cmd[SIZEOF_STR];
2177 char file_sq[SIZEOF_STR];
2178 char *editor;
2179 char *prefix = from_root ? opt_cdup : "";
2181 editor = getenv("GIT_EDITOR");
2182 if (!editor && *opt_editor)
2183 editor = opt_editor;
2184 if (!editor)
2185 editor = getenv("VISUAL");
2186 if (!editor)
2187 editor = getenv("EDITOR");
2188 if (!editor)
2189 editor = "vi";
2191 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2192 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2193 open_external_viewer(cmd);
2194 }
2195 }
2197 /*
2198 * User request switch noodle
2199 */
2201 static int
2202 view_driver(struct view *view, enum request request)
2203 {
2204 int i;
2206 if (request == REQ_NONE) {
2207 doupdate();
2208 return TRUE;
2209 }
2211 if (view && view->lines) {
2212 request = view->ops->request(view, request, &view->line[view->lineno]);
2213 if (request == REQ_NONE)
2214 return TRUE;
2215 }
2217 switch (request) {
2218 case REQ_MOVE_UP:
2219 case REQ_MOVE_DOWN:
2220 case REQ_MOVE_PAGE_UP:
2221 case REQ_MOVE_PAGE_DOWN:
2222 case REQ_MOVE_FIRST_LINE:
2223 case REQ_MOVE_LAST_LINE:
2224 move_view(view, request);
2225 break;
2227 case REQ_SCROLL_LINE_DOWN:
2228 case REQ_SCROLL_LINE_UP:
2229 case REQ_SCROLL_PAGE_DOWN:
2230 case REQ_SCROLL_PAGE_UP:
2231 scroll_view(view, request);
2232 break;
2234 case REQ_VIEW_BLOB:
2235 if (!ref_blob[0]) {
2236 report("No file chosen, press %s to open tree view",
2237 get_key(REQ_VIEW_TREE));
2238 break;
2239 }
2240 open_view(view, request, OPEN_DEFAULT);
2241 break;
2243 case REQ_VIEW_PAGER:
2244 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2245 report("No pager content, press %s to run command from prompt",
2246 get_key(REQ_PROMPT));
2247 break;
2248 }
2249 open_view(view, request, OPEN_DEFAULT);
2250 break;
2252 case REQ_VIEW_STAGE:
2253 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2254 report("No stage content, press %s to open the status view and choose file",
2255 get_key(REQ_VIEW_STATUS));
2256 break;
2257 }
2258 open_view(view, request, OPEN_DEFAULT);
2259 break;
2261 case REQ_VIEW_STATUS:
2262 if (opt_is_inside_work_tree == FALSE) {
2263 report("The status view requires a working tree");
2264 break;
2265 }
2266 open_view(view, request, OPEN_DEFAULT);
2267 break;
2269 case REQ_VIEW_MAIN:
2270 case REQ_VIEW_DIFF:
2271 case REQ_VIEW_LOG:
2272 case REQ_VIEW_TREE:
2273 case REQ_VIEW_HELP:
2274 open_view(view, request, OPEN_DEFAULT);
2275 break;
2277 case REQ_NEXT:
2278 case REQ_PREVIOUS:
2279 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2281 if ((view == VIEW(REQ_VIEW_DIFF) &&
2282 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2283 (view == VIEW(REQ_VIEW_STAGE) &&
2284 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2285 (view == VIEW(REQ_VIEW_BLOB) &&
2286 view->parent == VIEW(REQ_VIEW_TREE))) {
2287 int line;
2289 view = view->parent;
2290 line = view->lineno;
2291 move_view(view, request);
2292 if (view_is_displayed(view))
2293 update_view_title(view);
2294 if (line != view->lineno)
2295 view->ops->request(view, REQ_ENTER,
2296 &view->line[view->lineno]);
2298 } else {
2299 move_view(view, request);
2300 }
2301 break;
2303 case REQ_VIEW_NEXT:
2304 {
2305 int nviews = displayed_views();
2306 int next_view = (current_view + 1) % nviews;
2308 if (next_view == current_view) {
2309 report("Only one view is displayed");
2310 break;
2311 }
2313 current_view = next_view;
2314 /* Blur out the title of the previous view. */
2315 update_view_title(view);
2316 report("");
2317 break;
2318 }
2319 case REQ_REFRESH:
2320 report("Refreshing is not yet supported for the %s view", view->name);
2321 break;
2323 case REQ_TOGGLE_LINENO:
2324 opt_line_number = !opt_line_number;
2325 redraw_display();
2326 break;
2328 case REQ_TOGGLE_REV_GRAPH:
2329 opt_rev_graph = !opt_rev_graph;
2330 redraw_display();
2331 break;
2333 case REQ_PROMPT:
2334 /* Always reload^Wrerun commands from the prompt. */
2335 open_view(view, opt_request, OPEN_RELOAD);
2336 break;
2338 case REQ_SEARCH:
2339 case REQ_SEARCH_BACK:
2340 search_view(view, request);
2341 break;
2343 case REQ_FIND_NEXT:
2344 case REQ_FIND_PREV:
2345 find_next(view, request);
2346 break;
2348 case REQ_STOP_LOADING:
2349 for (i = 0; i < ARRAY_SIZE(views); i++) {
2350 view = &views[i];
2351 if (view->pipe)
2352 report("Stopped loading the %s view", view->name),
2353 end_update(view);
2354 }
2355 break;
2357 case REQ_SHOW_VERSION:
2358 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2359 return TRUE;
2361 case REQ_SCREEN_RESIZE:
2362 resize_display();
2363 /* Fall-through */
2364 case REQ_SCREEN_REDRAW:
2365 redraw_display();
2366 break;
2368 case REQ_EDIT:
2369 report("Nothing to edit");
2370 break;
2372 case REQ_CHERRY_PICK:
2373 report("Nothing to cherry-pick");
2374 break;
2376 case REQ_ENTER:
2377 report("Nothing to enter");
2378 break;
2381 case REQ_VIEW_CLOSE:
2382 /* XXX: Mark closed views by letting view->parent point to the
2383 * view itself. Parents to closed view should never be
2384 * followed. */
2385 if (view->parent &&
2386 view->parent->parent != view->parent) {
2387 memset(display, 0, sizeof(display));
2388 current_view = 0;
2389 display[current_view] = view->parent;
2390 view->parent = view;
2391 resize_display();
2392 redraw_display();
2393 break;
2394 }
2395 /* Fall-through */
2396 case REQ_QUIT:
2397 return FALSE;
2399 default:
2400 /* An unknown key will show most commonly used commands. */
2401 report("Unknown key, press 'h' for help");
2402 return TRUE;
2403 }
2405 return TRUE;
2406 }
2409 /*
2410 * Pager backend
2411 */
2413 static bool
2414 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2415 {
2416 char *text = line->data;
2417 enum line_type type = line->type;
2418 int textlen = strlen(text);
2419 int attr;
2421 wmove(view->win, lineno, 0);
2423 if (selected) {
2424 type = LINE_CURSOR;
2425 wchgat(view->win, -1, 0, type, NULL);
2426 }
2428 attr = get_line_attr(type);
2429 wattrset(view->win, attr);
2431 if (opt_line_number || opt_tab_size < TABSIZE) {
2432 static char spaces[] = " ";
2433 int col_offset = 0, col = 0;
2435 if (opt_line_number) {
2436 unsigned long real_lineno = view->offset + lineno + 1;
2438 if (real_lineno == 1 ||
2439 (real_lineno % opt_num_interval) == 0) {
2440 wprintw(view->win, "%.*d", view->digits, real_lineno);
2442 } else {
2443 waddnstr(view->win, spaces,
2444 MIN(view->digits, STRING_SIZE(spaces)));
2445 }
2446 waddstr(view->win, ": ");
2447 col_offset = view->digits + 2;
2448 }
2450 while (text && col_offset + col < view->width) {
2451 int cols_max = view->width - col_offset - col;
2452 char *pos = text;
2453 int cols;
2455 if (*text == '\t') {
2456 text++;
2457 assert(sizeof(spaces) > TABSIZE);
2458 pos = spaces;
2459 cols = opt_tab_size - (col % opt_tab_size);
2461 } else {
2462 text = strchr(text, '\t');
2463 cols = line ? text - pos : strlen(pos);
2464 }
2466 waddnstr(view->win, pos, MIN(cols, cols_max));
2467 col += cols;
2468 }
2470 } else {
2471 int col = 0, pos = 0;
2473 for (; pos < textlen && col < view->width; pos++, col++)
2474 if (text[pos] == '\t')
2475 col += TABSIZE - (col % TABSIZE) - 1;
2477 waddnstr(view->win, text, pos);
2478 }
2480 return TRUE;
2481 }
2483 static bool
2484 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2485 {
2486 char refbuf[SIZEOF_STR];
2487 char *ref = NULL;
2488 FILE *pipe;
2490 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2491 return TRUE;
2493 pipe = popen(refbuf, "r");
2494 if (!pipe)
2495 return TRUE;
2497 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2498 ref = chomp_string(ref);
2499 pclose(pipe);
2501 if (!ref || !*ref)
2502 return TRUE;
2504 /* This is the only fatal call, since it can "corrupt" the buffer. */
2505 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2506 return FALSE;
2508 return TRUE;
2509 }
2511 static void
2512 add_pager_refs(struct view *view, struct line *line)
2513 {
2514 char buf[SIZEOF_STR];
2515 char *commit_id = line->data + STRING_SIZE("commit ");
2516 struct ref **refs;
2517 size_t bufpos = 0, refpos = 0;
2518 const char *sep = "Refs: ";
2519 bool is_tag = FALSE;
2521 assert(line->type == LINE_COMMIT);
2523 refs = get_refs(commit_id);
2524 if (!refs) {
2525 if (view == VIEW(REQ_VIEW_DIFF))
2526 goto try_add_describe_ref;
2527 return;
2528 }
2530 do {
2531 struct ref *ref = refs[refpos];
2532 char *fmt = ref->tag ? "%s[%s]" :
2533 ref->remote ? "%s<%s>" : "%s%s";
2535 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2536 return;
2537 sep = ", ";
2538 if (ref->tag)
2539 is_tag = TRUE;
2540 } while (refs[refpos++]->next);
2542 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2543 try_add_describe_ref:
2544 /* Add <tag>-g<commit_id> "fake" reference. */
2545 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2546 return;
2547 }
2549 if (bufpos == 0)
2550 return;
2552 if (!realloc_lines(view, view->line_size + 1))
2553 return;
2555 add_line_text(view, buf, LINE_PP_REFS);
2556 }
2558 static bool
2559 pager_read(struct view *view, char *data)
2560 {
2561 struct line *line;
2563 if (!data)
2564 return TRUE;
2566 line = add_line_text(view, data, get_line_type(data));
2567 if (!line)
2568 return FALSE;
2570 if (line->type == LINE_COMMIT &&
2571 (view == VIEW(REQ_VIEW_DIFF) ||
2572 view == VIEW(REQ_VIEW_LOG)))
2573 add_pager_refs(view, line);
2575 return TRUE;
2576 }
2578 static enum request
2579 pager_request(struct view *view, enum request request, struct line *line)
2580 {
2581 int split = 0;
2583 if (request != REQ_ENTER)
2584 return request;
2586 if (line->type == LINE_COMMIT &&
2587 (view == VIEW(REQ_VIEW_LOG) ||
2588 view == VIEW(REQ_VIEW_PAGER))) {
2589 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2590 split = 1;
2591 }
2593 /* Always scroll the view even if it was split. That way
2594 * you can use Enter to scroll through the log view and
2595 * split open each commit diff. */
2596 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2598 /* FIXME: A minor workaround. Scrolling the view will call report("")
2599 * but if we are scrolling a non-current view this won't properly
2600 * update the view title. */
2601 if (split)
2602 update_view_title(view);
2604 return REQ_NONE;
2605 }
2607 static bool
2608 pager_grep(struct view *view, struct line *line)
2609 {
2610 regmatch_t pmatch;
2611 char *text = line->data;
2613 if (!*text)
2614 return FALSE;
2616 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2617 return FALSE;
2619 return TRUE;
2620 }
2622 static void
2623 pager_select(struct view *view, struct line *line)
2624 {
2625 if (line->type == LINE_COMMIT) {
2626 char *text = line->data + STRING_SIZE("commit ");
2628 if (view != VIEW(REQ_VIEW_PAGER))
2629 string_copy_rev(view->ref, text);
2630 string_copy_rev(ref_commit, text);
2631 }
2632 }
2634 static struct view_ops pager_ops = {
2635 "line",
2636 NULL,
2637 pager_read,
2638 pager_draw,
2639 pager_request,
2640 pager_grep,
2641 pager_select,
2642 };
2645 /*
2646 * Help backend
2647 */
2649 static bool
2650 help_open(struct view *view)
2651 {
2652 char buf[BUFSIZ];
2653 int lines = ARRAY_SIZE(req_info) + 2;
2654 int i;
2656 if (view->lines > 0)
2657 return TRUE;
2659 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2660 if (!req_info[i].request)
2661 lines++;
2663 view->line = calloc(lines, sizeof(*view->line));
2664 if (!view->line)
2665 return FALSE;
2667 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2669 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2670 char *key;
2672 if (req_info[i].request == REQ_NONE)
2673 continue;
2675 if (!req_info[i].request) {
2676 add_line_text(view, "", LINE_DEFAULT);
2677 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2678 continue;
2679 }
2681 key = get_key(req_info[i].request);
2682 if (!*key)
2683 key = "(no key defined)";
2685 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2686 continue;
2688 add_line_text(view, buf, LINE_DEFAULT);
2689 }
2691 return TRUE;
2692 }
2694 static struct view_ops help_ops = {
2695 "line",
2696 help_open,
2697 NULL,
2698 pager_draw,
2699 pager_request,
2700 pager_grep,
2701 pager_select,
2702 };
2705 /*
2706 * Tree backend
2707 */
2709 struct tree_stack_entry {
2710 struct tree_stack_entry *prev; /* Entry below this in the stack */
2711 unsigned long lineno; /* Line number to restore */
2712 char *name; /* Position of name in opt_path */
2713 };
2715 /* The top of the path stack. */
2716 static struct tree_stack_entry *tree_stack = NULL;
2717 unsigned long tree_lineno = 0;
2719 static void
2720 pop_tree_stack_entry(void)
2721 {
2722 struct tree_stack_entry *entry = tree_stack;
2724 tree_lineno = entry->lineno;
2725 entry->name[0] = 0;
2726 tree_stack = entry->prev;
2727 free(entry);
2728 }
2730 static void
2731 push_tree_stack_entry(char *name, unsigned long lineno)
2732 {
2733 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2734 size_t pathlen = strlen(opt_path);
2736 if (!entry)
2737 return;
2739 entry->prev = tree_stack;
2740 entry->name = opt_path + pathlen;
2741 tree_stack = entry;
2743 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2744 pop_tree_stack_entry();
2745 return;
2746 }
2748 /* Move the current line to the first tree entry. */
2749 tree_lineno = 1;
2750 entry->lineno = lineno;
2751 }
2753 /* Parse output from git-ls-tree(1):
2754 *
2755 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2756 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2757 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2758 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2759 */
2761 #define SIZEOF_TREE_ATTR \
2762 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2764 #define TREE_UP_FORMAT "040000 tree %s\t.."
2766 static int
2767 tree_compare_entry(enum line_type type1, char *name1,
2768 enum line_type type2, char *name2)
2769 {
2770 if (type1 != type2) {
2771 if (type1 == LINE_TREE_DIR)
2772 return -1;
2773 return 1;
2774 }
2776 return strcmp(name1, name2);
2777 }
2779 static bool
2780 tree_read(struct view *view, char *text)
2781 {
2782 size_t textlen = text ? strlen(text) : 0;
2783 char buf[SIZEOF_STR];
2784 unsigned long pos;
2785 enum line_type type;
2786 bool first_read = view->lines == 0;
2788 if (textlen <= SIZEOF_TREE_ATTR)
2789 return FALSE;
2791 type = text[STRING_SIZE("100644 ")] == 't'
2792 ? LINE_TREE_DIR : LINE_TREE_FILE;
2794 if (first_read) {
2795 /* Add path info line */
2796 if (!string_format(buf, "Directory path /%s", opt_path) ||
2797 !realloc_lines(view, view->line_size + 1) ||
2798 !add_line_text(view, buf, LINE_DEFAULT))
2799 return FALSE;
2801 /* Insert "link" to parent directory. */
2802 if (*opt_path) {
2803 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2804 !realloc_lines(view, view->line_size + 1) ||
2805 !add_line_text(view, buf, LINE_TREE_DIR))
2806 return FALSE;
2807 }
2808 }
2810 /* Strip the path part ... */
2811 if (*opt_path) {
2812 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2813 size_t striplen = strlen(opt_path);
2814 char *path = text + SIZEOF_TREE_ATTR;
2816 if (pathlen > striplen)
2817 memmove(path, path + striplen,
2818 pathlen - striplen + 1);
2819 }
2821 /* Skip "Directory ..." and ".." line. */
2822 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2823 struct line *line = &view->line[pos];
2824 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2825 char *path2 = text + SIZEOF_TREE_ATTR;
2826 int cmp = tree_compare_entry(line->type, path1, type, path2);
2828 if (cmp <= 0)
2829 continue;
2831 text = strdup(text);
2832 if (!text)
2833 return FALSE;
2835 if (view->lines > pos)
2836 memmove(&view->line[pos + 1], &view->line[pos],
2837 (view->lines - pos) * sizeof(*line));
2839 line = &view->line[pos];
2840 line->data = text;
2841 line->type = type;
2842 view->lines++;
2843 return TRUE;
2844 }
2846 if (!add_line_text(view, text, type))
2847 return FALSE;
2849 if (tree_lineno > view->lineno) {
2850 view->lineno = tree_lineno;
2851 tree_lineno = 0;
2852 }
2854 return TRUE;
2855 }
2857 static enum request
2858 tree_request(struct view *view, enum request request, struct line *line)
2859 {
2860 enum open_flags flags;
2862 if (request != REQ_ENTER)
2863 return request;
2865 /* Cleanup the stack if the tree view is at a different tree. */
2866 while (!*opt_path && tree_stack)
2867 pop_tree_stack_entry();
2869 switch (line->type) {
2870 case LINE_TREE_DIR:
2871 /* Depending on whether it is a subdir or parent (updir?) link
2872 * mangle the path buffer. */
2873 if (line == &view->line[1] && *opt_path) {
2874 pop_tree_stack_entry();
2876 } else {
2877 char *data = line->data;
2878 char *basename = data + SIZEOF_TREE_ATTR;
2880 push_tree_stack_entry(basename, view->lineno);
2881 }
2883 /* Trees and subtrees share the same ID, so they are not not
2884 * unique like blobs. */
2885 flags = OPEN_RELOAD;
2886 request = REQ_VIEW_TREE;
2887 break;
2889 case LINE_TREE_FILE:
2890 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2891 request = REQ_VIEW_BLOB;
2892 break;
2894 default:
2895 return TRUE;
2896 }
2898 open_view(view, request, flags);
2899 if (request == REQ_VIEW_TREE) {
2900 view->lineno = tree_lineno;
2901 }
2903 return REQ_NONE;
2904 }
2906 static void
2907 tree_select(struct view *view, struct line *line)
2908 {
2909 char *text = line->data + STRING_SIZE("100644 blob ");
2911 if (line->type == LINE_TREE_FILE) {
2912 string_copy_rev(ref_blob, text);
2914 } else if (line->type != LINE_TREE_DIR) {
2915 return;
2916 }
2918 string_copy_rev(view->ref, text);
2919 }
2921 static struct view_ops tree_ops = {
2922 "file",
2923 NULL,
2924 tree_read,
2925 pager_draw,
2926 tree_request,
2927 pager_grep,
2928 tree_select,
2929 };
2931 static bool
2932 blob_read(struct view *view, char *line)
2933 {
2934 return add_line_text(view, line, LINE_DEFAULT) != NULL;
2935 }
2937 static struct view_ops blob_ops = {
2938 "line",
2939 NULL,
2940 blob_read,
2941 pager_draw,
2942 pager_request,
2943 pager_grep,
2944 pager_select,
2945 };
2948 /*
2949 * Status backend
2950 */
2952 struct status {
2953 char status;
2954 struct {
2955 mode_t mode;
2956 char rev[SIZEOF_REV];
2957 } old;
2958 struct {
2959 mode_t mode;
2960 char rev[SIZEOF_REV];
2961 } new;
2962 char name[SIZEOF_STR];
2963 };
2965 static struct status stage_status;
2966 static enum line_type stage_line_type;
2968 /* Get fields from the diff line:
2969 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2970 */
2971 static inline bool
2972 status_get_diff(struct status *file, char *buf, size_t bufsize)
2973 {
2974 char *old_mode = buf + 1;
2975 char *new_mode = buf + 8;
2976 char *old_rev = buf + 15;
2977 char *new_rev = buf + 56;
2978 char *status = buf + 97;
2980 if (bufsize != 99 ||
2981 old_mode[-1] != ':' ||
2982 new_mode[-1] != ' ' ||
2983 old_rev[-1] != ' ' ||
2984 new_rev[-1] != ' ' ||
2985 status[-1] != ' ')
2986 return FALSE;
2988 file->status = *status;
2990 string_copy_rev(file->old.rev, old_rev);
2991 string_copy_rev(file->new.rev, new_rev);
2993 file->old.mode = strtoul(old_mode, NULL, 8);
2994 file->new.mode = strtoul(new_mode, NULL, 8);
2996 file->name[0] = 0;
2998 return TRUE;
2999 }
3001 static bool
3002 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3003 {
3004 struct status *file = NULL;
3005 struct status *unmerged = NULL;
3006 char buf[SIZEOF_STR * 4];
3007 size_t bufsize = 0;
3008 FILE *pipe;
3010 pipe = popen(cmd, "r");
3011 if (!pipe)
3012 return FALSE;
3014 add_line_data(view, NULL, type);
3016 while (!feof(pipe) && !ferror(pipe)) {
3017 char *sep;
3018 size_t readsize;
3020 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3021 if (!readsize)
3022 break;
3023 bufsize += readsize;
3025 /* Process while we have NUL chars. */
3026 while ((sep = memchr(buf, 0, bufsize))) {
3027 size_t sepsize = sep - buf + 1;
3029 if (!file) {
3030 if (!realloc_lines(view, view->line_size + 1))
3031 goto error_out;
3033 file = calloc(1, sizeof(*file));
3034 if (!file)
3035 goto error_out;
3037 add_line_data(view, file, type);
3038 }
3040 /* Parse diff info part. */
3041 if (!diff) {
3042 file->status = '?';
3044 } else if (!file->status) {
3045 if (!status_get_diff(file, buf, sepsize))
3046 goto error_out;
3048 bufsize -= sepsize;
3049 memmove(buf, sep + 1, bufsize);
3051 sep = memchr(buf, 0, bufsize);
3052 if (!sep)
3053 break;
3054 sepsize = sep - buf + 1;
3056 /* Collapse all 'M'odified entries that
3057 * follow a associated 'U'nmerged entry.
3058 */
3059 if (file->status == 'U') {
3060 unmerged = file;
3062 } else if (unmerged) {
3063 int collapse = !strcmp(buf, unmerged->name);
3065 unmerged = NULL;
3066 if (collapse) {
3067 free(file);
3068 view->lines--;
3069 continue;
3070 }
3071 }
3072 }
3074 /* git-ls-files just delivers a NUL separated
3075 * list of file names similar to the second half
3076 * of the git-diff-* output. */
3077 string_ncopy(file->name, buf, sepsize);
3078 bufsize -= sepsize;
3079 memmove(buf, sep + 1, bufsize);
3080 file = NULL;
3081 }
3082 }
3084 if (ferror(pipe)) {
3085 error_out:
3086 pclose(pipe);
3087 return FALSE;
3088 }
3090 if (!view->line[view->lines - 1].data)
3091 add_line_data(view, NULL, LINE_STAT_NONE);
3093 pclose(pipe);
3094 return TRUE;
3095 }
3097 /* Don't show unmerged entries in the staged section. */
3098 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3099 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3100 #define STATUS_LIST_OTHER_CMD \
3101 "git ls-files -z --others --exclude-per-directory=.gitignore"
3103 #define STATUS_DIFF_SHOW_CMD \
3104 "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3106 /* First parse staged info using git-diff-index(1), then parse unstaged
3107 * info using git-diff-files(1), and finally untracked files using
3108 * git-ls-files(1). */
3109 static bool
3110 status_open(struct view *view)
3111 {
3112 struct stat statbuf;
3113 char exclude[SIZEOF_STR];
3114 char cmd[SIZEOF_STR];
3115 unsigned long prev_lineno = view->lineno;
3116 size_t i;
3118 for (i = 0; i < view->lines; i++)
3119 free(view->line[i].data);
3120 free(view->line);
3121 view->lines = view->line_size = view->lineno = 0;
3122 view->line = NULL;
3124 if (!realloc_lines(view, view->line_size + 6))
3125 return FALSE;
3127 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3128 return FALSE;
3130 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3132 if (stat(exclude, &statbuf) >= 0) {
3133 size_t cmdsize = strlen(cmd);
3135 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3136 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3137 return FALSE;
3138 }
3140 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3141 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3142 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3143 return FALSE;
3145 /* If all went well restore the previous line number to stay in
3146 * the context. */
3147 if (prev_lineno < view->lines)
3148 view->lineno = prev_lineno;
3149 else
3150 view->lineno = view->lines - 1;
3152 return TRUE;
3153 }
3155 static bool
3156 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3157 {
3158 struct status *status = line->data;
3160 wmove(view->win, lineno, 0);
3162 if (selected) {
3163 wattrset(view->win, get_line_attr(LINE_CURSOR));
3164 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3166 } else if (!status && line->type != LINE_STAT_NONE) {
3167 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3168 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3170 } else {
3171 wattrset(view->win, get_line_attr(line->type));
3172 }
3174 if (!status) {
3175 char *text;
3177 switch (line->type) {
3178 case LINE_STAT_STAGED:
3179 text = "Changes to be committed:";
3180 break;
3182 case LINE_STAT_UNSTAGED:
3183 text = "Changed but not updated:";
3184 break;
3186 case LINE_STAT_UNTRACKED:
3187 text = "Untracked files:";
3188 break;
3190 case LINE_STAT_NONE:
3191 text = " (no files)";
3192 break;
3194 default:
3195 return FALSE;
3196 }
3198 waddstr(view->win, text);
3199 return TRUE;
3200 }
3202 waddch(view->win, status->status);
3203 if (!selected)
3204 wattrset(view->win, A_NORMAL);
3205 wmove(view->win, lineno, 4);
3206 waddstr(view->win, status->name);
3208 return TRUE;
3209 }
3211 static enum request
3212 status_enter(struct view *view, struct line *line)
3213 {
3214 struct status *status = line->data;
3215 char path[SIZEOF_STR] = "";
3216 char *info;
3217 size_t cmdsize = 0;
3219 if (line->type == LINE_STAT_NONE ||
3220 (!status && line[1].type == LINE_STAT_NONE)) {
3221 report("No file to diff");
3222 return REQ_NONE;
3223 }
3225 if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3226 return REQ_QUIT;
3228 if (opt_cdup[0] &&
3229 line->type != LINE_STAT_UNTRACKED &&
3230 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3231 return REQ_QUIT;
3233 switch (line->type) {
3234 case LINE_STAT_STAGED:
3235 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3236 "--cached", path))
3237 return REQ_QUIT;
3238 if (status)
3239 info = "Staged changes to %s";
3240 else
3241 info = "Staged changes";
3242 break;
3244 case LINE_STAT_UNSTAGED:
3245 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3246 "", path))
3247 return REQ_QUIT;
3248 if (status)
3249 info = "Unstaged changes to %s";
3250 else
3251 info = "Unstaged changes";
3252 break;
3254 case LINE_STAT_UNTRACKED:
3255 if (opt_pipe)
3256 return REQ_QUIT;
3259 if (!status) {
3260 report("No file to show");
3261 return REQ_NONE;
3262 }
3264 opt_pipe = fopen(status->name, "r");
3265 info = "Untracked file %s";
3266 break;
3268 default:
3269 die("w00t");
3270 }
3272 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3273 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3274 if (status) {
3275 stage_status = *status;
3276 } else {
3277 memset(&stage_status, 0, sizeof(stage_status));
3278 }
3280 stage_line_type = line->type;
3281 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3282 }
3284 return REQ_NONE;
3285 }
3288 static bool
3289 status_update_file(struct view *view, struct status *status, enum line_type type)
3290 {
3291 char cmd[SIZEOF_STR];
3292 char buf[SIZEOF_STR];
3293 size_t cmdsize = 0;
3294 size_t bufsize = 0;
3295 size_t written = 0;
3296 FILE *pipe;
3298 if (opt_cdup[0] &&
3299 type != LINE_STAT_UNTRACKED &&
3300 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3301 return FALSE;
3303 switch (type) {
3304 case LINE_STAT_STAGED:
3305 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3306 status->old.mode,
3307 status->old.rev,
3308 status->name, 0))
3309 return FALSE;
3311 string_add(cmd, cmdsize, "git update-index -z --index-info");
3312 break;
3314 case LINE_STAT_UNSTAGED:
3315 case LINE_STAT_UNTRACKED:
3316 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3317 return FALSE;
3319 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3320 break;
3322 default:
3323 die("w00t");
3324 }
3326 pipe = popen(cmd, "w");
3327 if (!pipe)
3328 return FALSE;
3330 while (!ferror(pipe) && written < bufsize) {
3331 written += fwrite(buf + written, 1, bufsize - written, pipe);
3332 }
3334 pclose(pipe);
3336 if (written != bufsize)
3337 return FALSE;
3339 return TRUE;
3340 }
3342 static void
3343 status_update(struct view *view)
3344 {
3345 struct line *line = &view->line[view->lineno];
3347 assert(view->lines);
3349 if (!line->data) {
3350 while (++line < view->line + view->lines && line->data) {
3351 if (!status_update_file(view, line->data, line->type))
3352 report("Failed to update file status");
3353 }
3355 if (!line[-1].data) {
3356 report("Nothing to update");
3357 return;
3358 }
3360 } else if (!status_update_file(view, line->data, line->type)) {
3361 report("Failed to update file status");
3362 }
3363 }
3365 static enum request
3366 status_request(struct view *view, enum request request, struct line *line)
3367 {
3368 struct status *status = line->data;
3370 switch (request) {
3371 case REQ_STATUS_UPDATE:
3372 status_update(view);
3373 break;
3375 case REQ_STATUS_MERGE:
3376 open_mergetool(status->name);
3377 break;
3379 case REQ_EDIT:
3380 if (!status)
3381 return request;
3383 open_editor(status->status != '?', status->name);
3384 break;
3386 case REQ_ENTER:
3387 /* After returning the status view has been split to
3388 * show the stage view. No further reloading is
3389 * necessary. */
3390 status_enter(view, line);
3391 return REQ_NONE;
3393 case REQ_REFRESH:
3394 /* Simply reload the view. */
3395 break;
3397 default:
3398 return request;
3399 }
3401 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
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 }