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 --no-color --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
110 #define TIG_LOG_CMD \
111 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
113 #define TIG_MAIN_CMD \
114 "git log --no-color --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_(TREE_PARENT, "Switch to parent directory in tree view"), \
356 REQ_(EDIT, "Open in editor"), \
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, "--")) {
510 i++;
511 break;
512 }
514 if (check_option(opt, 'v', "version", OPT_NONE)) {
515 printf("tig version %s\n", TIG_VERSION);
516 return FALSE;
517 }
519 if (check_option(opt, 'h', "help", OPT_NONE)) {
520 printf(usage);
521 return FALSE;
522 }
524 if (!strcmp(opt, "-S")) {
525 opt_request = REQ_VIEW_STATUS;
526 continue;
527 }
529 if (!strcmp(opt, "-l")) {
530 opt_request = REQ_VIEW_LOG;
531 continue;
532 }
534 if (!strcmp(opt, "-d")) {
535 opt_request = REQ_VIEW_DIFF;
536 continue;
537 }
539 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
540 opt_line_number = TRUE;
541 continue;
542 }
544 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
545 opt_tab_size = MIN(opt_tab_size, TABSIZE);
546 continue;
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 --no-color --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(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
631 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
632 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
633 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
634 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
635 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
636 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
637 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
639 enum line_type {
640 #define LINE(type, line, fg, bg, attr) \
641 LINE_##type
642 LINE_INFO
643 #undef LINE
644 };
646 struct line_info {
647 const char *name; /* Option name. */
648 int namelen; /* Size of option name. */
649 const char *line; /* The start of line to match. */
650 int linelen; /* Size of string to match. */
651 int fg, bg, attr; /* Color and text attributes for the lines. */
652 };
654 static struct line_info line_info[] = {
655 #define LINE(type, line, fg, bg, attr) \
656 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
657 LINE_INFO
658 #undef LINE
659 };
661 static enum line_type
662 get_line_type(char *line)
663 {
664 int linelen = strlen(line);
665 enum line_type type;
667 for (type = 0; type < ARRAY_SIZE(line_info); type++)
668 /* Case insensitive search matches Signed-off-by lines better. */
669 if (linelen >= line_info[type].linelen &&
670 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
671 return type;
673 return LINE_DEFAULT;
674 }
676 static inline int
677 get_line_attr(enum line_type type)
678 {
679 assert(type < ARRAY_SIZE(line_info));
680 return COLOR_PAIR(type) | line_info[type].attr;
681 }
683 static struct line_info *
684 get_line_info(char *name, int namelen)
685 {
686 enum line_type type;
688 for (type = 0; type < ARRAY_SIZE(line_info); type++)
689 if (namelen == line_info[type].namelen &&
690 !string_enum_compare(line_info[type].name, name, namelen))
691 return &line_info[type];
693 return NULL;
694 }
696 static void
697 init_colors(void)
698 {
699 int default_bg = COLOR_BLACK;
700 int default_fg = COLOR_WHITE;
701 enum line_type type;
703 start_color();
705 if (use_default_colors() != ERR) {
706 default_bg = -1;
707 default_fg = -1;
708 }
710 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
711 struct line_info *info = &line_info[type];
712 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
713 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
715 init_pair(type, fg, bg);
716 }
717 }
719 struct line {
720 enum line_type type;
722 /* State flags */
723 unsigned int selected:1;
725 void *data; /* User data */
726 };
729 /*
730 * Keys
731 */
733 struct keybinding {
734 int alias;
735 enum request request;
736 struct keybinding *next;
737 };
739 static struct keybinding default_keybindings[] = {
740 /* View switching */
741 { 'm', REQ_VIEW_MAIN },
742 { 'd', REQ_VIEW_DIFF },
743 { 'l', REQ_VIEW_LOG },
744 { 't', REQ_VIEW_TREE },
745 { 'f', REQ_VIEW_BLOB },
746 { 'p', REQ_VIEW_PAGER },
747 { 'h', REQ_VIEW_HELP },
748 { 'S', REQ_VIEW_STATUS },
749 { 'c', REQ_VIEW_STAGE },
751 /* View manipulation */
752 { 'q', REQ_VIEW_CLOSE },
753 { KEY_TAB, REQ_VIEW_NEXT },
754 { KEY_RETURN, REQ_ENTER },
755 { KEY_UP, REQ_PREVIOUS },
756 { KEY_DOWN, REQ_NEXT },
757 { 'R', REQ_REFRESH },
759 /* Cursor navigation */
760 { 'k', REQ_MOVE_UP },
761 { 'j', REQ_MOVE_DOWN },
762 { KEY_HOME, REQ_MOVE_FIRST_LINE },
763 { KEY_END, REQ_MOVE_LAST_LINE },
764 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
765 { ' ', REQ_MOVE_PAGE_DOWN },
766 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
767 { 'b', REQ_MOVE_PAGE_UP },
768 { '-', REQ_MOVE_PAGE_UP },
770 /* Scrolling */
771 { KEY_IC, REQ_SCROLL_LINE_UP },
772 { KEY_DC, REQ_SCROLL_LINE_DOWN },
773 { 'w', REQ_SCROLL_PAGE_UP },
774 { 's', REQ_SCROLL_PAGE_DOWN },
776 /* Searching */
777 { '/', REQ_SEARCH },
778 { '?', REQ_SEARCH_BACK },
779 { 'n', REQ_FIND_NEXT },
780 { 'N', REQ_FIND_PREV },
782 /* Misc */
783 { 'Q', REQ_QUIT },
784 { 'z', REQ_STOP_LOADING },
785 { 'v', REQ_SHOW_VERSION },
786 { 'r', REQ_SCREEN_REDRAW },
787 { '.', REQ_TOGGLE_LINENO },
788 { 'g', REQ_TOGGLE_REV_GRAPH },
789 { ':', REQ_PROMPT },
790 { 'u', REQ_STATUS_UPDATE },
791 { 'M', REQ_STATUS_MERGE },
792 { ',', REQ_TREE_PARENT },
793 { 'e', REQ_EDIT },
795 /* Using the ncurses SIGWINCH handler. */
796 { KEY_RESIZE, REQ_SCREEN_RESIZE },
797 };
799 #define KEYMAP_INFO \
800 KEYMAP_(GENERIC), \
801 KEYMAP_(MAIN), \
802 KEYMAP_(DIFF), \
803 KEYMAP_(LOG), \
804 KEYMAP_(TREE), \
805 KEYMAP_(BLOB), \
806 KEYMAP_(PAGER), \
807 KEYMAP_(HELP), \
808 KEYMAP_(STATUS), \
809 KEYMAP_(STAGE)
811 enum keymap {
812 #define KEYMAP_(name) KEYMAP_##name
813 KEYMAP_INFO
814 #undef KEYMAP_
815 };
817 static struct int_map keymap_table[] = {
818 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
819 KEYMAP_INFO
820 #undef KEYMAP_
821 };
823 #define set_keymap(map, name) \
824 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
826 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
828 static void
829 add_keybinding(enum keymap keymap, enum request request, int key)
830 {
831 struct keybinding *keybinding;
833 keybinding = calloc(1, sizeof(*keybinding));
834 if (!keybinding)
835 die("Failed to allocate keybinding");
837 keybinding->alias = key;
838 keybinding->request = request;
839 keybinding->next = keybindings[keymap];
840 keybindings[keymap] = keybinding;
841 }
843 /* Looks for a key binding first in the given map, then in the generic map, and
844 * lastly in the default keybindings. */
845 static enum request
846 get_keybinding(enum keymap keymap, int key)
847 {
848 struct keybinding *kbd;
849 int i;
851 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
852 if (kbd->alias == key)
853 return kbd->request;
855 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
856 if (kbd->alias == key)
857 return kbd->request;
859 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
860 if (default_keybindings[i].alias == key)
861 return default_keybindings[i].request;
863 return (enum request) key;
864 }
867 struct key {
868 char *name;
869 int value;
870 };
872 static struct key key_table[] = {
873 { "Enter", KEY_RETURN },
874 { "Space", ' ' },
875 { "Backspace", KEY_BACKSPACE },
876 { "Tab", KEY_TAB },
877 { "Escape", KEY_ESC },
878 { "Left", KEY_LEFT },
879 { "Right", KEY_RIGHT },
880 { "Up", KEY_UP },
881 { "Down", KEY_DOWN },
882 { "Insert", KEY_IC },
883 { "Delete", KEY_DC },
884 { "Hash", '#' },
885 { "Home", KEY_HOME },
886 { "End", KEY_END },
887 { "PageUp", KEY_PPAGE },
888 { "PageDown", KEY_NPAGE },
889 { "F1", KEY_F(1) },
890 { "F2", KEY_F(2) },
891 { "F3", KEY_F(3) },
892 { "F4", KEY_F(4) },
893 { "F5", KEY_F(5) },
894 { "F6", KEY_F(6) },
895 { "F7", KEY_F(7) },
896 { "F8", KEY_F(8) },
897 { "F9", KEY_F(9) },
898 { "F10", KEY_F(10) },
899 { "F11", KEY_F(11) },
900 { "F12", KEY_F(12) },
901 };
903 static int
904 get_key_value(const char *name)
905 {
906 int i;
908 for (i = 0; i < ARRAY_SIZE(key_table); i++)
909 if (!strcasecmp(key_table[i].name, name))
910 return key_table[i].value;
912 if (strlen(name) == 1 && isprint(*name))
913 return (int) *name;
915 return ERR;
916 }
918 static char *
919 get_key_name(int key_value)
920 {
921 static char key_char[] = "'X'";
922 char *seq = NULL;
923 int key;
925 for (key = 0; key < ARRAY_SIZE(key_table); key++)
926 if (key_table[key].value == key_value)
927 seq = key_table[key].name;
929 if (seq == NULL &&
930 key_value < 127 &&
931 isprint(key_value)) {
932 key_char[1] = (char) key_value;
933 seq = key_char;
934 }
936 return seq ? seq : "'?'";
937 }
939 static char *
940 get_key(enum request request)
941 {
942 static char buf[BUFSIZ];
943 size_t pos = 0;
944 char *sep = "";
945 int i;
947 buf[pos] = 0;
949 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
950 struct keybinding *keybinding = &default_keybindings[i];
952 if (keybinding->request != request)
953 continue;
955 if (!string_format_from(buf, &pos, "%s%s", sep,
956 get_key_name(keybinding->alias)))
957 return "Too many keybindings!";
958 sep = ", ";
959 }
961 return buf;
962 }
964 struct run_request {
965 enum keymap keymap;
966 int key;
967 char cmd[SIZEOF_STR];
968 };
970 static struct run_request *run_request;
971 static size_t run_requests;
973 static enum request
974 add_run_request(enum keymap keymap, int key, int argc, char **argv)
975 {
976 struct run_request *tmp;
977 struct run_request req = { keymap, key };
978 size_t bufpos;
980 for (bufpos = 0; argc > 0; argc--, argv++)
981 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
982 return REQ_NONE;
984 req.cmd[bufpos - 1] = 0;
986 tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
987 if (!tmp)
988 return REQ_NONE;
990 run_request = tmp;
991 run_request[run_requests++] = req;
993 return REQ_NONE + run_requests;
994 }
996 static struct run_request *
997 get_run_request(enum request request)
998 {
999 if (request <= REQ_NONE)
1000 return NULL;
1001 return &run_request[request - REQ_NONE - 1];
1002 }
1004 static void
1005 add_builtin_run_requests(void)
1006 {
1007 struct {
1008 enum keymap keymap;
1009 int key;
1010 char *argv[1];
1011 } reqs[] = {
1012 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
1013 { KEYMAP_GENERIC, 'G', { "git gc" } },
1014 };
1015 int i;
1017 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1018 enum request req;
1020 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1021 if (req != REQ_NONE)
1022 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1023 }
1024 }
1026 /*
1027 * User config file handling.
1028 */
1030 static struct int_map color_map[] = {
1031 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1032 COLOR_MAP(DEFAULT),
1033 COLOR_MAP(BLACK),
1034 COLOR_MAP(BLUE),
1035 COLOR_MAP(CYAN),
1036 COLOR_MAP(GREEN),
1037 COLOR_MAP(MAGENTA),
1038 COLOR_MAP(RED),
1039 COLOR_MAP(WHITE),
1040 COLOR_MAP(YELLOW),
1041 };
1043 #define set_color(color, name) \
1044 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1046 static struct int_map attr_map[] = {
1047 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1048 ATTR_MAP(NORMAL),
1049 ATTR_MAP(BLINK),
1050 ATTR_MAP(BOLD),
1051 ATTR_MAP(DIM),
1052 ATTR_MAP(REVERSE),
1053 ATTR_MAP(STANDOUT),
1054 ATTR_MAP(UNDERLINE),
1055 };
1057 #define set_attribute(attr, name) \
1058 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1060 static int config_lineno;
1061 static bool config_errors;
1062 static char *config_msg;
1064 /* Wants: object fgcolor bgcolor [attr] */
1065 static int
1066 option_color_command(int argc, char *argv[])
1067 {
1068 struct line_info *info;
1070 if (argc != 3 && argc != 4) {
1071 config_msg = "Wrong number of arguments given to color command";
1072 return ERR;
1073 }
1075 info = get_line_info(argv[0], strlen(argv[0]));
1076 if (!info) {
1077 config_msg = "Unknown color name";
1078 return ERR;
1079 }
1081 if (set_color(&info->fg, argv[1]) == ERR ||
1082 set_color(&info->bg, argv[2]) == ERR) {
1083 config_msg = "Unknown color";
1084 return ERR;
1085 }
1087 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1088 config_msg = "Unknown attribute";
1089 return ERR;
1090 }
1092 return OK;
1093 }
1095 /* Wants: name = value */
1096 static int
1097 option_set_command(int argc, char *argv[])
1098 {
1099 if (argc != 3) {
1100 config_msg = "Wrong number of arguments given to set command";
1101 return ERR;
1102 }
1104 if (strcmp(argv[1], "=")) {
1105 config_msg = "No value assigned";
1106 return ERR;
1107 }
1109 if (!strcmp(argv[0], "show-rev-graph")) {
1110 opt_rev_graph = (!strcmp(argv[2], "1") ||
1111 !strcmp(argv[2], "true") ||
1112 !strcmp(argv[2], "yes"));
1113 return OK;
1114 }
1116 if (!strcmp(argv[0], "line-number-interval")) {
1117 opt_num_interval = atoi(argv[2]);
1118 return OK;
1119 }
1121 if (!strcmp(argv[0], "tab-size")) {
1122 opt_tab_size = atoi(argv[2]);
1123 return OK;
1124 }
1126 if (!strcmp(argv[0], "commit-encoding")) {
1127 char *arg = argv[2];
1128 int delimiter = *arg;
1129 int i;
1131 switch (delimiter) {
1132 case '"':
1133 case '\'':
1134 for (arg++, i = 0; arg[i]; i++)
1135 if (arg[i] == delimiter) {
1136 arg[i] = 0;
1137 break;
1138 }
1139 default:
1140 string_ncopy(opt_encoding, arg, strlen(arg));
1141 return OK;
1142 }
1143 }
1145 config_msg = "Unknown variable name";
1146 return ERR;
1147 }
1149 /* Wants: mode request key */
1150 static int
1151 option_bind_command(int argc, char *argv[])
1152 {
1153 enum request request;
1154 int keymap;
1155 int key;
1157 if (argc < 3) {
1158 config_msg = "Wrong number of arguments given to bind command";
1159 return ERR;
1160 }
1162 if (set_keymap(&keymap, argv[0]) == ERR) {
1163 config_msg = "Unknown key map";
1164 return ERR;
1165 }
1167 key = get_key_value(argv[1]);
1168 if (key == ERR) {
1169 config_msg = "Unknown key";
1170 return ERR;
1171 }
1173 request = get_request(argv[2]);
1174 if (request == REQ_NONE) {
1175 const char *obsolete[] = { "cherry-pick" };
1176 size_t namelen = strlen(argv[2]);
1177 int i;
1179 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1180 if (namelen == strlen(obsolete[i]) &&
1181 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1182 config_msg = "Obsolete request name";
1183 return ERR;
1184 }
1185 }
1186 }
1187 if (request == REQ_NONE && *argv[2]++ == '!')
1188 request = add_run_request(keymap, key, argc - 2, argv + 2);
1189 if (request == REQ_NONE) {
1190 config_msg = "Unknown request name";
1191 return ERR;
1192 }
1194 add_keybinding(keymap, request, key);
1196 return OK;
1197 }
1199 static int
1200 set_option(char *opt, char *value)
1201 {
1202 char *argv[16];
1203 int valuelen;
1204 int argc = 0;
1206 /* Tokenize */
1207 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1208 argv[argc++] = value;
1209 value += valuelen;
1211 /* Nothing more to tokenize or last available token. */
1212 if (!*value || argc >= ARRAY_SIZE(argv))
1213 break;
1215 *value++ = 0;
1216 while (isspace(*value))
1217 value++;
1218 }
1220 if (!strcmp(opt, "color"))
1221 return option_color_command(argc, argv);
1223 if (!strcmp(opt, "set"))
1224 return option_set_command(argc, argv);
1226 if (!strcmp(opt, "bind"))
1227 return option_bind_command(argc, argv);
1229 config_msg = "Unknown option command";
1230 return ERR;
1231 }
1233 static int
1234 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1235 {
1236 int status = OK;
1238 config_lineno++;
1239 config_msg = "Internal error";
1241 /* Check for comment markers, since read_properties() will
1242 * only ensure opt and value are split at first " \t". */
1243 optlen = strcspn(opt, "#");
1244 if (optlen == 0)
1245 return OK;
1247 if (opt[optlen] != 0) {
1248 config_msg = "No option value";
1249 status = ERR;
1251 } else {
1252 /* Look for comment endings in the value. */
1253 size_t len = strcspn(value, "#");
1255 if (len < valuelen) {
1256 valuelen = len;
1257 value[valuelen] = 0;
1258 }
1260 status = set_option(opt, value);
1261 }
1263 if (status == ERR) {
1264 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1265 config_lineno, (int) optlen, opt, config_msg);
1266 config_errors = TRUE;
1267 }
1269 /* Always keep going if errors are encountered. */
1270 return OK;
1271 }
1273 static int
1274 load_options(void)
1275 {
1276 char *home = getenv("HOME");
1277 char buf[SIZEOF_STR];
1278 FILE *file;
1280 config_lineno = 0;
1281 config_errors = FALSE;
1283 add_builtin_run_requests();
1285 if (!home || !string_format(buf, "%s/.tigrc", home))
1286 return ERR;
1288 /* It's ok that the file doesn't exist. */
1289 file = fopen(buf, "r");
1290 if (!file)
1291 return OK;
1293 if (read_properties(file, " \t", read_option) == ERR ||
1294 config_errors == TRUE)
1295 fprintf(stderr, "Errors while loading %s.\n", buf);
1297 return OK;
1298 }
1301 /*
1302 * The viewer
1303 */
1305 struct view;
1306 struct view_ops;
1308 /* The display array of active views and the index of the current view. */
1309 static struct view *display[2];
1310 static unsigned int current_view;
1312 /* Reading from the prompt? */
1313 static bool input_mode = FALSE;
1315 #define foreach_displayed_view(view, i) \
1316 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1318 #define displayed_views() (display[1] != NULL ? 2 : 1)
1320 /* Current head and commit ID */
1321 static char ref_blob[SIZEOF_REF] = "";
1322 static char ref_commit[SIZEOF_REF] = "HEAD";
1323 static char ref_head[SIZEOF_REF] = "HEAD";
1325 struct view {
1326 const char *name; /* View name */
1327 const char *cmd_fmt; /* Default command line format */
1328 const char *cmd_env; /* Command line set via environment */
1329 const char *id; /* Points to either of ref_{head,commit,blob} */
1331 struct view_ops *ops; /* View operations */
1333 enum keymap keymap; /* What keymap does this view have */
1335 char cmd[SIZEOF_STR]; /* Command buffer */
1336 char ref[SIZEOF_REF]; /* Hovered commit reference */
1337 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1339 int height, width; /* The width and height of the main window */
1340 WINDOW *win; /* The main window */
1341 WINDOW *title; /* The title window living below the main window */
1343 /* Navigation */
1344 unsigned long offset; /* Offset of the window top */
1345 unsigned long lineno; /* Current line number */
1347 /* Searching */
1348 char grep[SIZEOF_STR]; /* Search string */
1349 regex_t *regex; /* Pre-compiled regex */
1351 /* If non-NULL, points to the view that opened this view. If this view
1352 * is closed tig will switch back to the parent view. */
1353 struct view *parent;
1355 /* Buffering */
1356 unsigned long lines; /* Total number of lines */
1357 struct line *line; /* Line index */
1358 unsigned long line_size;/* Total number of allocated lines */
1359 unsigned int digits; /* Number of digits in the lines member. */
1361 /* Loading */
1362 FILE *pipe;
1363 time_t start_time;
1364 };
1366 struct view_ops {
1367 /* What type of content being displayed. Used in the title bar. */
1368 const char *type;
1369 /* Open and reads in all view content. */
1370 bool (*open)(struct view *view);
1371 /* Read one line; updates view->line. */
1372 bool (*read)(struct view *view, char *data);
1373 /* Draw one line; @lineno must be < view->height. */
1374 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1375 /* Depending on view handle a special requests. */
1376 enum request (*request)(struct view *view, enum request request, struct line *line);
1377 /* Search for regex in a line. */
1378 bool (*grep)(struct view *view, struct line *line);
1379 /* Select line */
1380 void (*select)(struct view *view, struct line *line);
1381 };
1383 static struct view_ops pager_ops;
1384 static struct view_ops main_ops;
1385 static struct view_ops tree_ops;
1386 static struct view_ops blob_ops;
1387 static struct view_ops help_ops;
1388 static struct view_ops status_ops;
1389 static struct view_ops stage_ops;
1391 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1392 { name, cmd, #env, ref, ops, map}
1394 #define VIEW_(id, name, ops, ref) \
1395 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1398 static struct view views[] = {
1399 VIEW_(MAIN, "main", &main_ops, ref_head),
1400 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1401 VIEW_(LOG, "log", &pager_ops, ref_head),
1402 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1403 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1404 VIEW_(HELP, "help", &help_ops, ""),
1405 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1406 VIEW_(STATUS, "status", &status_ops, ""),
1407 VIEW_(STAGE, "stage", &stage_ops, ""),
1408 };
1410 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1412 #define foreach_view(view, i) \
1413 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1415 #define view_is_displayed(view) \
1416 (view == display[0] || view == display[1])
1418 static bool
1419 draw_view_line(struct view *view, unsigned int lineno)
1420 {
1421 struct line *line;
1422 bool selected = (view->offset + lineno == view->lineno);
1423 bool draw_ok;
1425 assert(view_is_displayed(view));
1427 if (view->offset + lineno >= view->lines)
1428 return FALSE;
1430 line = &view->line[view->offset + lineno];
1432 if (selected) {
1433 line->selected = TRUE;
1434 view->ops->select(view, line);
1435 } else if (line->selected) {
1436 line->selected = FALSE;
1437 wmove(view->win, lineno, 0);
1438 wclrtoeol(view->win);
1439 }
1441 scrollok(view->win, FALSE);
1442 draw_ok = view->ops->draw(view, line, lineno, selected);
1443 scrollok(view->win, TRUE);
1445 return draw_ok;
1446 }
1448 static void
1449 redraw_view_from(struct view *view, int lineno)
1450 {
1451 assert(0 <= lineno && lineno < view->height);
1453 for (; lineno < view->height; lineno++) {
1454 if (!draw_view_line(view, lineno))
1455 break;
1456 }
1458 redrawwin(view->win);
1459 if (input_mode)
1460 wnoutrefresh(view->win);
1461 else
1462 wrefresh(view->win);
1463 }
1465 static void
1466 redraw_view(struct view *view)
1467 {
1468 wclear(view->win);
1469 redraw_view_from(view, 0);
1470 }
1473 static void
1474 update_view_title(struct view *view)
1475 {
1476 char buf[SIZEOF_STR];
1477 char state[SIZEOF_STR];
1478 size_t bufpos = 0, statelen = 0;
1480 assert(view_is_displayed(view));
1482 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1483 unsigned int view_lines = view->offset + view->height;
1484 unsigned int lines = view->lines
1485 ? MIN(view_lines, view->lines) * 100 / view->lines
1486 : 0;
1488 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1489 view->ops->type,
1490 view->lineno + 1,
1491 view->lines,
1492 lines);
1494 if (view->pipe) {
1495 time_t secs = time(NULL) - view->start_time;
1497 /* Three git seconds are a long time ... */
1498 if (secs > 2)
1499 string_format_from(state, &statelen, " %lds", secs);
1500 }
1501 }
1503 string_format_from(buf, &bufpos, "[%s]", view->name);
1504 if (*view->ref && bufpos < view->width) {
1505 size_t refsize = strlen(view->ref);
1506 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1508 if (minsize < view->width)
1509 refsize = view->width - minsize + 7;
1510 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1511 }
1513 if (statelen && bufpos < view->width) {
1514 string_format_from(buf, &bufpos, " %s", state);
1515 }
1517 if (view == display[current_view])
1518 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1519 else
1520 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1522 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1523 wclrtoeol(view->title);
1524 wmove(view->title, 0, view->width - 1);
1526 if (input_mode)
1527 wnoutrefresh(view->title);
1528 else
1529 wrefresh(view->title);
1530 }
1532 static void
1533 resize_display(void)
1534 {
1535 int offset, i;
1536 struct view *base = display[0];
1537 struct view *view = display[1] ? display[1] : display[0];
1539 /* Setup window dimensions */
1541 getmaxyx(stdscr, base->height, base->width);
1543 /* Make room for the status window. */
1544 base->height -= 1;
1546 if (view != base) {
1547 /* Horizontal split. */
1548 view->width = base->width;
1549 view->height = SCALE_SPLIT_VIEW(base->height);
1550 base->height -= view->height;
1552 /* Make room for the title bar. */
1553 view->height -= 1;
1554 }
1556 /* Make room for the title bar. */
1557 base->height -= 1;
1559 offset = 0;
1561 foreach_displayed_view (view, i) {
1562 if (!view->win) {
1563 view->win = newwin(view->height, 0, offset, 0);
1564 if (!view->win)
1565 die("Failed to create %s view", view->name);
1567 scrollok(view->win, TRUE);
1569 view->title = newwin(1, 0, offset + view->height, 0);
1570 if (!view->title)
1571 die("Failed to create title window");
1573 } else {
1574 wresize(view->win, view->height, view->width);
1575 mvwin(view->win, offset, 0);
1576 mvwin(view->title, offset + view->height, 0);
1577 }
1579 offset += view->height + 1;
1580 }
1581 }
1583 static void
1584 redraw_display(void)
1585 {
1586 struct view *view;
1587 int i;
1589 foreach_displayed_view (view, i) {
1590 redraw_view(view);
1591 update_view_title(view);
1592 }
1593 }
1595 static void
1596 update_display_cursor(struct view *view)
1597 {
1598 /* Move the cursor to the right-most column of the cursor line.
1599 *
1600 * XXX: This could turn out to be a bit expensive, but it ensures that
1601 * the cursor does not jump around. */
1602 if (view->lines) {
1603 wmove(view->win, view->lineno - view->offset, view->width - 1);
1604 wrefresh(view->win);
1605 }
1606 }
1608 /*
1609 * Navigation
1610 */
1612 /* Scrolling backend */
1613 static void
1614 do_scroll_view(struct view *view, int lines)
1615 {
1616 bool redraw_current_line = FALSE;
1618 /* The rendering expects the new offset. */
1619 view->offset += lines;
1621 assert(0 <= view->offset && view->offset < view->lines);
1622 assert(lines);
1624 /* Move current line into the view. */
1625 if (view->lineno < view->offset) {
1626 view->lineno = view->offset;
1627 redraw_current_line = TRUE;
1628 } else if (view->lineno >= view->offset + view->height) {
1629 view->lineno = view->offset + view->height - 1;
1630 redraw_current_line = TRUE;
1631 }
1633 assert(view->offset <= view->lineno && view->lineno < view->lines);
1635 /* Redraw the whole screen if scrolling is pointless. */
1636 if (view->height < ABS(lines)) {
1637 redraw_view(view);
1639 } else {
1640 int line = lines > 0 ? view->height - lines : 0;
1641 int end = line + ABS(lines);
1643 wscrl(view->win, lines);
1645 for (; line < end; line++) {
1646 if (!draw_view_line(view, line))
1647 break;
1648 }
1650 if (redraw_current_line)
1651 draw_view_line(view, view->lineno - view->offset);
1652 }
1654 redrawwin(view->win);
1655 wrefresh(view->win);
1656 report("");
1657 }
1659 /* Scroll frontend */
1660 static void
1661 scroll_view(struct view *view, enum request request)
1662 {
1663 int lines = 1;
1665 assert(view_is_displayed(view));
1667 switch (request) {
1668 case REQ_SCROLL_PAGE_DOWN:
1669 lines = view->height;
1670 case REQ_SCROLL_LINE_DOWN:
1671 if (view->offset + lines > view->lines)
1672 lines = view->lines - view->offset;
1674 if (lines == 0 || view->offset + view->height >= view->lines) {
1675 report("Cannot scroll beyond the last line");
1676 return;
1677 }
1678 break;
1680 case REQ_SCROLL_PAGE_UP:
1681 lines = view->height;
1682 case REQ_SCROLL_LINE_UP:
1683 if (lines > view->offset)
1684 lines = view->offset;
1686 if (lines == 0) {
1687 report("Cannot scroll beyond the first line");
1688 return;
1689 }
1691 lines = -lines;
1692 break;
1694 default:
1695 die("request %d not handled in switch", request);
1696 }
1698 do_scroll_view(view, lines);
1699 }
1701 /* Cursor moving */
1702 static void
1703 move_view(struct view *view, enum request request)
1704 {
1705 int scroll_steps = 0;
1706 int steps;
1708 switch (request) {
1709 case REQ_MOVE_FIRST_LINE:
1710 steps = -view->lineno;
1711 break;
1713 case REQ_MOVE_LAST_LINE:
1714 steps = view->lines - view->lineno - 1;
1715 break;
1717 case REQ_MOVE_PAGE_UP:
1718 steps = view->height > view->lineno
1719 ? -view->lineno : -view->height;
1720 break;
1722 case REQ_MOVE_PAGE_DOWN:
1723 steps = view->lineno + view->height >= view->lines
1724 ? view->lines - view->lineno - 1 : view->height;
1725 break;
1727 case REQ_MOVE_UP:
1728 steps = -1;
1729 break;
1731 case REQ_MOVE_DOWN:
1732 steps = 1;
1733 break;
1735 default:
1736 die("request %d not handled in switch", request);
1737 }
1739 if (steps <= 0 && view->lineno == 0) {
1740 report("Cannot move beyond the first line");
1741 return;
1743 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1744 report("Cannot move beyond the last line");
1745 return;
1746 }
1748 /* Move the current line */
1749 view->lineno += steps;
1750 assert(0 <= view->lineno && view->lineno < view->lines);
1752 /* Check whether the view needs to be scrolled */
1753 if (view->lineno < view->offset ||
1754 view->lineno >= view->offset + view->height) {
1755 scroll_steps = steps;
1756 if (steps < 0 && -steps > view->offset) {
1757 scroll_steps = -view->offset;
1759 } else if (steps > 0) {
1760 if (view->lineno == view->lines - 1 &&
1761 view->lines > view->height) {
1762 scroll_steps = view->lines - view->offset - 1;
1763 if (scroll_steps >= view->height)
1764 scroll_steps -= view->height - 1;
1765 }
1766 }
1767 }
1769 if (!view_is_displayed(view)) {
1770 view->offset += scroll_steps;
1771 assert(0 <= view->offset && view->offset < view->lines);
1772 view->ops->select(view, &view->line[view->lineno]);
1773 return;
1774 }
1776 /* Repaint the old "current" line if we be scrolling */
1777 if (ABS(steps) < view->height)
1778 draw_view_line(view, view->lineno - steps - view->offset);
1780 if (scroll_steps) {
1781 do_scroll_view(view, scroll_steps);
1782 return;
1783 }
1785 /* Draw the current line */
1786 draw_view_line(view, view->lineno - view->offset);
1788 redrawwin(view->win);
1789 wrefresh(view->win);
1790 report("");
1791 }
1794 /*
1795 * Searching
1796 */
1798 static void search_view(struct view *view, enum request request);
1800 static bool
1801 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1802 {
1803 assert(view_is_displayed(view));
1805 if (!view->ops->grep(view, line))
1806 return FALSE;
1808 if (lineno - view->offset >= view->height) {
1809 view->offset = lineno;
1810 view->lineno = lineno;
1811 redraw_view(view);
1813 } else {
1814 unsigned long old_lineno = view->lineno - view->offset;
1816 view->lineno = lineno;
1817 draw_view_line(view, old_lineno);
1819 draw_view_line(view, view->lineno - view->offset);
1820 redrawwin(view->win);
1821 wrefresh(view->win);
1822 }
1824 report("Line %ld matches '%s'", lineno + 1, view->grep);
1825 return TRUE;
1826 }
1828 static void
1829 find_next(struct view *view, enum request request)
1830 {
1831 unsigned long lineno = view->lineno;
1832 int direction;
1834 if (!*view->grep) {
1835 if (!*opt_search)
1836 report("No previous search");
1837 else
1838 search_view(view, request);
1839 return;
1840 }
1842 switch (request) {
1843 case REQ_SEARCH:
1844 case REQ_FIND_NEXT:
1845 direction = 1;
1846 break;
1848 case REQ_SEARCH_BACK:
1849 case REQ_FIND_PREV:
1850 direction = -1;
1851 break;
1853 default:
1854 return;
1855 }
1857 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1858 lineno += direction;
1860 /* Note, lineno is unsigned long so will wrap around in which case it
1861 * will become bigger than view->lines. */
1862 for (; lineno < view->lines; lineno += direction) {
1863 struct line *line = &view->line[lineno];
1865 if (find_next_line(view, lineno, line))
1866 return;
1867 }
1869 report("No match found for '%s'", view->grep);
1870 }
1872 static void
1873 search_view(struct view *view, enum request request)
1874 {
1875 int regex_err;
1877 if (view->regex) {
1878 regfree(view->regex);
1879 *view->grep = 0;
1880 } else {
1881 view->regex = calloc(1, sizeof(*view->regex));
1882 if (!view->regex)
1883 return;
1884 }
1886 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1887 if (regex_err != 0) {
1888 char buf[SIZEOF_STR] = "unknown error";
1890 regerror(regex_err, view->regex, buf, sizeof(buf));
1891 report("Search failed: %s", buf);
1892 return;
1893 }
1895 string_copy(view->grep, opt_search);
1897 find_next(view, request);
1898 }
1900 /*
1901 * Incremental updating
1902 */
1904 static void
1905 end_update(struct view *view)
1906 {
1907 if (!view->pipe)
1908 return;
1909 set_nonblocking_input(FALSE);
1910 if (view->pipe == stdin)
1911 fclose(view->pipe);
1912 else
1913 pclose(view->pipe);
1914 view->pipe = NULL;
1915 }
1917 static bool
1918 begin_update(struct view *view)
1919 {
1920 if (view->pipe)
1921 end_update(view);
1923 if (opt_cmd[0]) {
1924 string_copy(view->cmd, opt_cmd);
1925 opt_cmd[0] = 0;
1926 /* When running random commands, initially show the
1927 * command in the title. However, it maybe later be
1928 * overwritten if a commit line is selected. */
1929 if (view == VIEW(REQ_VIEW_PAGER))
1930 string_copy(view->ref, view->cmd);
1931 else
1932 view->ref[0] = 0;
1934 } else if (view == VIEW(REQ_VIEW_TREE)) {
1935 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1936 char path[SIZEOF_STR];
1938 if (strcmp(view->vid, view->id))
1939 opt_path[0] = path[0] = 0;
1940 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1941 return FALSE;
1943 if (!string_format(view->cmd, format, view->id, path))
1944 return FALSE;
1946 } else {
1947 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1948 const char *id = view->id;
1950 if (!string_format(view->cmd, format, id, id, id, id, id))
1951 return FALSE;
1953 /* Put the current ref_* value to the view title ref
1954 * member. This is needed by the blob view. Most other
1955 * views sets it automatically after loading because the
1956 * first line is a commit line. */
1957 string_copy_rev(view->ref, view->id);
1958 }
1960 /* Special case for the pager view. */
1961 if (opt_pipe) {
1962 view->pipe = opt_pipe;
1963 opt_pipe = NULL;
1964 } else {
1965 view->pipe = popen(view->cmd, "r");
1966 }
1968 if (!view->pipe)
1969 return FALSE;
1971 set_nonblocking_input(TRUE);
1973 view->offset = 0;
1974 view->lines = 0;
1975 view->lineno = 0;
1976 string_copy_rev(view->vid, view->id);
1978 if (view->line) {
1979 int i;
1981 for (i = 0; i < view->lines; i++)
1982 if (view->line[i].data)
1983 free(view->line[i].data);
1985 free(view->line);
1986 view->line = NULL;
1987 }
1989 view->start_time = time(NULL);
1991 return TRUE;
1992 }
1994 static struct line *
1995 realloc_lines(struct view *view, size_t line_size)
1996 {
1997 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1999 if (!tmp)
2000 return NULL;
2002 view->line = tmp;
2003 view->line_size = line_size;
2004 return view->line;
2005 }
2007 static bool
2008 update_view(struct view *view)
2009 {
2010 char in_buffer[BUFSIZ];
2011 char out_buffer[BUFSIZ * 2];
2012 char *line;
2013 /* The number of lines to read. If too low it will cause too much
2014 * redrawing (and possible flickering), if too high responsiveness
2015 * will suffer. */
2016 unsigned long lines = view->height;
2017 int redraw_from = -1;
2019 if (!view->pipe)
2020 return TRUE;
2022 /* Only redraw if lines are visible. */
2023 if (view->offset + view->height >= view->lines)
2024 redraw_from = view->lines - view->offset;
2026 /* FIXME: This is probably not perfect for backgrounded views. */
2027 if (!realloc_lines(view, view->lines + lines))
2028 goto alloc_error;
2030 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2031 size_t linelen = strlen(line);
2033 if (linelen)
2034 line[linelen - 1] = 0;
2036 if (opt_iconv != ICONV_NONE) {
2037 ICONV_CONST char *inbuf = line;
2038 size_t inlen = linelen;
2040 char *outbuf = out_buffer;
2041 size_t outlen = sizeof(out_buffer);
2043 size_t ret;
2045 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2046 if (ret != (size_t) -1) {
2047 line = out_buffer;
2048 linelen = strlen(out_buffer);
2049 }
2050 }
2052 if (!view->ops->read(view, line))
2053 goto alloc_error;
2055 if (lines-- == 1)
2056 break;
2057 }
2059 {
2060 int digits;
2062 lines = view->lines;
2063 for (digits = 0; lines; digits++)
2064 lines /= 10;
2066 /* Keep the displayed view in sync with line number scaling. */
2067 if (digits != view->digits) {
2068 view->digits = digits;
2069 redraw_from = 0;
2070 }
2071 }
2073 if (!view_is_displayed(view))
2074 goto check_pipe;
2076 if (view == VIEW(REQ_VIEW_TREE)) {
2077 /* Clear the view and redraw everything since the tree sorting
2078 * might have rearranged things. */
2079 redraw_view(view);
2081 } else if (redraw_from >= 0) {
2082 /* If this is an incremental update, redraw the previous line
2083 * since for commits some members could have changed when
2084 * loading the main view. */
2085 if (redraw_from > 0)
2086 redraw_from--;
2088 /* Since revision graph visualization requires knowledge
2089 * about the parent commit, it causes a further one-off
2090 * needed to be redrawn for incremental updates. */
2091 if (redraw_from > 0 && opt_rev_graph)
2092 redraw_from--;
2094 /* Incrementally draw avoids flickering. */
2095 redraw_view_from(view, redraw_from);
2096 }
2098 /* Update the title _after_ the redraw so that if the redraw picks up a
2099 * commit reference in view->ref it'll be available here. */
2100 update_view_title(view);
2102 check_pipe:
2103 if (ferror(view->pipe)) {
2104 report("Failed to read: %s", strerror(errno));
2105 goto end;
2107 } else if (feof(view->pipe)) {
2108 report("");
2109 goto end;
2110 }
2112 return TRUE;
2114 alloc_error:
2115 report("Allocation failure");
2117 end:
2118 view->ops->read(view, NULL);
2119 end_update(view);
2120 return FALSE;
2121 }
2123 static struct line *
2124 add_line_data(struct view *view, void *data, enum line_type type)
2125 {
2126 struct line *line = &view->line[view->lines++];
2128 memset(line, 0, sizeof(*line));
2129 line->type = type;
2130 line->data = data;
2132 return line;
2133 }
2135 static struct line *
2136 add_line_text(struct view *view, char *data, enum line_type type)
2137 {
2138 if (data)
2139 data = strdup(data);
2141 return data ? add_line_data(view, data, type) : NULL;
2142 }
2145 /*
2146 * View opening
2147 */
2149 enum open_flags {
2150 OPEN_DEFAULT = 0, /* Use default view switching. */
2151 OPEN_SPLIT = 1, /* Split current view. */
2152 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2153 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2154 };
2156 static void
2157 open_view(struct view *prev, enum request request, enum open_flags flags)
2158 {
2159 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2160 bool split = !!(flags & OPEN_SPLIT);
2161 bool reload = !!(flags & OPEN_RELOAD);
2162 struct view *view = VIEW(request);
2163 int nviews = displayed_views();
2164 struct view *base_view = display[0];
2166 if (view == prev && nviews == 1 && !reload) {
2167 report("Already in %s view", view->name);
2168 return;
2169 }
2171 if (view->ops->open) {
2172 if (!view->ops->open(view)) {
2173 report("Failed to load %s view", view->name);
2174 return;
2175 }
2177 } else if ((reload || strcmp(view->vid, view->id)) &&
2178 !begin_update(view)) {
2179 report("Failed to load %s view", view->name);
2180 return;
2181 }
2183 if (split) {
2184 display[1] = view;
2185 if (!backgrounded)
2186 current_view = 1;
2187 } else {
2188 /* Maximize the current view. */
2189 memset(display, 0, sizeof(display));
2190 current_view = 0;
2191 display[current_view] = view;
2192 }
2194 /* Resize the view when switching between split- and full-screen,
2195 * or when switching between two different full-screen views. */
2196 if (nviews != displayed_views() ||
2197 (nviews == 1 && base_view != display[0]))
2198 resize_display();
2200 if (split && prev->lineno - prev->offset >= prev->height) {
2201 /* Take the title line into account. */
2202 int lines = prev->lineno - prev->offset - prev->height + 1;
2204 /* Scroll the view that was split if the current line is
2205 * outside the new limited view. */
2206 do_scroll_view(prev, lines);
2207 }
2209 if (prev && view != prev) {
2210 if (split && !backgrounded) {
2211 /* "Blur" the previous view. */
2212 update_view_title(prev);
2213 }
2215 view->parent = prev;
2216 }
2218 if (view->pipe && view->lines == 0) {
2219 /* Clear the old view and let the incremental updating refill
2220 * the screen. */
2221 wclear(view->win);
2222 report("");
2223 } else {
2224 redraw_view(view);
2225 report("");
2226 }
2228 /* If the view is backgrounded the above calls to report()
2229 * won't redraw the view title. */
2230 if (backgrounded)
2231 update_view_title(view);
2232 }
2234 static void
2235 open_external_viewer(const char *cmd)
2236 {
2237 def_prog_mode(); /* save current tty modes */
2238 endwin(); /* restore original tty modes */
2239 system(cmd);
2240 fprintf(stderr, "Press Enter to continue");
2241 getc(stdin);
2242 reset_prog_mode();
2243 redraw_display();
2244 }
2246 static void
2247 open_mergetool(const char *file)
2248 {
2249 char cmd[SIZEOF_STR];
2250 char file_sq[SIZEOF_STR];
2252 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2253 string_format(cmd, "git mergetool %s", file_sq)) {
2254 open_external_viewer(cmd);
2255 }
2256 }
2258 static void
2259 open_editor(bool from_root, const char *file)
2260 {
2261 char cmd[SIZEOF_STR];
2262 char file_sq[SIZEOF_STR];
2263 char *editor;
2264 char *prefix = from_root ? opt_cdup : "";
2266 editor = getenv("GIT_EDITOR");
2267 if (!editor && *opt_editor)
2268 editor = opt_editor;
2269 if (!editor)
2270 editor = getenv("VISUAL");
2271 if (!editor)
2272 editor = getenv("EDITOR");
2273 if (!editor)
2274 editor = "vi";
2276 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2277 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2278 open_external_viewer(cmd);
2279 }
2280 }
2282 static void
2283 open_run_request(enum request request)
2284 {
2285 struct run_request *req = get_run_request(request);
2286 char buf[SIZEOF_STR * 2];
2287 size_t bufpos;
2288 char *cmd;
2290 if (!req) {
2291 report("Unknown run request");
2292 return;
2293 }
2295 bufpos = 0;
2296 cmd = req->cmd;
2298 while (cmd) {
2299 char *next = strstr(cmd, "%(");
2300 int len = next - cmd;
2301 char *value;
2303 if (!next) {
2304 len = strlen(cmd);
2305 value = "";
2307 } else if (!strncmp(next, "%(head)", 7)) {
2308 value = ref_head;
2310 } else if (!strncmp(next, "%(commit)", 9)) {
2311 value = ref_commit;
2313 } else if (!strncmp(next, "%(blob)", 7)) {
2314 value = ref_blob;
2316 } else {
2317 report("Unknown replacement in run request: `%s`", req->cmd);
2318 return;
2319 }
2321 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2322 return;
2324 if (next)
2325 next = strchr(next, ')') + 1;
2326 cmd = next;
2327 }
2329 open_external_viewer(buf);
2330 }
2332 /*
2333 * User request switch noodle
2334 */
2336 static int
2337 view_driver(struct view *view, enum request request)
2338 {
2339 int i;
2341 if (request == REQ_NONE) {
2342 doupdate();
2343 return TRUE;
2344 }
2346 if (request > REQ_NONE) {
2347 open_run_request(request);
2348 return TRUE;
2349 }
2351 if (view && view->lines) {
2352 request = view->ops->request(view, request, &view->line[view->lineno]);
2353 if (request == REQ_NONE)
2354 return TRUE;
2355 }
2357 switch (request) {
2358 case REQ_MOVE_UP:
2359 case REQ_MOVE_DOWN:
2360 case REQ_MOVE_PAGE_UP:
2361 case REQ_MOVE_PAGE_DOWN:
2362 case REQ_MOVE_FIRST_LINE:
2363 case REQ_MOVE_LAST_LINE:
2364 move_view(view, request);
2365 break;
2367 case REQ_SCROLL_LINE_DOWN:
2368 case REQ_SCROLL_LINE_UP:
2369 case REQ_SCROLL_PAGE_DOWN:
2370 case REQ_SCROLL_PAGE_UP:
2371 scroll_view(view, request);
2372 break;
2374 case REQ_VIEW_BLOB:
2375 if (!ref_blob[0]) {
2376 report("No file chosen, press %s to open tree view",
2377 get_key(REQ_VIEW_TREE));
2378 break;
2379 }
2380 open_view(view, request, OPEN_DEFAULT);
2381 break;
2383 case REQ_VIEW_PAGER:
2384 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2385 report("No pager content, press %s to run command from prompt",
2386 get_key(REQ_PROMPT));
2387 break;
2388 }
2389 open_view(view, request, OPEN_DEFAULT);
2390 break;
2392 case REQ_VIEW_STAGE:
2393 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2394 report("No stage content, press %s to open the status view and choose file",
2395 get_key(REQ_VIEW_STATUS));
2396 break;
2397 }
2398 open_view(view, request, OPEN_DEFAULT);
2399 break;
2401 case REQ_VIEW_STATUS:
2402 if (opt_is_inside_work_tree == FALSE) {
2403 report("The status view requires a working tree");
2404 break;
2405 }
2406 open_view(view, request, OPEN_DEFAULT);
2407 break;
2409 case REQ_VIEW_MAIN:
2410 case REQ_VIEW_DIFF:
2411 case REQ_VIEW_LOG:
2412 case REQ_VIEW_TREE:
2413 case REQ_VIEW_HELP:
2414 open_view(view, request, OPEN_DEFAULT);
2415 break;
2417 case REQ_NEXT:
2418 case REQ_PREVIOUS:
2419 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2421 if ((view == VIEW(REQ_VIEW_DIFF) &&
2422 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2423 (view == VIEW(REQ_VIEW_STAGE) &&
2424 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2425 (view == VIEW(REQ_VIEW_BLOB) &&
2426 view->parent == VIEW(REQ_VIEW_TREE))) {
2427 int line;
2429 view = view->parent;
2430 line = view->lineno;
2431 move_view(view, request);
2432 if (view_is_displayed(view))
2433 update_view_title(view);
2434 if (line != view->lineno)
2435 view->ops->request(view, REQ_ENTER,
2436 &view->line[view->lineno]);
2438 } else {
2439 move_view(view, request);
2440 }
2441 break;
2443 case REQ_VIEW_NEXT:
2444 {
2445 int nviews = displayed_views();
2446 int next_view = (current_view + 1) % nviews;
2448 if (next_view == current_view) {
2449 report("Only one view is displayed");
2450 break;
2451 }
2453 current_view = next_view;
2454 /* Blur out the title of the previous view. */
2455 update_view_title(view);
2456 report("");
2457 break;
2458 }
2459 case REQ_REFRESH:
2460 report("Refreshing is not yet supported for the %s view", view->name);
2461 break;
2463 case REQ_TOGGLE_LINENO:
2464 opt_line_number = !opt_line_number;
2465 redraw_display();
2466 break;
2468 case REQ_TOGGLE_REV_GRAPH:
2469 opt_rev_graph = !opt_rev_graph;
2470 redraw_display();
2471 break;
2473 case REQ_PROMPT:
2474 /* Always reload^Wrerun commands from the prompt. */
2475 open_view(view, opt_request, OPEN_RELOAD);
2476 break;
2478 case REQ_SEARCH:
2479 case REQ_SEARCH_BACK:
2480 search_view(view, request);
2481 break;
2483 case REQ_FIND_NEXT:
2484 case REQ_FIND_PREV:
2485 find_next(view, request);
2486 break;
2488 case REQ_STOP_LOADING:
2489 for (i = 0; i < ARRAY_SIZE(views); i++) {
2490 view = &views[i];
2491 if (view->pipe)
2492 report("Stopped loading the %s view", view->name),
2493 end_update(view);
2494 }
2495 break;
2497 case REQ_SHOW_VERSION:
2498 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2499 return TRUE;
2501 case REQ_SCREEN_RESIZE:
2502 resize_display();
2503 /* Fall-through */
2504 case REQ_SCREEN_REDRAW:
2505 redraw_display();
2506 break;
2508 case REQ_EDIT:
2509 report("Nothing to edit");
2510 break;
2513 case REQ_ENTER:
2514 report("Nothing to enter");
2515 break;
2518 case REQ_VIEW_CLOSE:
2519 /* XXX: Mark closed views by letting view->parent point to the
2520 * view itself. Parents to closed view should never be
2521 * followed. */
2522 if (view->parent &&
2523 view->parent->parent != view->parent) {
2524 memset(display, 0, sizeof(display));
2525 current_view = 0;
2526 display[current_view] = view->parent;
2527 view->parent = view;
2528 resize_display();
2529 redraw_display();
2530 break;
2531 }
2532 /* Fall-through */
2533 case REQ_QUIT:
2534 return FALSE;
2536 default:
2537 /* An unknown key will show most commonly used commands. */
2538 report("Unknown key, press 'h' for help");
2539 return TRUE;
2540 }
2542 return TRUE;
2543 }
2546 /*
2547 * Pager backend
2548 */
2550 static bool
2551 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2552 {
2553 char *text = line->data;
2554 enum line_type type = line->type;
2555 int textlen = strlen(text);
2556 int attr;
2558 wmove(view->win, lineno, 0);
2560 if (selected) {
2561 type = LINE_CURSOR;
2562 wchgat(view->win, -1, 0, type, NULL);
2563 }
2565 attr = get_line_attr(type);
2566 wattrset(view->win, attr);
2568 if (opt_line_number || opt_tab_size < TABSIZE) {
2569 static char spaces[] = " ";
2570 int col_offset = 0, col = 0;
2572 if (opt_line_number) {
2573 unsigned long real_lineno = view->offset + lineno + 1;
2575 if (real_lineno == 1 ||
2576 (real_lineno % opt_num_interval) == 0) {
2577 wprintw(view->win, "%.*d", view->digits, real_lineno);
2579 } else {
2580 waddnstr(view->win, spaces,
2581 MIN(view->digits, STRING_SIZE(spaces)));
2582 }
2583 waddstr(view->win, ": ");
2584 col_offset = view->digits + 2;
2585 }
2587 while (text && col_offset + col < view->width) {
2588 int cols_max = view->width - col_offset - col;
2589 char *pos = text;
2590 int cols;
2592 if (*text == '\t') {
2593 text++;
2594 assert(sizeof(spaces) > TABSIZE);
2595 pos = spaces;
2596 cols = opt_tab_size - (col % opt_tab_size);
2598 } else {
2599 text = strchr(text, '\t');
2600 cols = line ? text - pos : strlen(pos);
2601 }
2603 waddnstr(view->win, pos, MIN(cols, cols_max));
2604 col += cols;
2605 }
2607 } else {
2608 int col = 0, pos = 0;
2610 for (; pos < textlen && col < view->width; pos++, col++)
2611 if (text[pos] == '\t')
2612 col += TABSIZE - (col % TABSIZE) - 1;
2614 waddnstr(view->win, text, pos);
2615 }
2617 return TRUE;
2618 }
2620 static bool
2621 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2622 {
2623 char refbuf[SIZEOF_STR];
2624 char *ref = NULL;
2625 FILE *pipe;
2627 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2628 return TRUE;
2630 pipe = popen(refbuf, "r");
2631 if (!pipe)
2632 return TRUE;
2634 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2635 ref = chomp_string(ref);
2636 pclose(pipe);
2638 if (!ref || !*ref)
2639 return TRUE;
2641 /* This is the only fatal call, since it can "corrupt" the buffer. */
2642 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2643 return FALSE;
2645 return TRUE;
2646 }
2648 static void
2649 add_pager_refs(struct view *view, struct line *line)
2650 {
2651 char buf[SIZEOF_STR];
2652 char *commit_id = line->data + STRING_SIZE("commit ");
2653 struct ref **refs;
2654 size_t bufpos = 0, refpos = 0;
2655 const char *sep = "Refs: ";
2656 bool is_tag = FALSE;
2658 assert(line->type == LINE_COMMIT);
2660 refs = get_refs(commit_id);
2661 if (!refs) {
2662 if (view == VIEW(REQ_VIEW_DIFF))
2663 goto try_add_describe_ref;
2664 return;
2665 }
2667 do {
2668 struct ref *ref = refs[refpos];
2669 char *fmt = ref->tag ? "%s[%s]" :
2670 ref->remote ? "%s<%s>" : "%s%s";
2672 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2673 return;
2674 sep = ", ";
2675 if (ref->tag)
2676 is_tag = TRUE;
2677 } while (refs[refpos++]->next);
2679 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2680 try_add_describe_ref:
2681 /* Add <tag>-g<commit_id> "fake" reference. */
2682 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2683 return;
2684 }
2686 if (bufpos == 0)
2687 return;
2689 if (!realloc_lines(view, view->line_size + 1))
2690 return;
2692 add_line_text(view, buf, LINE_PP_REFS);
2693 }
2695 static bool
2696 pager_read(struct view *view, char *data)
2697 {
2698 struct line *line;
2700 if (!data)
2701 return TRUE;
2703 line = add_line_text(view, data, get_line_type(data));
2704 if (!line)
2705 return FALSE;
2707 if (line->type == LINE_COMMIT &&
2708 (view == VIEW(REQ_VIEW_DIFF) ||
2709 view == VIEW(REQ_VIEW_LOG)))
2710 add_pager_refs(view, line);
2712 return TRUE;
2713 }
2715 static enum request
2716 pager_request(struct view *view, enum request request, struct line *line)
2717 {
2718 int split = 0;
2720 if (request != REQ_ENTER)
2721 return request;
2723 if (line->type == LINE_COMMIT &&
2724 (view == VIEW(REQ_VIEW_LOG) ||
2725 view == VIEW(REQ_VIEW_PAGER))) {
2726 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2727 split = 1;
2728 }
2730 /* Always scroll the view even if it was split. That way
2731 * you can use Enter to scroll through the log view and
2732 * split open each commit diff. */
2733 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2735 /* FIXME: A minor workaround. Scrolling the view will call report("")
2736 * but if we are scrolling a non-current view this won't properly
2737 * update the view title. */
2738 if (split)
2739 update_view_title(view);
2741 return REQ_NONE;
2742 }
2744 static bool
2745 pager_grep(struct view *view, struct line *line)
2746 {
2747 regmatch_t pmatch;
2748 char *text = line->data;
2750 if (!*text)
2751 return FALSE;
2753 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2754 return FALSE;
2756 return TRUE;
2757 }
2759 static void
2760 pager_select(struct view *view, struct line *line)
2761 {
2762 if (line->type == LINE_COMMIT) {
2763 char *text = line->data + STRING_SIZE("commit ");
2765 if (view != VIEW(REQ_VIEW_PAGER))
2766 string_copy_rev(view->ref, text);
2767 string_copy_rev(ref_commit, text);
2768 }
2769 }
2771 static struct view_ops pager_ops = {
2772 "line",
2773 NULL,
2774 pager_read,
2775 pager_draw,
2776 pager_request,
2777 pager_grep,
2778 pager_select,
2779 };
2782 /*
2783 * Help backend
2784 */
2786 static bool
2787 help_open(struct view *view)
2788 {
2789 char buf[BUFSIZ];
2790 int lines = ARRAY_SIZE(req_info) + 2;
2791 int i;
2793 if (view->lines > 0)
2794 return TRUE;
2796 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2797 if (!req_info[i].request)
2798 lines++;
2800 lines += run_requests + 1;
2802 view->line = calloc(lines, sizeof(*view->line));
2803 if (!view->line)
2804 return FALSE;
2806 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2808 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2809 char *key;
2811 if (req_info[i].request == REQ_NONE)
2812 continue;
2814 if (!req_info[i].request) {
2815 add_line_text(view, "", LINE_DEFAULT);
2816 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2817 continue;
2818 }
2820 key = get_key(req_info[i].request);
2821 if (!*key)
2822 key = "(no key defined)";
2824 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2825 continue;
2827 add_line_text(view, buf, LINE_DEFAULT);
2828 }
2830 if (run_requests) {
2831 add_line_text(view, "", LINE_DEFAULT);
2832 add_line_text(view, "External commands:", LINE_DEFAULT);
2833 }
2835 for (i = 0; i < run_requests; i++) {
2836 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2837 char *key;
2839 if (!req)
2840 continue;
2842 key = get_key_name(req->key);
2843 if (!*key)
2844 key = "(no key defined)";
2846 if (!string_format(buf, " %-10s %-14s `%s`",
2847 keymap_table[req->keymap].name,
2848 key, req->cmd))
2849 continue;
2851 add_line_text(view, buf, LINE_DEFAULT);
2852 }
2854 return TRUE;
2855 }
2857 static struct view_ops help_ops = {
2858 "line",
2859 help_open,
2860 NULL,
2861 pager_draw,
2862 pager_request,
2863 pager_grep,
2864 pager_select,
2865 };
2868 /*
2869 * Tree backend
2870 */
2872 struct tree_stack_entry {
2873 struct tree_stack_entry *prev; /* Entry below this in the stack */
2874 unsigned long lineno; /* Line number to restore */
2875 char *name; /* Position of name in opt_path */
2876 };
2878 /* The top of the path stack. */
2879 static struct tree_stack_entry *tree_stack = NULL;
2880 unsigned long tree_lineno = 0;
2882 static void
2883 pop_tree_stack_entry(void)
2884 {
2885 struct tree_stack_entry *entry = tree_stack;
2887 tree_lineno = entry->lineno;
2888 entry->name[0] = 0;
2889 tree_stack = entry->prev;
2890 free(entry);
2891 }
2893 static void
2894 push_tree_stack_entry(char *name, unsigned long lineno)
2895 {
2896 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2897 size_t pathlen = strlen(opt_path);
2899 if (!entry)
2900 return;
2902 entry->prev = tree_stack;
2903 entry->name = opt_path + pathlen;
2904 tree_stack = entry;
2906 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2907 pop_tree_stack_entry();
2908 return;
2909 }
2911 /* Move the current line to the first tree entry. */
2912 tree_lineno = 1;
2913 entry->lineno = lineno;
2914 }
2916 /* Parse output from git-ls-tree(1):
2917 *
2918 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2919 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2920 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2921 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2922 */
2924 #define SIZEOF_TREE_ATTR \
2925 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2927 #define TREE_UP_FORMAT "040000 tree %s\t.."
2929 static int
2930 tree_compare_entry(enum line_type type1, char *name1,
2931 enum line_type type2, char *name2)
2932 {
2933 if (type1 != type2) {
2934 if (type1 == LINE_TREE_DIR)
2935 return -1;
2936 return 1;
2937 }
2939 return strcmp(name1, name2);
2940 }
2942 static bool
2943 tree_read(struct view *view, char *text)
2944 {
2945 size_t textlen = text ? strlen(text) : 0;
2946 char buf[SIZEOF_STR];
2947 unsigned long pos;
2948 enum line_type type;
2949 bool first_read = view->lines == 0;
2951 if (textlen <= SIZEOF_TREE_ATTR)
2952 return FALSE;
2954 type = text[STRING_SIZE("100644 ")] == 't'
2955 ? LINE_TREE_DIR : LINE_TREE_FILE;
2957 if (first_read) {
2958 /* Add path info line */
2959 if (!string_format(buf, "Directory path /%s", opt_path) ||
2960 !realloc_lines(view, view->line_size + 1) ||
2961 !add_line_text(view, buf, LINE_DEFAULT))
2962 return FALSE;
2964 /* Insert "link" to parent directory. */
2965 if (*opt_path) {
2966 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2967 !realloc_lines(view, view->line_size + 1) ||
2968 !add_line_text(view, buf, LINE_TREE_DIR))
2969 return FALSE;
2970 }
2971 }
2973 /* Strip the path part ... */
2974 if (*opt_path) {
2975 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2976 size_t striplen = strlen(opt_path);
2977 char *path = text + SIZEOF_TREE_ATTR;
2979 if (pathlen > striplen)
2980 memmove(path, path + striplen,
2981 pathlen - striplen + 1);
2982 }
2984 /* Skip "Directory ..." and ".." line. */
2985 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2986 struct line *line = &view->line[pos];
2987 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2988 char *path2 = text + SIZEOF_TREE_ATTR;
2989 int cmp = tree_compare_entry(line->type, path1, type, path2);
2991 if (cmp <= 0)
2992 continue;
2994 text = strdup(text);
2995 if (!text)
2996 return FALSE;
2998 if (view->lines > pos)
2999 memmove(&view->line[pos + 1], &view->line[pos],
3000 (view->lines - pos) * sizeof(*line));
3002 line = &view->line[pos];
3003 line->data = text;
3004 line->type = type;
3005 view->lines++;
3006 return TRUE;
3007 }
3009 if (!add_line_text(view, text, type))
3010 return FALSE;
3012 if (tree_lineno > view->lineno) {
3013 view->lineno = tree_lineno;
3014 tree_lineno = 0;
3015 }
3017 return TRUE;
3018 }
3020 static enum request
3021 tree_request(struct view *view, enum request request, struct line *line)
3022 {
3023 enum open_flags flags;
3025 if (request == REQ_TREE_PARENT) {
3026 if (*opt_path) {
3027 /* fake 'cd ..' */
3028 request = REQ_ENTER;
3029 line = &view->line[1];
3030 } else {
3031 /* quit view if at top of tree */
3032 return REQ_VIEW_CLOSE;
3033 }
3034 }
3035 if (request != REQ_ENTER)
3036 return request;
3038 /* Cleanup the stack if the tree view is at a different tree. */
3039 while (!*opt_path && tree_stack)
3040 pop_tree_stack_entry();
3042 switch (line->type) {
3043 case LINE_TREE_DIR:
3044 /* Depending on whether it is a subdir or parent (updir?) link
3045 * mangle the path buffer. */
3046 if (line == &view->line[1] && *opt_path) {
3047 pop_tree_stack_entry();
3049 } else {
3050 char *data = line->data;
3051 char *basename = data + SIZEOF_TREE_ATTR;
3053 push_tree_stack_entry(basename, view->lineno);
3054 }
3056 /* Trees and subtrees share the same ID, so they are not not
3057 * unique like blobs. */
3058 flags = OPEN_RELOAD;
3059 request = REQ_VIEW_TREE;
3060 break;
3062 case LINE_TREE_FILE:
3063 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3064 request = REQ_VIEW_BLOB;
3065 break;
3067 default:
3068 return TRUE;
3069 }
3071 open_view(view, request, flags);
3072 if (request == REQ_VIEW_TREE) {
3073 view->lineno = tree_lineno;
3074 }
3076 return REQ_NONE;
3077 }
3079 static void
3080 tree_select(struct view *view, struct line *line)
3081 {
3082 char *text = line->data + STRING_SIZE("100644 blob ");
3084 if (line->type == LINE_TREE_FILE) {
3085 string_copy_rev(ref_blob, text);
3087 } else if (line->type != LINE_TREE_DIR) {
3088 return;
3089 }
3091 string_copy_rev(view->ref, text);
3092 }
3094 static struct view_ops tree_ops = {
3095 "file",
3096 NULL,
3097 tree_read,
3098 pager_draw,
3099 tree_request,
3100 pager_grep,
3101 tree_select,
3102 };
3104 static bool
3105 blob_read(struct view *view, char *line)
3106 {
3107 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3108 }
3110 static struct view_ops blob_ops = {
3111 "line",
3112 NULL,
3113 blob_read,
3114 pager_draw,
3115 pager_request,
3116 pager_grep,
3117 pager_select,
3118 };
3121 /*
3122 * Status backend
3123 */
3125 struct status {
3126 char status;
3127 struct {
3128 mode_t mode;
3129 char rev[SIZEOF_REV];
3130 } old;
3131 struct {
3132 mode_t mode;
3133 char rev[SIZEOF_REV];
3134 } new;
3135 char name[SIZEOF_STR];
3136 };
3138 static struct status stage_status;
3139 static enum line_type stage_line_type;
3141 /* Get fields from the diff line:
3142 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3143 */
3144 static inline bool
3145 status_get_diff(struct status *file, char *buf, size_t bufsize)
3146 {
3147 char *old_mode = buf + 1;
3148 char *new_mode = buf + 8;
3149 char *old_rev = buf + 15;
3150 char *new_rev = buf + 56;
3151 char *status = buf + 97;
3153 if (bufsize != 99 ||
3154 old_mode[-1] != ':' ||
3155 new_mode[-1] != ' ' ||
3156 old_rev[-1] != ' ' ||
3157 new_rev[-1] != ' ' ||
3158 status[-1] != ' ')
3159 return FALSE;
3161 file->status = *status;
3163 string_copy_rev(file->old.rev, old_rev);
3164 string_copy_rev(file->new.rev, new_rev);
3166 file->old.mode = strtoul(old_mode, NULL, 8);
3167 file->new.mode = strtoul(new_mode, NULL, 8);
3169 file->name[0] = 0;
3171 return TRUE;
3172 }
3174 static bool
3175 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3176 {
3177 struct status *file = NULL;
3178 struct status *unmerged = NULL;
3179 char buf[SIZEOF_STR * 4];
3180 size_t bufsize = 0;
3181 FILE *pipe;
3183 pipe = popen(cmd, "r");
3184 if (!pipe)
3185 return FALSE;
3187 add_line_data(view, NULL, type);
3189 while (!feof(pipe) && !ferror(pipe)) {
3190 char *sep;
3191 size_t readsize;
3193 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3194 if (!readsize)
3195 break;
3196 bufsize += readsize;
3198 /* Process while we have NUL chars. */
3199 while ((sep = memchr(buf, 0, bufsize))) {
3200 size_t sepsize = sep - buf + 1;
3202 if (!file) {
3203 if (!realloc_lines(view, view->line_size + 1))
3204 goto error_out;
3206 file = calloc(1, sizeof(*file));
3207 if (!file)
3208 goto error_out;
3210 add_line_data(view, file, type);
3211 }
3213 /* Parse diff info part. */
3214 if (!diff) {
3215 file->status = '?';
3217 } else if (!file->status) {
3218 if (!status_get_diff(file, buf, sepsize))
3219 goto error_out;
3221 bufsize -= sepsize;
3222 memmove(buf, sep + 1, bufsize);
3224 sep = memchr(buf, 0, bufsize);
3225 if (!sep)
3226 break;
3227 sepsize = sep - buf + 1;
3229 /* Collapse all 'M'odified entries that
3230 * follow a associated 'U'nmerged entry.
3231 */
3232 if (file->status == 'U') {
3233 unmerged = file;
3235 } else if (unmerged) {
3236 int collapse = !strcmp(buf, unmerged->name);
3238 unmerged = NULL;
3239 if (collapse) {
3240 free(file);
3241 view->lines--;
3242 continue;
3243 }
3244 }
3245 }
3247 /* git-ls-files just delivers a NUL separated
3248 * list of file names similar to the second half
3249 * of the git-diff-* output. */
3250 string_ncopy(file->name, buf, sepsize);
3251 bufsize -= sepsize;
3252 memmove(buf, sep + 1, bufsize);
3253 file = NULL;
3254 }
3255 }
3257 if (ferror(pipe)) {
3258 error_out:
3259 pclose(pipe);
3260 return FALSE;
3261 }
3263 if (!view->line[view->lines - 1].data)
3264 add_line_data(view, NULL, LINE_STAT_NONE);
3266 pclose(pipe);
3267 return TRUE;
3268 }
3270 /* Don't show unmerged entries in the staged section. */
3271 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3272 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3273 #define STATUS_LIST_OTHER_CMD \
3274 "git ls-files -z --others --exclude-per-directory=.gitignore"
3276 #define STATUS_DIFF_INDEX_SHOW_CMD \
3277 "git diff-index --root --patch-with-stat --find-copies-harder -B -C --cached HEAD -- %s 2>/dev/null"
3279 #define STATUS_DIFF_FILES_SHOW_CMD \
3280 "git diff-files --root --patch-with-stat --find-copies-harder -B -C -- %s 2>/dev/null"
3282 /* First parse staged info using git-diff-index(1), then parse unstaged
3283 * info using git-diff-files(1), and finally untracked files using
3284 * git-ls-files(1). */
3285 static bool
3286 status_open(struct view *view)
3287 {
3288 struct stat statbuf;
3289 char exclude[SIZEOF_STR];
3290 char cmd[SIZEOF_STR];
3291 unsigned long prev_lineno = view->lineno;
3292 size_t i;
3294 for (i = 0; i < view->lines; i++)
3295 free(view->line[i].data);
3296 free(view->line);
3297 view->lines = view->line_size = view->lineno = 0;
3298 view->line = NULL;
3300 if (!realloc_lines(view, view->line_size + 6))
3301 return FALSE;
3303 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3304 return FALSE;
3306 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3308 if (stat(exclude, &statbuf) >= 0) {
3309 size_t cmdsize = strlen(cmd);
3311 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3312 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3313 return FALSE;
3314 }
3316 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3317 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3318 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3319 return FALSE;
3321 /* If all went well restore the previous line number to stay in
3322 * the context. */
3323 if (prev_lineno < view->lines)
3324 view->lineno = prev_lineno;
3325 else
3326 view->lineno = view->lines - 1;
3328 return TRUE;
3329 }
3331 static bool
3332 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3333 {
3334 struct status *status = line->data;
3336 wmove(view->win, lineno, 0);
3338 if (selected) {
3339 wattrset(view->win, get_line_attr(LINE_CURSOR));
3340 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3342 } else if (!status && line->type != LINE_STAT_NONE) {
3343 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3344 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3346 } else {
3347 wattrset(view->win, get_line_attr(line->type));
3348 }
3350 if (!status) {
3351 char *text;
3353 switch (line->type) {
3354 case LINE_STAT_STAGED:
3355 text = "Changes to be committed:";
3356 break;
3358 case LINE_STAT_UNSTAGED:
3359 text = "Changed but not updated:";
3360 break;
3362 case LINE_STAT_UNTRACKED:
3363 text = "Untracked files:";
3364 break;
3366 case LINE_STAT_NONE:
3367 text = " (no files)";
3368 break;
3370 default:
3371 return FALSE;
3372 }
3374 waddstr(view->win, text);
3375 return TRUE;
3376 }
3378 waddch(view->win, status->status);
3379 if (!selected)
3380 wattrset(view->win, A_NORMAL);
3381 wmove(view->win, lineno, 4);
3382 waddstr(view->win, status->name);
3384 return TRUE;
3385 }
3387 static enum request
3388 status_enter(struct view *view, struct line *line)
3389 {
3390 struct status *status = line->data;
3391 char path[SIZEOF_STR] = "";
3392 char *info;
3393 size_t cmdsize = 0;
3395 if (line->type == LINE_STAT_NONE ||
3396 (!status && line[1].type == LINE_STAT_NONE)) {
3397 report("No file to diff");
3398 return REQ_NONE;
3399 }
3401 if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3402 return REQ_QUIT;
3404 if (opt_cdup[0] &&
3405 line->type != LINE_STAT_UNTRACKED &&
3406 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3407 return REQ_QUIT;
3409 switch (line->type) {
3410 case LINE_STAT_STAGED:
3411 if (!string_format_from(opt_cmd, &cmdsize,
3412 STATUS_DIFF_INDEX_SHOW_CMD, path))
3413 return REQ_QUIT;
3414 if (status)
3415 info = "Staged changes to %s";
3416 else
3417 info = "Staged changes";
3418 break;
3420 case LINE_STAT_UNSTAGED:
3421 if (!string_format_from(opt_cmd, &cmdsize,
3422 STATUS_DIFF_FILES_SHOW_CMD, path))
3423 return REQ_QUIT;
3424 if (status)
3425 info = "Unstaged changes to %s";
3426 else
3427 info = "Unstaged changes";
3428 break;
3430 case LINE_STAT_UNTRACKED:
3431 if (opt_pipe)
3432 return REQ_QUIT;
3435 if (!status) {
3436 report("No file to show");
3437 return REQ_NONE;
3438 }
3440 opt_pipe = fopen(status->name, "r");
3441 info = "Untracked file %s";
3442 break;
3444 default:
3445 die("line type %d not handled in switch", line->type);
3446 }
3448 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3449 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3450 if (status) {
3451 stage_status = *status;
3452 } else {
3453 memset(&stage_status, 0, sizeof(stage_status));
3454 }
3456 stage_line_type = line->type;
3457 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3458 }
3460 return REQ_NONE;
3461 }
3464 static bool
3465 status_update_file(struct view *view, struct status *status, enum line_type type)
3466 {
3467 char cmd[SIZEOF_STR];
3468 char buf[SIZEOF_STR];
3469 size_t cmdsize = 0;
3470 size_t bufsize = 0;
3471 size_t written = 0;
3472 FILE *pipe;
3474 if (opt_cdup[0] &&
3475 type != LINE_STAT_UNTRACKED &&
3476 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3477 return FALSE;
3479 switch (type) {
3480 case LINE_STAT_STAGED:
3481 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3482 status->old.mode,
3483 status->old.rev,
3484 status->name, 0))
3485 return FALSE;
3487 string_add(cmd, cmdsize, "git update-index -z --index-info");
3488 break;
3490 case LINE_STAT_UNSTAGED:
3491 case LINE_STAT_UNTRACKED:
3492 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3493 return FALSE;
3495 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3496 break;
3498 default:
3499 die("line type %d not handled in switch", type);
3500 }
3502 pipe = popen(cmd, "w");
3503 if (!pipe)
3504 return FALSE;
3506 while (!ferror(pipe) && written < bufsize) {
3507 written += fwrite(buf + written, 1, bufsize - written, pipe);
3508 }
3510 pclose(pipe);
3512 if (written != bufsize)
3513 return FALSE;
3515 return TRUE;
3516 }
3518 static void
3519 status_update(struct view *view)
3520 {
3521 struct line *line = &view->line[view->lineno];
3523 assert(view->lines);
3525 if (!line->data) {
3526 while (++line < view->line + view->lines && line->data) {
3527 if (!status_update_file(view, line->data, line->type))
3528 report("Failed to update file status");
3529 }
3531 if (!line[-1].data) {
3532 report("Nothing to update");
3533 return;
3534 }
3536 } else if (!status_update_file(view, line->data, line->type)) {
3537 report("Failed to update file status");
3538 }
3539 }
3541 static enum request
3542 status_request(struct view *view, enum request request, struct line *line)
3543 {
3544 struct status *status = line->data;
3546 switch (request) {
3547 case REQ_STATUS_UPDATE:
3548 status_update(view);
3549 break;
3551 case REQ_STATUS_MERGE:
3552 if (!status || status->status != 'U') {
3553 report("Merging only possible for files with unmerged status ('U').");
3554 return REQ_NONE;
3555 }
3556 open_mergetool(status->name);
3557 break;
3559 case REQ_EDIT:
3560 if (!status)
3561 return request;
3563 open_editor(status->status != '?', status->name);
3564 break;
3566 case REQ_ENTER:
3567 /* After returning the status view has been split to
3568 * show the stage view. No further reloading is
3569 * necessary. */
3570 status_enter(view, line);
3571 return REQ_NONE;
3573 case REQ_REFRESH:
3574 /* Simply reload the view. */
3575 break;
3577 default:
3578 return request;
3579 }
3581 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3583 return REQ_NONE;
3584 }
3586 static void
3587 status_select(struct view *view, struct line *line)
3588 {
3589 struct status *status = line->data;
3590 char file[SIZEOF_STR] = "all files";
3591 char *text;
3592 char *key;
3594 if (status && !string_format(file, "'%s'", status->name))
3595 return;
3597 if (!status && line[1].type == LINE_STAT_NONE)
3598 line++;
3600 switch (line->type) {
3601 case LINE_STAT_STAGED:
3602 text = "Press %s to unstage %s for commit";
3603 break;
3605 case LINE_STAT_UNSTAGED:
3606 text = "Press %s to stage %s for commit";
3607 break;
3609 case LINE_STAT_UNTRACKED:
3610 text = "Press %s to stage %s for addition";
3611 break;
3613 case LINE_STAT_NONE:
3614 text = "Nothing to update";
3615 break;
3617 default:
3618 die("line type %d not handled in switch", line->type);
3619 }
3621 if (status && status->status == 'U') {
3622 text = "Press %s to resolve conflict in %s";
3623 key = get_key(REQ_STATUS_MERGE);
3625 } else {
3626 key = get_key(REQ_STATUS_UPDATE);
3627 }
3629 string_format(view->ref, text, key, file);
3630 }
3632 static bool
3633 status_grep(struct view *view, struct line *line)
3634 {
3635 struct status *status = line->data;
3636 enum { S_STATUS, S_NAME, S_END } state;
3637 char buf[2] = "?";
3638 regmatch_t pmatch;
3640 if (!status)
3641 return FALSE;
3643 for (state = S_STATUS; state < S_END; state++) {
3644 char *text;
3646 switch (state) {
3647 case S_NAME: text = status->name; break;
3648 case S_STATUS:
3649 buf[0] = status->status;
3650 text = buf;
3651 break;
3653 default:
3654 return FALSE;
3655 }
3657 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3658 return TRUE;
3659 }
3661 return FALSE;
3662 }
3664 static struct view_ops status_ops = {
3665 "file",
3666 status_open,
3667 NULL,
3668 status_draw,
3669 status_request,
3670 status_grep,
3671 status_select,
3672 };
3675 static bool
3676 stage_diff_line(FILE *pipe, struct line *line)
3677 {
3678 char *buf = line->data;
3679 size_t bufsize = strlen(buf);
3680 size_t written = 0;
3682 while (!ferror(pipe) && written < bufsize) {
3683 written += fwrite(buf + written, 1, bufsize - written, pipe);
3684 }
3686 fputc('\n', pipe);
3688 return written == bufsize;
3689 }
3691 static struct line *
3692 stage_diff_hdr(struct view *view, struct line *line)
3693 {
3694 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3695 struct line *diff_hdr;
3697 if (line->type == LINE_DIFF_CHUNK)
3698 diff_hdr = line - 1;
3699 else
3700 diff_hdr = view->line + 1;
3702 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3703 if (diff_hdr->type == LINE_DIFF_HEADER)
3704 return diff_hdr;
3706 diff_hdr += diff_hdr_dir;
3707 }
3709 return NULL;
3710 }
3712 static bool
3713 stage_update_chunk(struct view *view, struct line *line)
3714 {
3715 char cmd[SIZEOF_STR];
3716 size_t cmdsize = 0;
3717 struct line *diff_hdr, *diff_chunk, *diff_end;
3718 FILE *pipe;
3720 diff_hdr = stage_diff_hdr(view, line);
3721 if (!diff_hdr)
3722 return FALSE;
3724 if (opt_cdup[0] &&
3725 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3726 return FALSE;
3728 if (!string_format_from(cmd, &cmdsize,
3729 "git apply --cached %s - && "
3730 "git update-index -q --unmerged --refresh 2>/dev/null",
3731 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3732 return FALSE;
3734 pipe = popen(cmd, "w");
3735 if (!pipe)
3736 return FALSE;
3738 diff_end = view->line + view->lines;
3739 if (line->type != LINE_DIFF_CHUNK) {
3740 diff_chunk = diff_hdr;
3742 } else {
3743 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3744 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3745 diff_chunk->type == LINE_DIFF_HEADER)
3746 diff_end = diff_chunk;
3748 diff_chunk = line;
3750 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3751 switch (diff_hdr->type) {
3752 case LINE_DIFF_HEADER:
3753 case LINE_DIFF_INDEX:
3754 case LINE_DIFF_ADD:
3755 case LINE_DIFF_DEL:
3756 break;
3758 default:
3759 diff_hdr++;
3760 continue;
3761 }
3763 if (!stage_diff_line(pipe, diff_hdr++)) {
3764 pclose(pipe);
3765 return FALSE;
3766 }
3767 }
3768 }
3770 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3771 diff_chunk++;
3773 pclose(pipe);
3775 if (diff_chunk != diff_end)
3776 return FALSE;
3778 return TRUE;
3779 }
3781 static void
3782 stage_update(struct view *view, struct line *line)
3783 {
3784 if (stage_line_type != LINE_STAT_UNTRACKED &&
3785 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3786 if (!stage_update_chunk(view, line)) {
3787 report("Failed to apply chunk");
3788 return;
3789 }
3791 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3792 report("Failed to update file");
3793 return;
3794 }
3796 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3798 view = VIEW(REQ_VIEW_STATUS);
3799 if (view_is_displayed(view))
3800 status_enter(view, &view->line[view->lineno]);
3801 }
3803 static enum request
3804 stage_request(struct view *view, enum request request, struct line *line)
3805 {
3806 switch (request) {
3807 case REQ_STATUS_UPDATE:
3808 stage_update(view, line);
3809 break;
3811 case REQ_EDIT:
3812 if (!stage_status.name[0])
3813 return request;
3815 open_editor(stage_status.status != '?', stage_status.name);
3816 break;
3818 case REQ_ENTER:
3819 pager_request(view, request, line);
3820 break;
3822 default:
3823 return request;
3824 }
3826 return REQ_NONE;
3827 }
3829 static struct view_ops stage_ops = {
3830 "line",
3831 NULL,
3832 pager_read,
3833 pager_draw,
3834 stage_request,
3835 pager_grep,
3836 pager_select,
3837 };
3840 /*
3841 * Revision graph
3842 */
3844 struct commit {
3845 char id[SIZEOF_REV]; /* SHA1 ID. */
3846 char title[128]; /* First line of the commit message. */
3847 char author[75]; /* Author of the commit. */
3848 struct tm time; /* Date from the author ident. */
3849 struct ref **refs; /* Repository references. */
3850 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3851 size_t graph_size; /* The width of the graph array. */
3852 };
3854 /* Size of rev graph with no "padding" columns */
3855 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3857 struct rev_graph {
3858 struct rev_graph *prev, *next, *parents;
3859 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3860 size_t size;
3861 struct commit *commit;
3862 size_t pos;
3863 };
3865 /* Parents of the commit being visualized. */
3866 static struct rev_graph graph_parents[4];
3868 /* The current stack of revisions on the graph. */
3869 static struct rev_graph graph_stacks[4] = {
3870 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3871 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3872 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3873 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3874 };
3876 static inline bool
3877 graph_parent_is_merge(struct rev_graph *graph)
3878 {
3879 return graph->parents->size > 1;
3880 }
3882 static inline void
3883 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3884 {
3885 struct commit *commit = graph->commit;
3887 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3888 commit->graph[commit->graph_size++] = symbol;
3889 }
3891 static void
3892 done_rev_graph(struct rev_graph *graph)
3893 {
3894 if (graph_parent_is_merge(graph) &&
3895 graph->pos < graph->size - 1 &&
3896 graph->next->size == graph->size + graph->parents->size - 1) {
3897 size_t i = graph->pos + graph->parents->size - 1;
3899 graph->commit->graph_size = i * 2;
3900 while (i < graph->next->size - 1) {
3901 append_to_rev_graph(graph, ' ');
3902 append_to_rev_graph(graph, '\\');
3903 i++;
3904 }
3905 }
3907 graph->size = graph->pos = 0;
3908 graph->commit = NULL;
3909 memset(graph->parents, 0, sizeof(*graph->parents));
3910 }
3912 static void
3913 push_rev_graph(struct rev_graph *graph, char *parent)
3914 {
3915 int i;
3917 /* "Collapse" duplicate parents lines.
3918 *
3919 * FIXME: This needs to also update update the drawn graph but
3920 * for now it just serves as a method for pruning graph lines. */
3921 for (i = 0; i < graph->size; i++)
3922 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3923 return;
3925 if (graph->size < SIZEOF_REVITEMS) {
3926 string_copy_rev(graph->rev[graph->size++], parent);
3927 }
3928 }
3930 static chtype
3931 get_rev_graph_symbol(struct rev_graph *graph)
3932 {
3933 chtype symbol;
3935 if (graph->parents->size == 0)
3936 symbol = REVGRAPH_INIT;
3937 else if (graph_parent_is_merge(graph))
3938 symbol = REVGRAPH_MERGE;
3939 else if (graph->pos >= graph->size)
3940 symbol = REVGRAPH_BRANCH;
3941 else
3942 symbol = REVGRAPH_COMMIT;
3944 return symbol;
3945 }
3947 static void
3948 draw_rev_graph(struct rev_graph *graph)
3949 {
3950 struct rev_filler {
3951 chtype separator, line;
3952 };
3953 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3954 static struct rev_filler fillers[] = {
3955 { ' ', REVGRAPH_LINE },
3956 { '`', '.' },
3957 { '\'', ' ' },
3958 { '/', ' ' },
3959 };
3960 chtype symbol = get_rev_graph_symbol(graph);
3961 struct rev_filler *filler;
3962 size_t i;
3964 filler = &fillers[DEFAULT];
3966 for (i = 0; i < graph->pos; i++) {
3967 append_to_rev_graph(graph, filler->line);
3968 if (graph_parent_is_merge(graph->prev) &&
3969 graph->prev->pos == i)
3970 filler = &fillers[RSHARP];
3972 append_to_rev_graph(graph, filler->separator);
3973 }
3975 /* Place the symbol for this revision. */
3976 append_to_rev_graph(graph, symbol);
3978 if (graph->prev->size > graph->size)
3979 filler = &fillers[RDIAG];
3980 else
3981 filler = &fillers[DEFAULT];
3983 i++;
3985 for (; i < graph->size; i++) {
3986 append_to_rev_graph(graph, filler->separator);
3987 append_to_rev_graph(graph, filler->line);
3988 if (graph_parent_is_merge(graph->prev) &&
3989 i < graph->prev->pos + graph->parents->size)
3990 filler = &fillers[RSHARP];
3991 if (graph->prev->size > graph->size)
3992 filler = &fillers[LDIAG];
3993 }
3995 if (graph->prev->size > graph->size) {
3996 append_to_rev_graph(graph, filler->separator);
3997 if (filler->line != ' ')
3998 append_to_rev_graph(graph, filler->line);
3999 }
4000 }
4002 /* Prepare the next rev graph */
4003 static void
4004 prepare_rev_graph(struct rev_graph *graph)
4005 {
4006 size_t i;
4008 /* First, traverse all lines of revisions up to the active one. */
4009 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4010 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4011 break;
4013 push_rev_graph(graph->next, graph->rev[graph->pos]);
4014 }
4016 /* Interleave the new revision parent(s). */
4017 for (i = 0; i < graph->parents->size; i++)
4018 push_rev_graph(graph->next, graph->parents->rev[i]);
4020 /* Lastly, put any remaining revisions. */
4021 for (i = graph->pos + 1; i < graph->size; i++)
4022 push_rev_graph(graph->next, graph->rev[i]);
4023 }
4025 static void
4026 update_rev_graph(struct rev_graph *graph)
4027 {
4028 /* If this is the finalizing update ... */
4029 if (graph->commit)
4030 prepare_rev_graph(graph);
4032 /* Graph visualization needs a one rev look-ahead,
4033 * so the first update doesn't visualize anything. */
4034 if (!graph->prev->commit)
4035 return;
4037 draw_rev_graph(graph->prev);
4038 done_rev_graph(graph->prev->prev);
4039 }
4042 /*
4043 * Main view backend
4044 */
4046 static bool
4047 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4048 {
4049 char buf[DATE_COLS + 1];
4050 struct commit *commit = line->data;
4051 enum line_type type;
4052 int col = 0;
4053 size_t timelen;
4054 size_t authorlen;
4055 int trimmed = 1;
4057 if (!*commit->author)
4058 return FALSE;
4060 wmove(view->win, lineno, col);
4062 if (selected) {
4063 type = LINE_CURSOR;
4064 wattrset(view->win, get_line_attr(type));
4065 wchgat(view->win, -1, 0, type, NULL);
4067 } else {
4068 type = LINE_MAIN_COMMIT;
4069 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4070 }
4072 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4073 waddnstr(view->win, buf, timelen);
4074 waddstr(view->win, " ");
4076 col += DATE_COLS;
4077 wmove(view->win, lineno, col);
4078 if (type != LINE_CURSOR)
4079 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4081 if (opt_utf8) {
4082 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
4083 } else {
4084 authorlen = strlen(commit->author);
4085 if (authorlen > AUTHOR_COLS - 2) {
4086 authorlen = AUTHOR_COLS - 2;
4087 trimmed = 1;
4088 }
4089 }
4091 if (trimmed) {
4092 waddnstr(view->win, commit->author, authorlen);
4093 if (type != LINE_CURSOR)
4094 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
4095 waddch(view->win, '~');
4096 } else {
4097 waddstr(view->win, commit->author);
4098 }
4100 col += AUTHOR_COLS;
4102 if (opt_rev_graph && commit->graph_size) {
4103 size_t i;
4105 if (type != LINE_CURSOR)
4106 wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4107 wmove(view->win, lineno, col);
4108 /* Using waddch() instead of waddnstr() ensures that
4109 * they'll be rendered correctly for the cursor line. */
4110 for (i = 0; i < commit->graph_size; i++)
4111 waddch(view->win, commit->graph[i]);
4113 waddch(view->win, ' ');
4114 col += commit->graph_size + 1;
4115 }
4116 if (type != LINE_CURSOR)
4117 wattrset(view->win, A_NORMAL);
4119 wmove(view->win, lineno, col);
4121 if (commit->refs) {
4122 size_t i = 0;
4124 do {
4125 if (type == LINE_CURSOR)
4126 ;
4127 else if (commit->refs[i]->tag)
4128 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4129 else if (commit->refs[i]->remote)
4130 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4131 else
4132 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4133 waddstr(view->win, "[");
4134 waddstr(view->win, commit->refs[i]->name);
4135 waddstr(view->win, "]");
4136 if (type != LINE_CURSOR)
4137 wattrset(view->win, A_NORMAL);
4138 waddstr(view->win, " ");
4139 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
4140 } while (commit->refs[i++]->next);
4141 }
4143 if (type != LINE_CURSOR)
4144 wattrset(view->win, get_line_attr(type));
4146 {
4147 int titlelen = strlen(commit->title);
4149 if (col + titlelen > view->width)
4150 titlelen = view->width - col;
4152 waddnstr(view->win, commit->title, titlelen);
4153 }
4155 return TRUE;
4156 }
4158 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4159 static bool
4160 main_read(struct view *view, char *line)
4161 {
4162 static struct rev_graph *graph = graph_stacks;
4163 enum line_type type;
4164 struct commit *commit;
4166 if (!line) {
4167 update_rev_graph(graph);
4168 return TRUE;
4169 }
4171 type = get_line_type(line);
4172 if (type == LINE_COMMIT) {
4173 commit = calloc(1, sizeof(struct commit));
4174 if (!commit)
4175 return FALSE;
4177 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
4178 commit->refs = get_refs(commit->id);
4179 graph->commit = commit;
4180 add_line_data(view, commit, LINE_MAIN_COMMIT);
4181 return TRUE;
4182 }
4184 if (!view->lines)
4185 return TRUE;
4186 commit = view->line[view->lines - 1].data;
4188 switch (type) {
4189 case LINE_PARENT:
4190 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4191 break;
4193 case LINE_AUTHOR:
4194 {
4195 /* Parse author lines where the name may be empty:
4196 * author <email@address.tld> 1138474660 +0100
4197 */
4198 char *ident = line + STRING_SIZE("author ");
4199 char *nameend = strchr(ident, '<');
4200 char *emailend = strchr(ident, '>');
4202 if (!nameend || !emailend)
4203 break;
4205 update_rev_graph(graph);
4206 graph = graph->next;
4208 *nameend = *emailend = 0;
4209 ident = chomp_string(ident);
4210 if (!*ident) {
4211 ident = chomp_string(nameend + 1);
4212 if (!*ident)
4213 ident = "Unknown";
4214 }
4216 string_ncopy(commit->author, ident, strlen(ident));
4218 /* Parse epoch and timezone */
4219 if (emailend[1] == ' ') {
4220 char *secs = emailend + 2;
4221 char *zone = strchr(secs, ' ');
4222 time_t time = (time_t) atol(secs);
4224 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4225 long tz;
4227 zone++;
4228 tz = ('0' - zone[1]) * 60 * 60 * 10;
4229 tz += ('0' - zone[2]) * 60 * 60;
4230 tz += ('0' - zone[3]) * 60;
4231 tz += ('0' - zone[4]) * 60;
4233 if (zone[0] == '-')
4234 tz = -tz;
4236 time -= tz;
4237 }
4239 gmtime_r(&time, &commit->time);
4240 }
4241 break;
4242 }
4243 default:
4244 /* Fill in the commit title if it has not already been set. */
4245 if (commit->title[0])
4246 break;
4248 /* Require titles to start with a non-space character at the
4249 * offset used by git log. */
4250 if (strncmp(line, " ", 4))
4251 break;
4252 line += 4;
4253 /* Well, if the title starts with a whitespace character,
4254 * try to be forgiving. Otherwise we end up with no title. */
4255 while (isspace(*line))
4256 line++;
4257 if (*line == '\0')
4258 break;
4259 /* FIXME: More graceful handling of titles; append "..." to
4260 * shortened titles, etc. */
4262 string_ncopy(commit->title, line, strlen(line));
4263 }
4265 return TRUE;
4266 }
4268 static enum request
4269 main_request(struct view *view, enum request request, struct line *line)
4270 {
4271 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4273 if (request == REQ_ENTER)
4274 open_view(view, REQ_VIEW_DIFF, flags);
4275 else
4276 return request;
4278 return REQ_NONE;
4279 }
4281 static bool
4282 main_grep(struct view *view, struct line *line)
4283 {
4284 struct commit *commit = line->data;
4285 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4286 char buf[DATE_COLS + 1];
4287 regmatch_t pmatch;
4289 for (state = S_TITLE; state < S_END; state++) {
4290 char *text;
4292 switch (state) {
4293 case S_TITLE: text = commit->title; break;
4294 case S_AUTHOR: text = commit->author; break;
4295 case S_DATE:
4296 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4297 continue;
4298 text = buf;
4299 break;
4301 default:
4302 return FALSE;
4303 }
4305 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4306 return TRUE;
4307 }
4309 return FALSE;
4310 }
4312 static void
4313 main_select(struct view *view, struct line *line)
4314 {
4315 struct commit *commit = line->data;
4317 string_copy_rev(view->ref, commit->id);
4318 string_copy_rev(ref_commit, view->ref);
4319 }
4321 static struct view_ops main_ops = {
4322 "commit",
4323 NULL,
4324 main_read,
4325 main_draw,
4326 main_request,
4327 main_grep,
4328 main_select,
4329 };
4332 /*
4333 * Unicode / UTF-8 handling
4334 *
4335 * NOTE: Much of the following code for dealing with unicode is derived from
4336 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4337 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4338 */
4340 /* I've (over)annotated a lot of code snippets because I am not entirely
4341 * confident that the approach taken by this small UTF-8 interface is correct.
4342 * --jonas */
4344 static inline int
4345 unicode_width(unsigned long c)
4346 {
4347 if (c >= 0x1100 &&
4348 (c <= 0x115f /* Hangul Jamo */
4349 || c == 0x2329
4350 || c == 0x232a
4351 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
4352 /* CJK ... Yi */
4353 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4354 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4355 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4356 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4357 || (c >= 0xffe0 && c <= 0xffe6)
4358 || (c >= 0x20000 && c <= 0x2fffd)
4359 || (c >= 0x30000 && c <= 0x3fffd)))
4360 return 2;
4362 return 1;
4363 }
4365 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4366 * Illegal bytes are set one. */
4367 static const unsigned char utf8_bytes[256] = {
4368 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,
4369 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,
4370 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,
4371 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,
4372 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,
4373 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,
4374 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,
4375 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,
4376 };
4378 /* Decode UTF-8 multi-byte representation into a unicode character. */
4379 static inline unsigned long
4380 utf8_to_unicode(const char *string, size_t length)
4381 {
4382 unsigned long unicode;
4384 switch (length) {
4385 case 1:
4386 unicode = string[0];
4387 break;
4388 case 2:
4389 unicode = (string[0] & 0x1f) << 6;
4390 unicode += (string[1] & 0x3f);
4391 break;
4392 case 3:
4393 unicode = (string[0] & 0x0f) << 12;
4394 unicode += ((string[1] & 0x3f) << 6);
4395 unicode += (string[2] & 0x3f);
4396 break;
4397 case 4:
4398 unicode = (string[0] & 0x0f) << 18;
4399 unicode += ((string[1] & 0x3f) << 12);
4400 unicode += ((string[2] & 0x3f) << 6);
4401 unicode += (string[3] & 0x3f);
4402 break;
4403 case 5:
4404 unicode = (string[0] & 0x0f) << 24;
4405 unicode += ((string[1] & 0x3f) << 18);
4406 unicode += ((string[2] & 0x3f) << 12);
4407 unicode += ((string[3] & 0x3f) << 6);
4408 unicode += (string[4] & 0x3f);
4409 break;
4410 case 6:
4411 unicode = (string[0] & 0x01) << 30;
4412 unicode += ((string[1] & 0x3f) << 24);
4413 unicode += ((string[2] & 0x3f) << 18);
4414 unicode += ((string[3] & 0x3f) << 12);
4415 unicode += ((string[4] & 0x3f) << 6);
4416 unicode += (string[5] & 0x3f);
4417 break;
4418 default:
4419 die("Invalid unicode length");
4420 }
4422 /* Invalid characters could return the special 0xfffd value but NUL
4423 * should be just as good. */
4424 return unicode > 0xffff ? 0 : unicode;
4425 }
4427 /* Calculates how much of string can be shown within the given maximum width
4428 * and sets trimmed parameter to non-zero value if all of string could not be
4429 * shown.
4430 *
4431 * Additionally, adds to coloffset how many many columns to move to align with
4432 * the expected position. Takes into account how multi-byte and double-width
4433 * characters will effect the cursor position.
4434 *
4435 * Returns the number of bytes to output from string to satisfy max_width. */
4436 static size_t
4437 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4438 {
4439 const char *start = string;
4440 const char *end = strchr(string, '\0');
4441 size_t mbwidth = 0;
4442 size_t width = 0;
4444 *trimmed = 0;
4446 while (string < end) {
4447 int c = *(unsigned char *) string;
4448 unsigned char bytes = utf8_bytes[c];
4449 size_t ucwidth;
4450 unsigned long unicode;
4452 if (string + bytes > end)
4453 break;
4455 /* Change representation to figure out whether
4456 * it is a single- or double-width character. */
4458 unicode = utf8_to_unicode(string, bytes);
4459 /* FIXME: Graceful handling of invalid unicode character. */
4460 if (!unicode)
4461 break;
4463 ucwidth = unicode_width(unicode);
4464 width += ucwidth;
4465 if (width > max_width) {
4466 *trimmed = 1;
4467 break;
4468 }
4470 /* The column offset collects the differences between the
4471 * number of bytes encoding a character and the number of
4472 * columns will be used for rendering said character.
4473 *
4474 * So if some character A is encoded in 2 bytes, but will be
4475 * represented on the screen using only 1 byte this will and up
4476 * adding 1 to the multi-byte column offset.
4477 *
4478 * Assumes that no double-width character can be encoding in
4479 * less than two bytes. */
4480 if (bytes > ucwidth)
4481 mbwidth += bytes - ucwidth;
4483 string += bytes;
4484 }
4486 *coloffset += mbwidth;
4488 return string - start;
4489 }
4492 /*
4493 * Status management
4494 */
4496 /* Whether or not the curses interface has been initialized. */
4497 static bool cursed = FALSE;
4499 /* The status window is used for polling keystrokes. */
4500 static WINDOW *status_win;
4502 static bool status_empty = TRUE;
4504 /* Update status and title window. */
4505 static void
4506 report(const char *msg, ...)
4507 {
4508 struct view *view = display[current_view];
4510 if (input_mode)
4511 return;
4513 if (!view) {
4514 char buf[SIZEOF_STR];
4515 va_list args;
4517 va_start(args, msg);
4518 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4519 buf[sizeof(buf) - 1] = 0;
4520 buf[sizeof(buf) - 2] = '.';
4521 buf[sizeof(buf) - 3] = '.';
4522 buf[sizeof(buf) - 4] = '.';
4523 }
4524 va_end(args);
4525 die("%s", buf);
4526 }
4528 if (!status_empty || *msg) {
4529 va_list args;
4531 va_start(args, msg);
4533 wmove(status_win, 0, 0);
4534 if (*msg) {
4535 vwprintw(status_win, msg, args);
4536 status_empty = FALSE;
4537 } else {
4538 status_empty = TRUE;
4539 }
4540 wclrtoeol(status_win);
4541 wrefresh(status_win);
4543 va_end(args);
4544 }
4546 update_view_title(view);
4547 update_display_cursor(view);
4548 }
4550 /* Controls when nodelay should be in effect when polling user input. */
4551 static void
4552 set_nonblocking_input(bool loading)
4553 {
4554 static unsigned int loading_views;
4556 if ((loading == FALSE && loading_views-- == 1) ||
4557 (loading == TRUE && loading_views++ == 0))
4558 nodelay(status_win, loading);
4559 }
4561 static void
4562 init_display(void)
4563 {
4564 int x, y;
4566 /* Initialize the curses library */
4567 if (isatty(STDIN_FILENO)) {
4568 cursed = !!initscr();
4569 } else {
4570 /* Leave stdin and stdout alone when acting as a pager. */
4571 FILE *io = fopen("/dev/tty", "r+");
4573 if (!io)
4574 die("Failed to open /dev/tty");
4575 cursed = !!newterm(NULL, io, io);
4576 }
4578 if (!cursed)
4579 die("Failed to initialize curses");
4581 nonl(); /* Tell curses not to do NL->CR/NL on output */
4582 cbreak(); /* Take input chars one at a time, no wait for \n */
4583 noecho(); /* Don't echo input */
4584 leaveok(stdscr, TRUE);
4586 if (has_colors())
4587 init_colors();
4589 getmaxyx(stdscr, y, x);
4590 status_win = newwin(1, 0, y - 1, 0);
4591 if (!status_win)
4592 die("Failed to create status window");
4594 /* Enable keyboard mapping */
4595 keypad(status_win, TRUE);
4596 wbkgdset(status_win, get_line_attr(LINE_STATUS));
4597 }
4599 static char *
4600 read_prompt(const char *prompt)
4601 {
4602 enum { READING, STOP, CANCEL } status = READING;
4603 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4604 int pos = 0;
4606 while (status == READING) {
4607 struct view *view;
4608 int i, key;
4610 input_mode = TRUE;
4612 foreach_view (view, i)
4613 update_view(view);
4615 input_mode = FALSE;
4617 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4618 wclrtoeol(status_win);
4620 /* Refresh, accept single keystroke of input */
4621 key = wgetch(status_win);
4622 switch (key) {
4623 case KEY_RETURN:
4624 case KEY_ENTER:
4625 case '\n':
4626 status = pos ? STOP : CANCEL;
4627 break;
4629 case KEY_BACKSPACE:
4630 if (pos > 0)
4631 pos--;
4632 else
4633 status = CANCEL;
4634 break;
4636 case KEY_ESC:
4637 status = CANCEL;
4638 break;
4640 case ERR:
4641 break;
4643 default:
4644 if (pos >= sizeof(buf)) {
4645 report("Input string too long");
4646 return NULL;
4647 }
4649 if (isprint(key))
4650 buf[pos++] = (char) key;
4651 }
4652 }
4654 /* Clear the status window */
4655 status_empty = FALSE;
4656 report("");
4658 if (status == CANCEL)
4659 return NULL;
4661 buf[pos++] = 0;
4663 return buf;
4664 }
4666 /*
4667 * Repository references
4668 */
4670 static struct ref *refs;
4671 static size_t refs_size;
4673 /* Id <-> ref store */
4674 static struct ref ***id_refs;
4675 static size_t id_refs_size;
4677 static struct ref **
4678 get_refs(char *id)
4679 {
4680 struct ref ***tmp_id_refs;
4681 struct ref **ref_list = NULL;
4682 size_t ref_list_size = 0;
4683 size_t i;
4685 for (i = 0; i < id_refs_size; i++)
4686 if (!strcmp(id, id_refs[i][0]->id))
4687 return id_refs[i];
4689 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4690 if (!tmp_id_refs)
4691 return NULL;
4693 id_refs = tmp_id_refs;
4695 for (i = 0; i < refs_size; i++) {
4696 struct ref **tmp;
4698 if (strcmp(id, refs[i].id))
4699 continue;
4701 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4702 if (!tmp) {
4703 if (ref_list)
4704 free(ref_list);
4705 return NULL;
4706 }
4708 ref_list = tmp;
4709 if (ref_list_size > 0)
4710 ref_list[ref_list_size - 1]->next = 1;
4711 ref_list[ref_list_size] = &refs[i];
4713 /* XXX: The properties of the commit chains ensures that we can
4714 * safely modify the shared ref. The repo references will
4715 * always be similar for the same id. */
4716 ref_list[ref_list_size]->next = 0;
4717 ref_list_size++;
4718 }
4720 if (ref_list)
4721 id_refs[id_refs_size++] = ref_list;
4723 return ref_list;
4724 }
4726 static int
4727 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4728 {
4729 struct ref *ref;
4730 bool tag = FALSE;
4731 bool remote = FALSE;
4733 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4734 /* Commits referenced by tags has "^{}" appended. */
4735 if (name[namelen - 1] != '}')
4736 return OK;
4738 while (namelen > 0 && name[namelen] != '^')
4739 namelen--;
4741 tag = TRUE;
4742 namelen -= STRING_SIZE("refs/tags/");
4743 name += STRING_SIZE("refs/tags/");
4745 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4746 remote = TRUE;
4747 namelen -= STRING_SIZE("refs/remotes/");
4748 name += STRING_SIZE("refs/remotes/");
4750 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4751 namelen -= STRING_SIZE("refs/heads/");
4752 name += STRING_SIZE("refs/heads/");
4754 } else if (!strcmp(name, "HEAD")) {
4755 return OK;
4756 }
4758 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4759 if (!refs)
4760 return ERR;
4762 ref = &refs[refs_size++];
4763 ref->name = malloc(namelen + 1);
4764 if (!ref->name)
4765 return ERR;
4767 strncpy(ref->name, name, namelen);
4768 ref->name[namelen] = 0;
4769 ref->tag = tag;
4770 ref->remote = remote;
4771 string_copy_rev(ref->id, id);
4773 return OK;
4774 }
4776 static int
4777 load_refs(void)
4778 {
4779 const char *cmd_env = getenv("TIG_LS_REMOTE");
4780 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4782 return read_properties(popen(cmd, "r"), "\t", read_ref);
4783 }
4785 static int
4786 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4787 {
4788 if (!strcmp(name, "i18n.commitencoding"))
4789 string_ncopy(opt_encoding, value, valuelen);
4791 if (!strcmp(name, "core.editor"))
4792 string_ncopy(opt_editor, value, valuelen);
4794 return OK;
4795 }
4797 static int
4798 load_repo_config(void)
4799 {
4800 return read_properties(popen(GIT_CONFIG " --list", "r"),
4801 "=", read_repo_config_option);
4802 }
4804 static int
4805 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4806 {
4807 if (!opt_git_dir[0]) {
4808 string_ncopy(opt_git_dir, name, namelen);
4810 } else if (opt_is_inside_work_tree == -1) {
4811 /* This can be 3 different values depending on the
4812 * version of git being used. If git-rev-parse does not
4813 * understand --is-inside-work-tree it will simply echo
4814 * the option else either "true" or "false" is printed.
4815 * Default to true for the unknown case. */
4816 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4818 } else {
4819 string_ncopy(opt_cdup, name, namelen);
4820 }
4822 return OK;
4823 }
4825 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4826 * must be the last one! */
4827 static int
4828 load_repo_info(void)
4829 {
4830 return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4831 "=", read_repo_info);
4832 }
4834 static int
4835 read_properties(FILE *pipe, const char *separators,
4836 int (*read_property)(char *, size_t, char *, size_t))
4837 {
4838 char buffer[BUFSIZ];
4839 char *name;
4840 int state = OK;
4842 if (!pipe)
4843 return ERR;
4845 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4846 char *value;
4847 size_t namelen;
4848 size_t valuelen;
4850 name = chomp_string(name);
4851 namelen = strcspn(name, separators);
4853 if (name[namelen]) {
4854 name[namelen] = 0;
4855 value = chomp_string(name + namelen + 1);
4856 valuelen = strlen(value);
4858 } else {
4859 value = "";
4860 valuelen = 0;
4861 }
4863 state = read_property(name, namelen, value, valuelen);
4864 }
4866 if (state != ERR && ferror(pipe))
4867 state = ERR;
4869 pclose(pipe);
4871 return state;
4872 }
4875 /*
4876 * Main
4877 */
4879 static void __NORETURN
4880 quit(int sig)
4881 {
4882 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4883 if (cursed)
4884 endwin();
4885 exit(0);
4886 }
4888 static void __NORETURN
4889 die(const char *err, ...)
4890 {
4891 va_list args;
4893 endwin();
4895 va_start(args, err);
4896 fputs("tig: ", stderr);
4897 vfprintf(stderr, err, args);
4898 fputs("\n", stderr);
4899 va_end(args);
4901 exit(1);
4902 }
4904 int
4905 main(int argc, char *argv[])
4906 {
4907 struct view *view;
4908 enum request request;
4909 size_t i;
4911 signal(SIGINT, quit);
4913 if (setlocale(LC_ALL, "")) {
4914 char *codeset = nl_langinfo(CODESET);
4916 string_ncopy(opt_codeset, codeset, strlen(codeset));
4917 }
4919 if (load_repo_info() == ERR)
4920 die("Failed to load repo info.");
4922 if (load_options() == ERR)
4923 die("Failed to load user config.");
4925 /* Load the repo config file so options can be overwritten from
4926 * the command line. */
4927 if (load_repo_config() == ERR)
4928 die("Failed to load repo config.");
4930 if (!parse_options(argc, argv))
4931 return 0;
4933 /* Require a git repository unless when running in pager mode. */
4934 if (!opt_git_dir[0])
4935 die("Not a git repository");
4937 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4938 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4939 if (opt_iconv == ICONV_NONE)
4940 die("Failed to initialize character set conversion");
4941 }
4943 if (load_refs() == ERR)
4944 die("Failed to load refs.");
4946 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4947 view->cmd_env = getenv(view->cmd_env);
4949 request = opt_request;
4951 init_display();
4953 while (view_driver(display[current_view], request)) {
4954 int key;
4955 int i;
4957 foreach_view (view, i)
4958 update_view(view);
4960 /* Refresh, accept single keystroke of input */
4961 key = wgetch(status_win);
4963 /* wgetch() with nodelay() enabled returns ERR when there's no
4964 * input. */
4965 if (key == ERR) {
4966 request = REQ_NONE;
4967 continue;
4968 }
4970 request = get_keybinding(display[current_view]->keymap, key);
4972 /* Some low-level request handling. This keeps access to
4973 * status_win restricted. */
4974 switch (request) {
4975 case REQ_PROMPT:
4976 {
4977 char *cmd = read_prompt(":");
4979 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4980 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4981 opt_request = REQ_VIEW_DIFF;
4982 } else {
4983 opt_request = REQ_VIEW_PAGER;
4984 }
4985 break;
4986 }
4988 request = REQ_NONE;
4989 break;
4990 }
4991 case REQ_SEARCH:
4992 case REQ_SEARCH_BACK:
4993 {
4994 const char *prompt = request == REQ_SEARCH
4995 ? "/" : "?";
4996 char *search = read_prompt(prompt);
4998 if (search)
4999 string_ncopy(opt_search, search, strlen(search));
5000 else
5001 request = REQ_NONE;
5002 break;
5003 }
5004 case REQ_SCREEN_RESIZE:
5005 {
5006 int height, width;
5008 getmaxyx(stdscr, height, width);
5010 /* Resize the status view and let the view driver take
5011 * care of resizing the displayed views. */
5012 wresize(status_win, 1, width);
5013 mvwin(status_win, height - 1, 0);
5014 wrefresh(status_win);
5015 break;
5016 }
5017 default:
5018 break;
5019 }
5020 }
5022 quit(0);
5024 return 0;
5025 }