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