Update and improve the manual
[tig.git] / tig.c
blob84e7bfc3b98864141466d49807fc43275ced2fc0
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
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.
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.
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 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
50 #else
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
53 #else
54 #include <ncurses.h>
55 #endif
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS 20
107 #define ID_COLS 8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE 8
114 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
116 #define NULL_ID "0000000000000000000000000000000000000000"
118 #ifndef GIT_CONFIG
119 #define GIT_CONFIG "config"
120 #endif
122 #define TIG_LS_REMOTE \
123 "git ls-remote . 2>/dev/null"
125 #define TIG_DIFF_CMD \
126 "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
128 #define TIG_LOG_CMD \
129 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
131 #define TIG_MAIN_BASE \
132 "git log --no-color --pretty=raw --parents --topo-order"
134 #define TIG_MAIN_CMD \
135 TIG_MAIN_BASE " %s 2>/dev/null"
137 #define TIG_TREE_CMD \
138 "git ls-tree %s %s"
140 #define TIG_BLOB_CMD \
141 "git cat-file blob %s"
143 /* XXX: Needs to be defined to the empty string. */
144 #define TIG_HELP_CMD ""
145 #define TIG_PAGER_CMD ""
146 #define TIG_STATUS_CMD ""
147 #define TIG_STAGE_CMD ""
148 #define TIG_BLAME_CMD ""
150 /* Some ascii-shorthands fitted into the ncurses namespace. */
151 #define KEY_TAB '\t'
152 #define KEY_RETURN '\r'
153 #define KEY_ESC 27
156 struct ref {
157 char *name; /* Ref name; tag or head names are shortened. */
158 char id[SIZEOF_REV]; /* Commit SHA1 ID */
159 unsigned int head:1; /* Is it the current HEAD? */
160 unsigned int tag:1; /* Is it a tag? */
161 unsigned int ltag:1; /* If so, is the tag local? */
162 unsigned int remote:1; /* Is it a remote ref? */
163 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
164 unsigned int next:1; /* For ref lists: are there more refs? */
167 static struct ref **get_refs(const char *id);
169 struct int_map {
170 const char *name;
171 int namelen;
172 int value;
175 static int
176 set_from_int_map(struct int_map *map, size_t map_size,
177 int *value, const char *name, int namelen)
180 int i;
182 for (i = 0; i < map_size; i++)
183 if (namelen == map[i].namelen &&
184 !strncasecmp(name, map[i].name, namelen)) {
185 *value = map[i].value;
186 return OK;
189 return ERR;
194 * String helpers
197 static inline void
198 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
200 if (srclen > dstlen - 1)
201 srclen = dstlen - 1;
203 strncpy(dst, src, srclen);
204 dst[srclen] = 0;
207 /* Shorthands for safely copying into a fixed buffer. */
209 #define string_copy(dst, src) \
210 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
212 #define string_ncopy(dst, src, srclen) \
213 string_ncopy_do(dst, sizeof(dst), src, srclen)
215 #define string_copy_rev(dst, src) \
216 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
218 #define string_add(dst, from, src) \
219 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
221 static char *
222 chomp_string(char *name)
224 int namelen;
226 while (isspace(*name))
227 name++;
229 namelen = strlen(name) - 1;
230 while (namelen > 0 && isspace(name[namelen]))
231 name[namelen--] = 0;
233 return name;
236 static bool
237 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
239 va_list args;
240 size_t pos = bufpos ? *bufpos : 0;
242 va_start(args, fmt);
243 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
244 va_end(args);
246 if (bufpos)
247 *bufpos = pos;
249 return pos >= bufsize ? FALSE : TRUE;
252 #define string_format(buf, fmt, args...) \
253 string_nformat(buf, sizeof(buf), NULL, fmt, args)
255 #define string_format_from(buf, from, fmt, args...) \
256 string_nformat(buf, sizeof(buf), from, fmt, args)
258 static int
259 string_enum_compare(const char *str1, const char *str2, int len)
261 size_t i;
263 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
265 /* Diff-Header == DIFF_HEADER */
266 for (i = 0; i < len; i++) {
267 if (toupper(str1[i]) == toupper(str2[i]))
268 continue;
270 if (string_enum_sep(str1[i]) &&
271 string_enum_sep(str2[i]))
272 continue;
274 return str1[i] - str2[i];
277 return 0;
280 #define prefixcmp(str1, str2) \
281 strncmp(str1, str2, STRING_SIZE(str2))
283 /* Shell quoting
285 * NOTE: The following is a slightly modified copy of the git project's shell
286 * quoting routines found in the quote.c file.
288 * Help to copy the thing properly quoted for the shell safety. any single
289 * quote is replaced with '\'', any exclamation point is replaced with '\!',
290 * and the whole thing is enclosed in a
292 * E.g.
293 * original sq_quote result
294 * name ==> name ==> 'name'
295 * a b ==> a b ==> 'a b'
296 * a'b ==> a'\''b ==> 'a'\''b'
297 * a!b ==> a'\!'b ==> 'a'\!'b'
300 static size_t
301 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
303 char c;
305 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
307 BUFPUT('\'');
308 while ((c = *src++)) {
309 if (c == '\'' || c == '!') {
310 BUFPUT('\'');
311 BUFPUT('\\');
312 BUFPUT(c);
313 BUFPUT('\'');
314 } else {
315 BUFPUT(c);
318 BUFPUT('\'');
320 if (bufsize < SIZEOF_STR)
321 buf[bufsize] = 0;
323 return bufsize;
328 * User requests
331 #define REQ_INFO \
332 /* XXX: Keep the view request first and in sync with views[]. */ \
333 REQ_GROUP("View switching") \
334 REQ_(VIEW_MAIN, "Show main view"), \
335 REQ_(VIEW_DIFF, "Show diff view"), \
336 REQ_(VIEW_LOG, "Show log view"), \
337 REQ_(VIEW_TREE, "Show tree view"), \
338 REQ_(VIEW_BLOB, "Show blob view"), \
339 REQ_(VIEW_BLAME, "Show blame view"), \
340 REQ_(VIEW_HELP, "Show help page"), \
341 REQ_(VIEW_PAGER, "Show pager view"), \
342 REQ_(VIEW_STATUS, "Show status view"), \
343 REQ_(VIEW_STAGE, "Show stage view"), \
345 REQ_GROUP("View manipulation") \
346 REQ_(ENTER, "Enter current line and scroll"), \
347 REQ_(NEXT, "Move to next"), \
348 REQ_(PREVIOUS, "Move to previous"), \
349 REQ_(VIEW_NEXT, "Move focus to next view"), \
350 REQ_(REFRESH, "Reload and refresh"), \
351 REQ_(MAXIMIZE, "Maximize the current view"), \
352 REQ_(VIEW_CLOSE, "Close the current view"), \
353 REQ_(QUIT, "Close all views and quit"), \
355 REQ_GROUP("View specific requests") \
356 REQ_(STATUS_UPDATE, "Update file status"), \
357 REQ_(STATUS_REVERT, "Revert file changes"), \
358 REQ_(STATUS_MERGE, "Merge file using external tool"), \
359 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
360 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
362 REQ_GROUP("Cursor navigation") \
363 REQ_(MOVE_UP, "Move cursor one line up"), \
364 REQ_(MOVE_DOWN, "Move cursor one line down"), \
365 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
366 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
367 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
368 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
370 REQ_GROUP("Scrolling") \
371 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
372 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
373 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
374 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
376 REQ_GROUP("Searching") \
377 REQ_(SEARCH, "Search the view"), \
378 REQ_(SEARCH_BACK, "Search backwards in the view"), \
379 REQ_(FIND_NEXT, "Find next search match"), \
380 REQ_(FIND_PREV, "Find previous search match"), \
382 REQ_GROUP("Option manipulation") \
383 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
384 REQ_(TOGGLE_DATE, "Toggle date display"), \
385 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
386 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
387 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
389 REQ_GROUP("Misc") \
390 REQ_(PROMPT, "Bring up the prompt"), \
391 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
392 REQ_(SCREEN_RESIZE, "Resize the screen"), \
393 REQ_(SHOW_VERSION, "Show version information"), \
394 REQ_(STOP_LOADING, "Stop all loading views"), \
395 REQ_(EDIT, "Open in editor"), \
396 REQ_(NONE, "Do nothing")
399 /* User action requests. */
400 enum request {
401 #define REQ_GROUP(help)
402 #define REQ_(req, help) REQ_##req
404 /* Offset all requests to avoid conflicts with ncurses getch values. */
405 REQ_OFFSET = KEY_MAX + 1,
406 REQ_INFO
408 #undef REQ_GROUP
409 #undef REQ_
412 struct request_info {
413 enum request request;
414 const char *name;
415 int namelen;
416 const char *help;
419 static struct request_info req_info[] = {
420 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
421 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
422 REQ_INFO
423 #undef REQ_GROUP
424 #undef REQ_
427 static enum request
428 get_request(const char *name)
430 int namelen = strlen(name);
431 int i;
433 for (i = 0; i < ARRAY_SIZE(req_info); i++)
434 if (req_info[i].namelen == namelen &&
435 !string_enum_compare(req_info[i].name, name, namelen))
436 return req_info[i].request;
438 return REQ_NONE;
443 * Options
446 static const char usage[] =
447 "tig " TIG_VERSION " (" __DATE__ ")\n"
448 "\n"
449 "Usage: tig [options] [revs] [--] [paths]\n"
450 " or: tig show [options] [revs] [--] [paths]\n"
451 " or: tig blame [rev] path\n"
452 " or: tig status\n"
453 " or: tig < [git command output]\n"
454 "\n"
455 "Options:\n"
456 " -v, --version Show version and exit\n"
457 " -h, --help Show help message and exit";
459 /* Option and state variables. */
460 static bool opt_date = TRUE;
461 static bool opt_author = TRUE;
462 static bool opt_line_number = FALSE;
463 static bool opt_line_graphics = TRUE;
464 static bool opt_rev_graph = FALSE;
465 static bool opt_show_refs = TRUE;
466 static int opt_num_interval = NUMBER_INTERVAL;
467 static int opt_tab_size = TAB_SIZE;
468 static int opt_author_cols = AUTHOR_COLS-1;
469 static char opt_cmd[SIZEOF_STR] = "";
470 static char opt_path[SIZEOF_STR] = "";
471 static char opt_file[SIZEOF_STR] = "";
472 static char opt_ref[SIZEOF_REF] = "";
473 static char opt_head[SIZEOF_REF] = "";
474 static char opt_remote[SIZEOF_REF] = "";
475 static bool opt_no_head = TRUE;
476 static FILE *opt_pipe = NULL;
477 static char opt_encoding[20] = "UTF-8";
478 static bool opt_utf8 = TRUE;
479 static char opt_codeset[20] = "UTF-8";
480 static iconv_t opt_iconv = ICONV_NONE;
481 static char opt_search[SIZEOF_STR] = "";
482 static char opt_cdup[SIZEOF_STR] = "";
483 static char opt_git_dir[SIZEOF_STR] = "";
484 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
485 static char opt_editor[SIZEOF_STR] = "";
487 static enum request
488 parse_options(int argc, const char *argv[])
490 enum request request = REQ_VIEW_MAIN;
491 size_t buf_size;
492 const char *subcommand;
493 bool seen_dashdash = FALSE;
494 int i;
496 if (!isatty(STDIN_FILENO)) {
497 opt_pipe = stdin;
498 return REQ_VIEW_PAGER;
501 if (argc <= 1)
502 return REQ_VIEW_MAIN;
504 subcommand = argv[1];
505 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
506 if (!strcmp(subcommand, "-S"))
507 warn("`-S' has been deprecated; use `tig status' instead");
508 if (argc > 2)
509 warn("ignoring arguments after `%s'", subcommand);
510 return REQ_VIEW_STATUS;
512 } else if (!strcmp(subcommand, "blame")) {
513 if (argc <= 2 || argc > 4)
514 die("invalid number of options to blame\n\n%s", usage);
516 i = 2;
517 if (argc == 4) {
518 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
519 i++;
522 string_ncopy(opt_file, argv[i], strlen(argv[i]));
523 return REQ_VIEW_BLAME;
525 } else if (!strcmp(subcommand, "show")) {
526 request = REQ_VIEW_DIFF;
528 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
529 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
530 warn("`tig %s' has been deprecated", subcommand);
532 } else {
533 subcommand = NULL;
536 if (!subcommand)
537 /* XXX: This is vulnerable to the user overriding
538 * options required for the main view parser. */
539 string_copy(opt_cmd, TIG_MAIN_BASE);
540 else
541 string_format(opt_cmd, "git %s", subcommand);
543 buf_size = strlen(opt_cmd);
545 for (i = 1 + !!subcommand; i < argc; i++) {
546 const char *opt = argv[i];
548 if (seen_dashdash || !strcmp(opt, "--")) {
549 seen_dashdash = TRUE;
551 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
552 printf("tig version %s\n", TIG_VERSION);
553 return REQ_NONE;
555 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
556 printf("%s\n", usage);
557 return REQ_NONE;
560 opt_cmd[buf_size++] = ' ';
561 buf_size = sq_quote(opt_cmd, buf_size, opt);
562 if (buf_size >= sizeof(opt_cmd))
563 die("command too long");
566 opt_cmd[buf_size] = 0;
568 return request;
573 * Line-oriented content detection.
576 #define LINE_INFO \
577 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
578 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
579 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
580 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
581 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
582 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
583 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
584 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
585 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
586 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
587 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
588 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
589 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
590 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
591 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
592 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
593 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
594 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
595 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
596 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
597 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
598 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
599 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
600 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
601 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
602 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
603 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
604 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
605 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
606 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
607 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
608 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
609 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
610 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
611 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
612 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
613 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
614 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
615 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
616 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
617 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
618 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
619 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
620 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
621 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
622 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
623 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
624 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
625 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
626 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
627 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
628 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
629 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
630 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
632 enum line_type {
633 #define LINE(type, line, fg, bg, attr) \
634 LINE_##type
635 LINE_INFO,
636 LINE_NONE
637 #undef LINE
640 struct line_info {
641 const char *name; /* Option name. */
642 int namelen; /* Size of option name. */
643 const char *line; /* The start of line to match. */
644 int linelen; /* Size of string to match. */
645 int fg, bg, attr; /* Color and text attributes for the lines. */
648 static struct line_info line_info[] = {
649 #define LINE(type, line, fg, bg, attr) \
650 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
651 LINE_INFO
652 #undef LINE
655 static enum line_type
656 get_line_type(const char *line)
658 int linelen = strlen(line);
659 enum line_type type;
661 for (type = 0; type < ARRAY_SIZE(line_info); type++)
662 /* Case insensitive search matches Signed-off-by lines better. */
663 if (linelen >= line_info[type].linelen &&
664 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
665 return type;
667 return LINE_DEFAULT;
670 static inline int
671 get_line_attr(enum line_type type)
673 assert(type < ARRAY_SIZE(line_info));
674 return COLOR_PAIR(type) | line_info[type].attr;
677 static struct line_info *
678 get_line_info(const char *name)
680 size_t namelen = strlen(name);
681 enum line_type type;
683 for (type = 0; type < ARRAY_SIZE(line_info); type++)
684 if (namelen == line_info[type].namelen &&
685 !string_enum_compare(line_info[type].name, name, namelen))
686 return &line_info[type];
688 return NULL;
691 static void
692 init_colors(void)
694 int default_bg = line_info[LINE_DEFAULT].bg;
695 int default_fg = line_info[LINE_DEFAULT].fg;
696 enum line_type type;
698 start_color();
700 if (assume_default_colors(default_fg, default_bg) == ERR) {
701 default_bg = COLOR_BLACK;
702 default_fg = COLOR_WHITE;
705 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
706 struct line_info *info = &line_info[type];
707 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
708 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
710 init_pair(type, fg, bg);
714 struct line {
715 enum line_type type;
717 /* State flags */
718 unsigned int selected:1;
719 unsigned int dirty:1;
721 void *data; /* User data */
726 * Keys
729 struct keybinding {
730 int alias;
731 enum request request;
732 struct keybinding *next;
735 static struct keybinding default_keybindings[] = {
736 /* View switching */
737 { 'm', REQ_VIEW_MAIN },
738 { 'd', REQ_VIEW_DIFF },
739 { 'l', REQ_VIEW_LOG },
740 { 't', REQ_VIEW_TREE },
741 { 'f', REQ_VIEW_BLOB },
742 { 'B', REQ_VIEW_BLAME },
743 { 'p', REQ_VIEW_PAGER },
744 { 'h', REQ_VIEW_HELP },
745 { 'S', REQ_VIEW_STATUS },
746 { 'c', REQ_VIEW_STAGE },
748 /* View manipulation */
749 { 'q', REQ_VIEW_CLOSE },
750 { KEY_TAB, REQ_VIEW_NEXT },
751 { KEY_RETURN, REQ_ENTER },
752 { KEY_UP, REQ_PREVIOUS },
753 { KEY_DOWN, REQ_NEXT },
754 { 'R', REQ_REFRESH },
755 { KEY_F(5), REQ_REFRESH },
756 { 'O', REQ_MAXIMIZE },
758 /* Cursor navigation */
759 { 'k', REQ_MOVE_UP },
760 { 'j', REQ_MOVE_DOWN },
761 { KEY_HOME, REQ_MOVE_FIRST_LINE },
762 { KEY_END, REQ_MOVE_LAST_LINE },
763 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
764 { ' ', REQ_MOVE_PAGE_DOWN },
765 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
766 { 'b', REQ_MOVE_PAGE_UP },
767 { '-', REQ_MOVE_PAGE_UP },
769 /* Scrolling */
770 { KEY_IC, REQ_SCROLL_LINE_UP },
771 { KEY_DC, REQ_SCROLL_LINE_DOWN },
772 { 'w', REQ_SCROLL_PAGE_UP },
773 { 's', REQ_SCROLL_PAGE_DOWN },
775 /* Searching */
776 { '/', REQ_SEARCH },
777 { '?', REQ_SEARCH_BACK },
778 { 'n', REQ_FIND_NEXT },
779 { 'N', REQ_FIND_PREV },
781 /* Misc */
782 { 'Q', REQ_QUIT },
783 { 'z', REQ_STOP_LOADING },
784 { 'v', REQ_SHOW_VERSION },
785 { 'r', REQ_SCREEN_REDRAW },
786 { '.', REQ_TOGGLE_LINENO },
787 { 'D', REQ_TOGGLE_DATE },
788 { 'A', REQ_TOGGLE_AUTHOR },
789 { 'g', REQ_TOGGLE_REV_GRAPH },
790 { 'F', REQ_TOGGLE_REFS },
791 { ':', REQ_PROMPT },
792 { 'u', REQ_STATUS_UPDATE },
793 { '!', REQ_STATUS_REVERT },
794 { 'M', REQ_STATUS_MERGE },
795 { '@', REQ_STAGE_NEXT },
796 { ',', REQ_TREE_PARENT },
797 { 'e', REQ_EDIT },
799 /* Using the ncurses SIGWINCH handler. */
800 { KEY_RESIZE, REQ_SCREEN_RESIZE },
803 #define KEYMAP_INFO \
804 KEYMAP_(GENERIC), \
805 KEYMAP_(MAIN), \
806 KEYMAP_(DIFF), \
807 KEYMAP_(LOG), \
808 KEYMAP_(TREE), \
809 KEYMAP_(BLOB), \
810 KEYMAP_(BLAME), \
811 KEYMAP_(PAGER), \
812 KEYMAP_(HELP), \
813 KEYMAP_(STATUS), \
814 KEYMAP_(STAGE)
816 enum keymap {
817 #define KEYMAP_(name) KEYMAP_##name
818 KEYMAP_INFO
819 #undef KEYMAP_
822 static struct int_map keymap_table[] = {
823 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
824 KEYMAP_INFO
825 #undef KEYMAP_
828 #define set_keymap(map, name) \
829 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
831 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
833 static void
834 add_keybinding(enum keymap keymap, enum request request, int key)
836 struct keybinding *keybinding;
838 keybinding = calloc(1, sizeof(*keybinding));
839 if (!keybinding)
840 die("Failed to allocate keybinding");
842 keybinding->alias = key;
843 keybinding->request = request;
844 keybinding->next = keybindings[keymap];
845 keybindings[keymap] = keybinding;
848 /* Looks for a key binding first in the given map, then in the generic map, and
849 * lastly in the default keybindings. */
850 static enum request
851 get_keybinding(enum keymap keymap, int key)
853 struct keybinding *kbd;
854 int i;
856 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
857 if (kbd->alias == key)
858 return kbd->request;
860 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
861 if (kbd->alias == key)
862 return kbd->request;
864 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
865 if (default_keybindings[i].alias == key)
866 return default_keybindings[i].request;
868 return (enum request) key;
872 struct key {
873 const char *name;
874 int value;
877 static struct key key_table[] = {
878 { "Enter", KEY_RETURN },
879 { "Space", ' ' },
880 { "Backspace", KEY_BACKSPACE },
881 { "Tab", KEY_TAB },
882 { "Escape", KEY_ESC },
883 { "Left", KEY_LEFT },
884 { "Right", KEY_RIGHT },
885 { "Up", KEY_UP },
886 { "Down", KEY_DOWN },
887 { "Insert", KEY_IC },
888 { "Delete", KEY_DC },
889 { "Hash", '#' },
890 { "Home", KEY_HOME },
891 { "End", KEY_END },
892 { "PageUp", KEY_PPAGE },
893 { "PageDown", KEY_NPAGE },
894 { "F1", KEY_F(1) },
895 { "F2", KEY_F(2) },
896 { "F3", KEY_F(3) },
897 { "F4", KEY_F(4) },
898 { "F5", KEY_F(5) },
899 { "F6", KEY_F(6) },
900 { "F7", KEY_F(7) },
901 { "F8", KEY_F(8) },
902 { "F9", KEY_F(9) },
903 { "F10", KEY_F(10) },
904 { "F11", KEY_F(11) },
905 { "F12", KEY_F(12) },
908 static int
909 get_key_value(const char *name)
911 int i;
913 for (i = 0; i < ARRAY_SIZE(key_table); i++)
914 if (!strcasecmp(key_table[i].name, name))
915 return key_table[i].value;
917 if (strlen(name) == 1 && isprint(*name))
918 return (int) *name;
920 return ERR;
923 static const char *
924 get_key_name(int key_value)
926 static char key_char[] = "'X'";
927 const char *seq = NULL;
928 int key;
930 for (key = 0; key < ARRAY_SIZE(key_table); key++)
931 if (key_table[key].value == key_value)
932 seq = key_table[key].name;
934 if (seq == NULL &&
935 key_value < 127 &&
936 isprint(key_value)) {
937 key_char[1] = (char) key_value;
938 seq = key_char;
941 return seq ? seq : "(no key)";
944 static const char *
945 get_key(enum request request)
947 static char buf[BUFSIZ];
948 size_t pos = 0;
949 char *sep = "";
950 int i;
952 buf[pos] = 0;
954 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
955 struct keybinding *keybinding = &default_keybindings[i];
957 if (keybinding->request != request)
958 continue;
960 if (!string_format_from(buf, &pos, "%s%s", sep,
961 get_key_name(keybinding->alias)))
962 return "Too many keybindings!";
963 sep = ", ";
966 return buf;
969 struct run_request {
970 enum keymap keymap;
971 int key;
972 char cmd[SIZEOF_STR];
975 static struct run_request *run_request;
976 static size_t run_requests;
978 static enum request
979 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
981 struct run_request *req;
982 char cmd[SIZEOF_STR];
983 size_t bufpos;
985 for (bufpos = 0; argc > 0; argc--, argv++)
986 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
987 return REQ_NONE;
989 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
990 if (!req)
991 return REQ_NONE;
993 run_request = req;
994 req = &run_request[run_requests++];
995 string_copy(req->cmd, cmd);
996 req->keymap = keymap;
997 req->key = key;
999 return REQ_NONE + run_requests;
1002 static struct run_request *
1003 get_run_request(enum request request)
1005 if (request <= REQ_NONE)
1006 return NULL;
1007 return &run_request[request - REQ_NONE - 1];
1010 static void
1011 add_builtin_run_requests(void)
1013 struct {
1014 enum keymap keymap;
1015 int key;
1016 const char *argv[1];
1017 } reqs[] = {
1018 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
1019 { KEYMAP_GENERIC, 'G', { "git gc" } },
1021 int i;
1023 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1024 enum request req;
1026 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1027 if (req != REQ_NONE)
1028 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1033 * User config file handling.
1036 static struct int_map color_map[] = {
1037 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1038 COLOR_MAP(DEFAULT),
1039 COLOR_MAP(BLACK),
1040 COLOR_MAP(BLUE),
1041 COLOR_MAP(CYAN),
1042 COLOR_MAP(GREEN),
1043 COLOR_MAP(MAGENTA),
1044 COLOR_MAP(RED),
1045 COLOR_MAP(WHITE),
1046 COLOR_MAP(YELLOW),
1049 #define set_color(color, name) \
1050 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1052 static struct int_map attr_map[] = {
1053 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1054 ATTR_MAP(NORMAL),
1055 ATTR_MAP(BLINK),
1056 ATTR_MAP(BOLD),
1057 ATTR_MAP(DIM),
1058 ATTR_MAP(REVERSE),
1059 ATTR_MAP(STANDOUT),
1060 ATTR_MAP(UNDERLINE),
1063 #define set_attribute(attr, name) \
1064 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1066 static int config_lineno;
1067 static bool config_errors;
1068 static const char *config_msg;
1070 /* Wants: object fgcolor bgcolor [attr] */
1071 static int
1072 option_color_command(int argc, const char *argv[])
1074 struct line_info *info;
1076 if (argc != 3 && argc != 4) {
1077 config_msg = "Wrong number of arguments given to color command";
1078 return ERR;
1081 info = get_line_info(argv[0]);
1082 if (!info) {
1083 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1084 info = get_line_info("delimiter");
1086 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1087 info = get_line_info("date");
1089 } else {
1090 config_msg = "Unknown color name";
1091 return ERR;
1095 if (set_color(&info->fg, argv[1]) == ERR ||
1096 set_color(&info->bg, argv[2]) == ERR) {
1097 config_msg = "Unknown color";
1098 return ERR;
1101 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1102 config_msg = "Unknown attribute";
1103 return ERR;
1106 return OK;
1109 static bool parse_bool(const char *s)
1111 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1112 !strcmp(s, "yes")) ? TRUE : FALSE;
1115 static int
1116 parse_int(const char *s, int default_value, int min, int max)
1118 int value = atoi(s);
1120 return (value < min || value > max) ? default_value : value;
1123 /* Wants: name = value */
1124 static int
1125 option_set_command(int argc, const char *argv[])
1127 if (argc != 3) {
1128 config_msg = "Wrong number of arguments given to set command";
1129 return ERR;
1132 if (strcmp(argv[1], "=")) {
1133 config_msg = "No value assigned";
1134 return ERR;
1137 if (!strcmp(argv[0], "show-author")) {
1138 opt_author = parse_bool(argv[2]);
1139 return OK;
1142 if (!strcmp(argv[0], "show-date")) {
1143 opt_date = parse_bool(argv[2]);
1144 return OK;
1147 if (!strcmp(argv[0], "show-rev-graph")) {
1148 opt_rev_graph = parse_bool(argv[2]);
1149 return OK;
1152 if (!strcmp(argv[0], "show-refs")) {
1153 opt_show_refs = parse_bool(argv[2]);
1154 return OK;
1157 if (!strcmp(argv[0], "show-line-numbers")) {
1158 opt_line_number = parse_bool(argv[2]);
1159 return OK;
1162 if (!strcmp(argv[0], "line-graphics")) {
1163 opt_line_graphics = parse_bool(argv[2]);
1164 return OK;
1167 if (!strcmp(argv[0], "line-number-interval")) {
1168 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1169 return OK;
1172 if (!strcmp(argv[0], "author-width")) {
1173 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1174 return OK;
1177 if (!strcmp(argv[0], "tab-size")) {
1178 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1179 return OK;
1182 if (!strcmp(argv[0], "commit-encoding")) {
1183 const char *arg = argv[2];
1184 int arglen = strlen(arg);
1186 switch (arg[0]) {
1187 case '"':
1188 case '\'':
1189 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1190 config_msg = "Unmatched quotation";
1191 return ERR;
1193 arg += 1; arglen -= 2;
1194 default:
1195 string_ncopy(opt_encoding, arg, strlen(arg));
1196 return OK;
1200 config_msg = "Unknown variable name";
1201 return ERR;
1204 /* Wants: mode request key */
1205 static int
1206 option_bind_command(int argc, const char *argv[])
1208 enum request request;
1209 int keymap;
1210 int key;
1212 if (argc < 3) {
1213 config_msg = "Wrong number of arguments given to bind command";
1214 return ERR;
1217 if (set_keymap(&keymap, argv[0]) == ERR) {
1218 config_msg = "Unknown key map";
1219 return ERR;
1222 key = get_key_value(argv[1]);
1223 if (key == ERR) {
1224 config_msg = "Unknown key";
1225 return ERR;
1228 request = get_request(argv[2]);
1229 if (request == REQ_NONE) {
1230 const char *obsolete[] = { "cherry-pick" };
1231 size_t namelen = strlen(argv[2]);
1232 int i;
1234 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1235 if (namelen == strlen(obsolete[i]) &&
1236 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1237 config_msg = "Obsolete request name";
1238 return ERR;
1242 if (request == REQ_NONE && *argv[2]++ == '!')
1243 request = add_run_request(keymap, key, argc - 2, argv + 2);
1244 if (request == REQ_NONE) {
1245 config_msg = "Unknown request name";
1246 return ERR;
1249 add_keybinding(keymap, request, key);
1251 return OK;
1254 static int
1255 set_option(const char *opt, char *value)
1257 const char *argv[SIZEOF_ARG];
1258 int valuelen;
1259 int argc = 0;
1261 /* Tokenize */
1262 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1263 argv[argc++] = value;
1264 value += valuelen;
1266 /* Nothing more to tokenize or last available token. */
1267 if (!*value || argc >= ARRAY_SIZE(argv))
1268 break;
1270 *value++ = 0;
1271 while (isspace(*value))
1272 value++;
1275 if (!strcmp(opt, "color"))
1276 return option_color_command(argc, argv);
1278 if (!strcmp(opt, "set"))
1279 return option_set_command(argc, argv);
1281 if (!strcmp(opt, "bind"))
1282 return option_bind_command(argc, argv);
1284 config_msg = "Unknown option command";
1285 return ERR;
1288 static int
1289 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1291 int status = OK;
1293 config_lineno++;
1294 config_msg = "Internal error";
1296 /* Check for comment markers, since read_properties() will
1297 * only ensure opt and value are split at first " \t". */
1298 optlen = strcspn(opt, "#");
1299 if (optlen == 0)
1300 return OK;
1302 if (opt[optlen] != 0) {
1303 config_msg = "No option value";
1304 status = ERR;
1306 } else {
1307 /* Look for comment endings in the value. */
1308 size_t len = strcspn(value, "#");
1310 if (len < valuelen) {
1311 valuelen = len;
1312 value[valuelen] = 0;
1315 status = set_option(opt, value);
1318 if (status == ERR) {
1319 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1320 config_lineno, (int) optlen, opt, config_msg);
1321 config_errors = TRUE;
1324 /* Always keep going if errors are encountered. */
1325 return OK;
1328 static void
1329 load_option_file(const char *path)
1331 FILE *file;
1333 /* It's ok that the file doesn't exist. */
1334 file = fopen(path, "r");
1335 if (!file)
1336 return;
1338 config_lineno = 0;
1339 config_errors = FALSE;
1341 if (read_properties(file, " \t", read_option) == ERR ||
1342 config_errors == TRUE)
1343 fprintf(stderr, "Errors while loading %s.\n", path);
1346 static int
1347 load_options(void)
1349 const char *home = getenv("HOME");
1350 const char *tigrc_user = getenv("TIGRC_USER");
1351 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1352 char buf[SIZEOF_STR];
1354 add_builtin_run_requests();
1356 if (!tigrc_system) {
1357 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1358 return ERR;
1359 tigrc_system = buf;
1361 load_option_file(tigrc_system);
1363 if (!tigrc_user) {
1364 if (!home || !string_format(buf, "%s/.tigrc", home))
1365 return ERR;
1366 tigrc_user = buf;
1368 load_option_file(tigrc_user);
1370 return OK;
1375 * The viewer
1378 struct view;
1379 struct view_ops;
1381 /* The display array of active views and the index of the current view. */
1382 static struct view *display[2];
1383 static unsigned int current_view;
1385 /* Reading from the prompt? */
1386 static bool input_mode = FALSE;
1388 #define foreach_displayed_view(view, i) \
1389 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1391 #define displayed_views() (display[1] != NULL ? 2 : 1)
1393 /* Current head and commit ID */
1394 static char ref_blob[SIZEOF_REF] = "";
1395 static char ref_commit[SIZEOF_REF] = "HEAD";
1396 static char ref_head[SIZEOF_REF] = "HEAD";
1398 struct view {
1399 const char *name; /* View name */
1400 const char *cmd_fmt; /* Default command line format */
1401 const char *cmd_env; /* Command line set via environment */
1402 const char *id; /* Points to either of ref_{head,commit,blob} */
1404 struct view_ops *ops; /* View operations */
1406 enum keymap keymap; /* What keymap does this view have */
1407 bool git_dir; /* Whether the view requires a git directory. */
1409 char cmd[SIZEOF_STR]; /* Command buffer */
1410 char ref[SIZEOF_REF]; /* Hovered commit reference */
1411 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1413 int height, width; /* The width and height of the main window */
1414 WINDOW *win; /* The main window */
1415 WINDOW *title; /* The title window living below the main window */
1417 /* Navigation */
1418 unsigned long offset; /* Offset of the window top */
1419 unsigned long lineno; /* Current line number */
1421 /* Searching */
1422 char grep[SIZEOF_STR]; /* Search string */
1423 regex_t *regex; /* Pre-compiled regex */
1425 /* If non-NULL, points to the view that opened this view. If this view
1426 * is closed tig will switch back to the parent view. */
1427 struct view *parent;
1429 /* Buffering */
1430 size_t lines; /* Total number of lines */
1431 struct line *line; /* Line index */
1432 size_t line_alloc; /* Total number of allocated lines */
1433 size_t line_size; /* Total number of used lines */
1434 unsigned int digits; /* Number of digits in the lines member. */
1436 /* Drawing */
1437 struct line *curline; /* Line currently being drawn. */
1438 enum line_type curtype; /* Attribute currently used for drawing. */
1439 unsigned long col; /* Column when drawing. */
1441 /* Loading */
1442 FILE *pipe;
1443 time_t start_time;
1446 struct view_ops {
1447 /* What type of content being displayed. Used in the title bar. */
1448 const char *type;
1449 /* Open and reads in all view content. */
1450 bool (*open)(struct view *view);
1451 /* Read one line; updates view->line. */
1452 bool (*read)(struct view *view, char *data);
1453 /* Draw one line; @lineno must be < view->height. */
1454 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1455 /* Depending on view handle a special requests. */
1456 enum request (*request)(struct view *view, enum request request, struct line *line);
1457 /* Search for regex in a line. */
1458 bool (*grep)(struct view *view, struct line *line);
1459 /* Select line */
1460 void (*select)(struct view *view, struct line *line);
1463 static struct view_ops blame_ops;
1464 static struct view_ops blob_ops;
1465 static struct view_ops help_ops;
1466 static struct view_ops log_ops;
1467 static struct view_ops main_ops;
1468 static struct view_ops pager_ops;
1469 static struct view_ops stage_ops;
1470 static struct view_ops status_ops;
1471 static struct view_ops tree_ops;
1473 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1474 { name, cmd, #env, ref, ops, map, git }
1476 #define VIEW_(id, name, ops, git, ref) \
1477 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1480 static struct view views[] = {
1481 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1482 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1483 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1484 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1485 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1486 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1487 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1488 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1489 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1490 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1493 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1494 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1496 #define foreach_view(view, i) \
1497 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1499 #define view_is_displayed(view) \
1500 (view == display[0] || view == display[1])
1503 enum line_graphic {
1504 LINE_GRAPHIC_VLINE
1507 static int line_graphics[] = {
1508 /* LINE_GRAPHIC_VLINE: */ '|'
1511 static inline void
1512 set_view_attr(struct view *view, enum line_type type)
1514 if (!view->curline->selected && view->curtype != type) {
1515 wattrset(view->win, get_line_attr(type));
1516 wchgat(view->win, -1, 0, type, NULL);
1517 view->curtype = type;
1521 static int
1522 draw_chars(struct view *view, enum line_type type, const char *string,
1523 int max_len, bool use_tilde)
1525 int len = 0;
1526 int col = 0;
1527 int trimmed = FALSE;
1529 if (max_len <= 0)
1530 return 0;
1532 if (opt_utf8) {
1533 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1534 } else {
1535 col = len = strlen(string);
1536 if (len > max_len) {
1537 if (use_tilde) {
1538 max_len -= 1;
1540 col = len = max_len;
1541 trimmed = TRUE;
1545 set_view_attr(view, type);
1546 waddnstr(view->win, string, len);
1547 if (trimmed && use_tilde) {
1548 set_view_attr(view, LINE_DELIMITER);
1549 waddch(view->win, '~');
1550 col++;
1553 return col;
1556 static int
1557 draw_space(struct view *view, enum line_type type, int max, int spaces)
1559 static char space[] = " ";
1560 int col = 0;
1562 spaces = MIN(max, spaces);
1564 while (spaces > 0) {
1565 int len = MIN(spaces, sizeof(space) - 1);
1567 col += draw_chars(view, type, space, spaces, FALSE);
1568 spaces -= len;
1571 return col;
1574 static bool
1575 draw_lineno(struct view *view, unsigned int lineno)
1577 char number[10];
1578 int digits3 = view->digits < 3 ? 3 : view->digits;
1579 int max_number = MIN(digits3, STRING_SIZE(number));
1580 int max = view->width - view->col;
1581 int col;
1583 if (max < max_number)
1584 max_number = max;
1586 lineno += view->offset + 1;
1587 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1588 static char fmt[] = "%1ld";
1590 if (view->digits <= 9)
1591 fmt[1] = '0' + digits3;
1593 if (!string_format(number, fmt, lineno))
1594 number[0] = 0;
1595 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1596 } else {
1597 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1600 if (col < max) {
1601 set_view_attr(view, LINE_DEFAULT);
1602 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1603 col++;
1606 if (col < max)
1607 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1608 view->col += col;
1610 return view->width - view->col <= 0;
1613 static bool
1614 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1616 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1617 return view->width - view->col <= 0;
1620 static bool
1621 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1623 int max = view->width - view->col;
1624 int i;
1626 if (max < size)
1627 size = max;
1629 set_view_attr(view, type);
1630 /* Using waddch() instead of waddnstr() ensures that
1631 * they'll be rendered correctly for the cursor line. */
1632 for (i = 0; i < size; i++)
1633 waddch(view->win, graphic[i]);
1635 view->col += size;
1636 if (size < max) {
1637 waddch(view->win, ' ');
1638 view->col++;
1641 return view->width - view->col <= 0;
1644 static bool
1645 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1647 int max = MIN(view->width - view->col, len);
1648 int col;
1650 if (text)
1651 col = draw_chars(view, type, text, max - 1, trim);
1652 else
1653 col = draw_space(view, type, max - 1, max - 1);
1655 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1656 return view->width - view->col <= 0;
1659 static bool
1660 draw_date(struct view *view, struct tm *time)
1662 char buf[DATE_COLS];
1663 char *date;
1664 int timelen = 0;
1666 if (time)
1667 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1668 date = timelen ? buf : NULL;
1670 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1673 static bool
1674 draw_view_line(struct view *view, unsigned int lineno)
1676 struct line *line;
1677 bool selected = (view->offset + lineno == view->lineno);
1678 bool draw_ok;
1680 assert(view_is_displayed(view));
1682 if (view->offset + lineno >= view->lines)
1683 return FALSE;
1685 line = &view->line[view->offset + lineno];
1687 wmove(view->win, lineno, 0);
1688 view->col = 0;
1689 view->curline = line;
1690 view->curtype = LINE_NONE;
1691 line->selected = FALSE;
1693 if (selected) {
1694 set_view_attr(view, LINE_CURSOR);
1695 line->selected = TRUE;
1696 view->ops->select(view, line);
1697 } else if (line->selected) {
1698 wclrtoeol(view->win);
1701 scrollok(view->win, FALSE);
1702 draw_ok = view->ops->draw(view, line, lineno);
1703 scrollok(view->win, TRUE);
1705 return draw_ok;
1708 static void
1709 redraw_view_dirty(struct view *view)
1711 bool dirty = FALSE;
1712 int lineno;
1714 for (lineno = 0; lineno < view->height; lineno++) {
1715 struct line *line = &view->line[view->offset + lineno];
1717 if (!line->dirty)
1718 continue;
1719 line->dirty = 0;
1720 dirty = TRUE;
1721 if (!draw_view_line(view, lineno))
1722 break;
1725 if (!dirty)
1726 return;
1727 redrawwin(view->win);
1728 if (input_mode)
1729 wnoutrefresh(view->win);
1730 else
1731 wrefresh(view->win);
1734 static void
1735 redraw_view_from(struct view *view, int lineno)
1737 assert(0 <= lineno && lineno < view->height);
1739 for (; lineno < view->height; lineno++) {
1740 if (!draw_view_line(view, lineno))
1741 break;
1744 redrawwin(view->win);
1745 if (input_mode)
1746 wnoutrefresh(view->win);
1747 else
1748 wrefresh(view->win);
1751 static void
1752 redraw_view(struct view *view)
1754 wclear(view->win);
1755 redraw_view_from(view, 0);
1759 static void
1760 update_view_title(struct view *view)
1762 char buf[SIZEOF_STR];
1763 char state[SIZEOF_STR];
1764 size_t bufpos = 0, statelen = 0;
1766 assert(view_is_displayed(view));
1768 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1769 unsigned int view_lines = view->offset + view->height;
1770 unsigned int lines = view->lines
1771 ? MIN(view_lines, view->lines) * 100 / view->lines
1772 : 0;
1774 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1775 view->ops->type,
1776 view->lineno + 1,
1777 view->lines,
1778 lines);
1780 if (view->pipe) {
1781 time_t secs = time(NULL) - view->start_time;
1783 /* Three git seconds are a long time ... */
1784 if (secs > 2)
1785 string_format_from(state, &statelen, " %lds", secs);
1789 string_format_from(buf, &bufpos, "[%s]", view->name);
1790 if (*view->ref && bufpos < view->width) {
1791 size_t refsize = strlen(view->ref);
1792 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1794 if (minsize < view->width)
1795 refsize = view->width - minsize + 7;
1796 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1799 if (statelen && bufpos < view->width) {
1800 string_format_from(buf, &bufpos, " %s", state);
1803 if (view == display[current_view])
1804 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1805 else
1806 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1808 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1809 wclrtoeol(view->title);
1810 wmove(view->title, 0, view->width - 1);
1812 if (input_mode)
1813 wnoutrefresh(view->title);
1814 else
1815 wrefresh(view->title);
1818 static void
1819 resize_display(void)
1821 int offset, i;
1822 struct view *base = display[0];
1823 struct view *view = display[1] ? display[1] : display[0];
1825 /* Setup window dimensions */
1827 getmaxyx(stdscr, base->height, base->width);
1829 /* Make room for the status window. */
1830 base->height -= 1;
1832 if (view != base) {
1833 /* Horizontal split. */
1834 view->width = base->width;
1835 view->height = SCALE_SPLIT_VIEW(base->height);
1836 base->height -= view->height;
1838 /* Make room for the title bar. */
1839 view->height -= 1;
1842 /* Make room for the title bar. */
1843 base->height -= 1;
1845 offset = 0;
1847 foreach_displayed_view (view, i) {
1848 if (!view->win) {
1849 view->win = newwin(view->height, 0, offset, 0);
1850 if (!view->win)
1851 die("Failed to create %s view", view->name);
1853 scrollok(view->win, TRUE);
1855 view->title = newwin(1, 0, offset + view->height, 0);
1856 if (!view->title)
1857 die("Failed to create title window");
1859 } else {
1860 wresize(view->win, view->height, view->width);
1861 mvwin(view->win, offset, 0);
1862 mvwin(view->title, offset + view->height, 0);
1865 offset += view->height + 1;
1869 static void
1870 redraw_display(void)
1872 struct view *view;
1873 int i;
1875 foreach_displayed_view (view, i) {
1876 redraw_view(view);
1877 update_view_title(view);
1881 static void
1882 update_display_cursor(struct view *view)
1884 /* Move the cursor to the right-most column of the cursor line.
1886 * XXX: This could turn out to be a bit expensive, but it ensures that
1887 * the cursor does not jump around. */
1888 if (view->lines) {
1889 wmove(view->win, view->lineno - view->offset, view->width - 1);
1890 wrefresh(view->win);
1895 * Navigation
1898 /* Scrolling backend */
1899 static void
1900 do_scroll_view(struct view *view, int lines)
1902 bool redraw_current_line = FALSE;
1904 /* The rendering expects the new offset. */
1905 view->offset += lines;
1907 assert(0 <= view->offset && view->offset < view->lines);
1908 assert(lines);
1910 /* Move current line into the view. */
1911 if (view->lineno < view->offset) {
1912 view->lineno = view->offset;
1913 redraw_current_line = TRUE;
1914 } else if (view->lineno >= view->offset + view->height) {
1915 view->lineno = view->offset + view->height - 1;
1916 redraw_current_line = TRUE;
1919 assert(view->offset <= view->lineno && view->lineno < view->lines);
1921 /* Redraw the whole screen if scrolling is pointless. */
1922 if (view->height < ABS(lines)) {
1923 redraw_view(view);
1925 } else {
1926 int line = lines > 0 ? view->height - lines : 0;
1927 int end = line + ABS(lines);
1929 wscrl(view->win, lines);
1931 for (; line < end; line++) {
1932 if (!draw_view_line(view, line))
1933 break;
1936 if (redraw_current_line)
1937 draw_view_line(view, view->lineno - view->offset);
1940 redrawwin(view->win);
1941 wrefresh(view->win);
1942 report("");
1945 /* Scroll frontend */
1946 static void
1947 scroll_view(struct view *view, enum request request)
1949 int lines = 1;
1951 assert(view_is_displayed(view));
1953 switch (request) {
1954 case REQ_SCROLL_PAGE_DOWN:
1955 lines = view->height;
1956 case REQ_SCROLL_LINE_DOWN:
1957 if (view->offset + lines > view->lines)
1958 lines = view->lines - view->offset;
1960 if (lines == 0 || view->offset + view->height >= view->lines) {
1961 report("Cannot scroll beyond the last line");
1962 return;
1964 break;
1966 case REQ_SCROLL_PAGE_UP:
1967 lines = view->height;
1968 case REQ_SCROLL_LINE_UP:
1969 if (lines > view->offset)
1970 lines = view->offset;
1972 if (lines == 0) {
1973 report("Cannot scroll beyond the first line");
1974 return;
1977 lines = -lines;
1978 break;
1980 default:
1981 die("request %d not handled in switch", request);
1984 do_scroll_view(view, lines);
1987 /* Cursor moving */
1988 static void
1989 move_view(struct view *view, enum request request)
1991 int scroll_steps = 0;
1992 int steps;
1994 switch (request) {
1995 case REQ_MOVE_FIRST_LINE:
1996 steps = -view->lineno;
1997 break;
1999 case REQ_MOVE_LAST_LINE:
2000 steps = view->lines - view->lineno - 1;
2001 break;
2003 case REQ_MOVE_PAGE_UP:
2004 steps = view->height > view->lineno
2005 ? -view->lineno : -view->height;
2006 break;
2008 case REQ_MOVE_PAGE_DOWN:
2009 steps = view->lineno + view->height >= view->lines
2010 ? view->lines - view->lineno - 1 : view->height;
2011 break;
2013 case REQ_MOVE_UP:
2014 steps = -1;
2015 break;
2017 case REQ_MOVE_DOWN:
2018 steps = 1;
2019 break;
2021 default:
2022 die("request %d not handled in switch", request);
2025 if (steps <= 0 && view->lineno == 0) {
2026 report("Cannot move beyond the first line");
2027 return;
2029 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2030 report("Cannot move beyond the last line");
2031 return;
2034 /* Move the current line */
2035 view->lineno += steps;
2036 assert(0 <= view->lineno && view->lineno < view->lines);
2038 /* Check whether the view needs to be scrolled */
2039 if (view->lineno < view->offset ||
2040 view->lineno >= view->offset + view->height) {
2041 scroll_steps = steps;
2042 if (steps < 0 && -steps > view->offset) {
2043 scroll_steps = -view->offset;
2045 } else if (steps > 0) {
2046 if (view->lineno == view->lines - 1 &&
2047 view->lines > view->height) {
2048 scroll_steps = view->lines - view->offset - 1;
2049 if (scroll_steps >= view->height)
2050 scroll_steps -= view->height - 1;
2055 if (!view_is_displayed(view)) {
2056 view->offset += scroll_steps;
2057 assert(0 <= view->offset && view->offset < view->lines);
2058 view->ops->select(view, &view->line[view->lineno]);
2059 return;
2062 /* Repaint the old "current" line if we be scrolling */
2063 if (ABS(steps) < view->height)
2064 draw_view_line(view, view->lineno - steps - view->offset);
2066 if (scroll_steps) {
2067 do_scroll_view(view, scroll_steps);
2068 return;
2071 /* Draw the current line */
2072 draw_view_line(view, view->lineno - view->offset);
2074 redrawwin(view->win);
2075 wrefresh(view->win);
2076 report("");
2081 * Searching
2084 static void search_view(struct view *view, enum request request);
2086 static bool
2087 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2089 assert(view_is_displayed(view));
2091 if (!view->ops->grep(view, line))
2092 return FALSE;
2094 if (lineno - view->offset >= view->height) {
2095 view->offset = lineno;
2096 view->lineno = lineno;
2097 redraw_view(view);
2099 } else {
2100 unsigned long old_lineno = view->lineno - view->offset;
2102 view->lineno = lineno;
2103 draw_view_line(view, old_lineno);
2105 draw_view_line(view, view->lineno - view->offset);
2106 redrawwin(view->win);
2107 wrefresh(view->win);
2110 report("Line %ld matches '%s'", lineno + 1, view->grep);
2111 return TRUE;
2114 static void
2115 find_next(struct view *view, enum request request)
2117 unsigned long lineno = view->lineno;
2118 int direction;
2120 if (!*view->grep) {
2121 if (!*opt_search)
2122 report("No previous search");
2123 else
2124 search_view(view, request);
2125 return;
2128 switch (request) {
2129 case REQ_SEARCH:
2130 case REQ_FIND_NEXT:
2131 direction = 1;
2132 break;
2134 case REQ_SEARCH_BACK:
2135 case REQ_FIND_PREV:
2136 direction = -1;
2137 break;
2139 default:
2140 return;
2143 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2144 lineno += direction;
2146 /* Note, lineno is unsigned long so will wrap around in which case it
2147 * will become bigger than view->lines. */
2148 for (; lineno < view->lines; lineno += direction) {
2149 struct line *line = &view->line[lineno];
2151 if (find_next_line(view, lineno, line))
2152 return;
2155 report("No match found for '%s'", view->grep);
2158 static void
2159 search_view(struct view *view, enum request request)
2161 int regex_err;
2163 if (view->regex) {
2164 regfree(view->regex);
2165 *view->grep = 0;
2166 } else {
2167 view->regex = calloc(1, sizeof(*view->regex));
2168 if (!view->regex)
2169 return;
2172 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2173 if (regex_err != 0) {
2174 char buf[SIZEOF_STR] = "unknown error";
2176 regerror(regex_err, view->regex, buf, sizeof(buf));
2177 report("Search failed: %s", buf);
2178 return;
2181 string_copy(view->grep, opt_search);
2183 find_next(view, request);
2187 * Incremental updating
2190 static void
2191 reset_view(struct view *view)
2193 int i;
2195 for (i = 0; i < view->lines; i++)
2196 free(view->line[i].data);
2197 free(view->line);
2199 view->line = NULL;
2200 view->offset = 0;
2201 view->lines = 0;
2202 view->lineno = 0;
2203 view->line_size = 0;
2204 view->line_alloc = 0;
2205 view->vid[0] = 0;
2208 static void
2209 end_update(struct view *view, bool force)
2211 if (!view->pipe)
2212 return;
2213 while (!view->ops->read(view, NULL))
2214 if (!force)
2215 return;
2216 set_nonblocking_input(FALSE);
2217 if (view->pipe == stdin)
2218 fclose(view->pipe);
2219 else
2220 pclose(view->pipe);
2221 view->pipe = NULL;
2224 static bool
2225 begin_update(struct view *view, bool refresh)
2227 if (opt_cmd[0]) {
2228 string_copy(view->cmd, opt_cmd);
2229 opt_cmd[0] = 0;
2230 /* When running random commands, initially show the
2231 * command in the title. However, it maybe later be
2232 * overwritten if a commit line is selected. */
2233 if (view == VIEW(REQ_VIEW_PAGER))
2234 string_copy(view->ref, view->cmd);
2235 else
2236 view->ref[0] = 0;
2238 } else if (view == VIEW(REQ_VIEW_TREE)) {
2239 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2240 char path[SIZEOF_STR];
2242 if (strcmp(view->vid, view->id))
2243 opt_path[0] = path[0] = 0;
2244 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2245 return FALSE;
2247 if (!string_format(view->cmd, format, view->id, path))
2248 return FALSE;
2250 } else if (!refresh) {
2251 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2252 const char *id = view->id;
2254 if (!string_format(view->cmd, format, id, id, id, id, id))
2255 return FALSE;
2257 /* Put the current ref_* value to the view title ref
2258 * member. This is needed by the blob view. Most other
2259 * views sets it automatically after loading because the
2260 * first line is a commit line. */
2261 string_copy_rev(view->ref, view->id);
2264 /* Special case for the pager view. */
2265 if (opt_pipe) {
2266 view->pipe = opt_pipe;
2267 opt_pipe = NULL;
2268 } else {
2269 view->pipe = popen(view->cmd, "r");
2272 if (!view->pipe)
2273 return FALSE;
2275 set_nonblocking_input(TRUE);
2276 reset_view(view);
2277 string_copy_rev(view->vid, view->id);
2279 view->start_time = time(NULL);
2281 return TRUE;
2284 #define ITEM_CHUNK_SIZE 256
2285 static void *
2286 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2288 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2289 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2291 if (mem == NULL || num_chunks != num_chunks_new) {
2292 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2293 mem = realloc(mem, *size * item_size);
2296 return mem;
2299 static struct line *
2300 realloc_lines(struct view *view, size_t line_size)
2302 size_t alloc = view->line_alloc;
2303 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2304 sizeof(*view->line));
2306 if (!tmp)
2307 return NULL;
2309 view->line = tmp;
2310 view->line_alloc = alloc;
2311 view->line_size = line_size;
2312 return view->line;
2315 static bool
2316 update_view(struct view *view)
2318 char in_buffer[BUFSIZ];
2319 char out_buffer[BUFSIZ * 2];
2320 char *line;
2321 /* The number of lines to read. If too low it will cause too much
2322 * redrawing (and possible flickering), if too high responsiveness
2323 * will suffer. */
2324 unsigned long lines = view->height;
2325 int redraw_from = -1;
2327 if (!view->pipe)
2328 return TRUE;
2330 /* Only redraw if lines are visible. */
2331 if (view->offset + view->height >= view->lines)
2332 redraw_from = view->lines - view->offset;
2334 /* FIXME: This is probably not perfect for backgrounded views. */
2335 if (!realloc_lines(view, view->lines + lines))
2336 goto alloc_error;
2338 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2339 size_t linelen = strlen(line);
2341 if (linelen)
2342 line[linelen - 1] = 0;
2344 if (opt_iconv != ICONV_NONE) {
2345 ICONV_CONST char *inbuf = line;
2346 size_t inlen = linelen;
2348 char *outbuf = out_buffer;
2349 size_t outlen = sizeof(out_buffer);
2351 size_t ret;
2353 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2354 if (ret != (size_t) -1) {
2355 line = out_buffer;
2356 linelen = strlen(out_buffer);
2360 if (!view->ops->read(view, line))
2361 goto alloc_error;
2363 if (lines-- == 1)
2364 break;
2368 int digits;
2370 lines = view->lines;
2371 for (digits = 0; lines; digits++)
2372 lines /= 10;
2374 /* Keep the displayed view in sync with line number scaling. */
2375 if (digits != view->digits) {
2376 view->digits = digits;
2377 redraw_from = 0;
2381 if (!view_is_displayed(view))
2382 goto check_pipe;
2384 if (view == VIEW(REQ_VIEW_TREE)) {
2385 /* Clear the view and redraw everything since the tree sorting
2386 * might have rearranged things. */
2387 redraw_view(view);
2389 } else if (redraw_from >= 0) {
2390 /* If this is an incremental update, redraw the previous line
2391 * since for commits some members could have changed when
2392 * loading the main view. */
2393 if (redraw_from > 0)
2394 redraw_from--;
2396 /* Since revision graph visualization requires knowledge
2397 * about the parent commit, it causes a further one-off
2398 * needed to be redrawn for incremental updates. */
2399 if (redraw_from > 0 && opt_rev_graph)
2400 redraw_from--;
2402 /* Incrementally draw avoids flickering. */
2403 redraw_view_from(view, redraw_from);
2406 if (view == VIEW(REQ_VIEW_BLAME))
2407 redraw_view_dirty(view);
2409 /* Update the title _after_ the redraw so that if the redraw picks up a
2410 * commit reference in view->ref it'll be available here. */
2411 update_view_title(view);
2413 check_pipe:
2414 if (ferror(view->pipe) && errno != 0) {
2415 report("Failed to read: %s", strerror(errno));
2416 end_update(view, TRUE);
2418 } else if (feof(view->pipe)) {
2419 report("");
2420 end_update(view, FALSE);
2423 return TRUE;
2425 alloc_error:
2426 report("Allocation failure");
2427 end_update(view, TRUE);
2428 return FALSE;
2431 static struct line *
2432 add_line_data(struct view *view, void *data, enum line_type type)
2434 struct line *line = &view->line[view->lines++];
2436 memset(line, 0, sizeof(*line));
2437 line->type = type;
2438 line->data = data;
2440 return line;
2443 static struct line *
2444 add_line_text(struct view *view, const char *text, enum line_type type)
2446 char *data = text ? strdup(text) : NULL;
2448 return data ? add_line_data(view, data, type) : NULL;
2453 * View opening
2456 enum open_flags {
2457 OPEN_DEFAULT = 0, /* Use default view switching. */
2458 OPEN_SPLIT = 1, /* Split current view. */
2459 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2460 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2461 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2462 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2465 static void
2466 open_view(struct view *prev, enum request request, enum open_flags flags)
2468 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2469 bool split = !!(flags & OPEN_SPLIT);
2470 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2471 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2472 struct view *view = VIEW(request);
2473 int nviews = displayed_views();
2474 struct view *base_view = display[0];
2476 if (view == prev && nviews == 1 && !reload) {
2477 report("Already in %s view", view->name);
2478 return;
2481 if (view->git_dir && !opt_git_dir[0]) {
2482 report("The %s view is disabled in pager view", view->name);
2483 return;
2486 if (split) {
2487 display[1] = view;
2488 if (!backgrounded)
2489 current_view = 1;
2490 } else if (!nomaximize) {
2491 /* Maximize the current view. */
2492 memset(display, 0, sizeof(display));
2493 current_view = 0;
2494 display[current_view] = view;
2497 /* Resize the view when switching between split- and full-screen,
2498 * or when switching between two different full-screen views. */
2499 if (nviews != displayed_views() ||
2500 (nviews == 1 && base_view != display[0]))
2501 resize_display();
2503 if (view->pipe)
2504 end_update(view, TRUE);
2506 if (view->ops->open) {
2507 if (!view->ops->open(view)) {
2508 report("Failed to load %s view", view->name);
2509 return;
2512 } else if ((reload || strcmp(view->vid, view->id)) &&
2513 !begin_update(view, flags & OPEN_REFRESH)) {
2514 report("Failed to load %s view", view->name);
2515 return;
2518 if (split && prev->lineno - prev->offset >= prev->height) {
2519 /* Take the title line into account. */
2520 int lines = prev->lineno - prev->offset - prev->height + 1;
2522 /* Scroll the view that was split if the current line is
2523 * outside the new limited view. */
2524 do_scroll_view(prev, lines);
2527 if (prev && view != prev) {
2528 if (split && !backgrounded) {
2529 /* "Blur" the previous view. */
2530 update_view_title(prev);
2533 view->parent = prev;
2536 if (view->pipe && view->lines == 0) {
2537 /* Clear the old view and let the incremental updating refill
2538 * the screen. */
2539 werase(view->win);
2540 report("");
2541 } else if (view_is_displayed(view)) {
2542 redraw_view(view);
2543 report("");
2546 /* If the view is backgrounded the above calls to report()
2547 * won't redraw the view title. */
2548 if (backgrounded)
2549 update_view_title(view);
2552 static bool
2553 run_confirm(const char *cmd, const char *prompt)
2555 bool confirmation = prompt_yesno(prompt);
2557 if (confirmation)
2558 system(cmd);
2560 return confirmation;
2563 static void
2564 open_external_viewer(const char *cmd)
2566 def_prog_mode(); /* save current tty modes */
2567 endwin(); /* restore original tty modes */
2568 system(cmd);
2569 fprintf(stderr, "Press Enter to continue");
2570 getc(stdin);
2571 reset_prog_mode();
2572 redraw_display();
2575 static void
2576 open_mergetool(const char *file)
2578 char cmd[SIZEOF_STR];
2579 char file_sq[SIZEOF_STR];
2581 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2582 string_format(cmd, "git mergetool %s", file_sq)) {
2583 open_external_viewer(cmd);
2587 static void
2588 open_editor(bool from_root, const char *file)
2590 char cmd[SIZEOF_STR];
2591 char file_sq[SIZEOF_STR];
2592 const char *editor;
2593 char *prefix = from_root ? opt_cdup : "";
2595 editor = getenv("GIT_EDITOR");
2596 if (!editor && *opt_editor)
2597 editor = opt_editor;
2598 if (!editor)
2599 editor = getenv("VISUAL");
2600 if (!editor)
2601 editor = getenv("EDITOR");
2602 if (!editor)
2603 editor = "vi";
2605 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2606 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2607 open_external_viewer(cmd);
2611 static void
2612 open_run_request(enum request request)
2614 struct run_request *req = get_run_request(request);
2615 char buf[SIZEOF_STR * 2];
2616 size_t bufpos;
2617 char *cmd;
2619 if (!req) {
2620 report("Unknown run request");
2621 return;
2624 bufpos = 0;
2625 cmd = req->cmd;
2627 while (cmd) {
2628 char *next = strstr(cmd, "%(");
2629 int len = next - cmd;
2630 char *value;
2632 if (!next) {
2633 len = strlen(cmd);
2634 value = "";
2636 } else if (!strncmp(next, "%(head)", 7)) {
2637 value = ref_head;
2639 } else if (!strncmp(next, "%(commit)", 9)) {
2640 value = ref_commit;
2642 } else if (!strncmp(next, "%(blob)", 7)) {
2643 value = ref_blob;
2645 } else {
2646 report("Unknown replacement in run request: `%s`", req->cmd);
2647 return;
2650 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2651 return;
2653 if (next)
2654 next = strchr(next, ')') + 1;
2655 cmd = next;
2658 open_external_viewer(buf);
2662 * User request switch noodle
2665 static int
2666 view_driver(struct view *view, enum request request)
2668 int i;
2670 if (request == REQ_NONE) {
2671 doupdate();
2672 return TRUE;
2675 if (request > REQ_NONE) {
2676 open_run_request(request);
2677 /* FIXME: When all views can refresh always do this. */
2678 if (view == VIEW(REQ_VIEW_STATUS) ||
2679 view == VIEW(REQ_VIEW_MAIN) ||
2680 view == VIEW(REQ_VIEW_LOG) ||
2681 view == VIEW(REQ_VIEW_STAGE))
2682 request = REQ_REFRESH;
2683 else
2684 return TRUE;
2687 if (view && view->lines) {
2688 request = view->ops->request(view, request, &view->line[view->lineno]);
2689 if (request == REQ_NONE)
2690 return TRUE;
2693 switch (request) {
2694 case REQ_MOVE_UP:
2695 case REQ_MOVE_DOWN:
2696 case REQ_MOVE_PAGE_UP:
2697 case REQ_MOVE_PAGE_DOWN:
2698 case REQ_MOVE_FIRST_LINE:
2699 case REQ_MOVE_LAST_LINE:
2700 move_view(view, request);
2701 break;
2703 case REQ_SCROLL_LINE_DOWN:
2704 case REQ_SCROLL_LINE_UP:
2705 case REQ_SCROLL_PAGE_DOWN:
2706 case REQ_SCROLL_PAGE_UP:
2707 scroll_view(view, request);
2708 break;
2710 case REQ_VIEW_BLAME:
2711 if (!opt_file[0]) {
2712 report("No file chosen, press %s to open tree view",
2713 get_key(REQ_VIEW_TREE));
2714 break;
2716 open_view(view, request, OPEN_DEFAULT);
2717 break;
2719 case REQ_VIEW_BLOB:
2720 if (!ref_blob[0]) {
2721 report("No file chosen, press %s to open tree view",
2722 get_key(REQ_VIEW_TREE));
2723 break;
2725 open_view(view, request, OPEN_DEFAULT);
2726 break;
2728 case REQ_VIEW_PAGER:
2729 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2730 report("No pager content, press %s to run command from prompt",
2731 get_key(REQ_PROMPT));
2732 break;
2734 open_view(view, request, OPEN_DEFAULT);
2735 break;
2737 case REQ_VIEW_STAGE:
2738 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2739 report("No stage content, press %s to open the status view and choose file",
2740 get_key(REQ_VIEW_STATUS));
2741 break;
2743 open_view(view, request, OPEN_DEFAULT);
2744 break;
2746 case REQ_VIEW_STATUS:
2747 if (opt_is_inside_work_tree == FALSE) {
2748 report("The status view requires a working tree");
2749 break;
2751 open_view(view, request, OPEN_DEFAULT);
2752 break;
2754 case REQ_VIEW_MAIN:
2755 case REQ_VIEW_DIFF:
2756 case REQ_VIEW_LOG:
2757 case REQ_VIEW_TREE:
2758 case REQ_VIEW_HELP:
2759 open_view(view, request, OPEN_DEFAULT);
2760 break;
2762 case REQ_NEXT:
2763 case REQ_PREVIOUS:
2764 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2766 if ((view == VIEW(REQ_VIEW_DIFF) &&
2767 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2768 (view == VIEW(REQ_VIEW_DIFF) &&
2769 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2770 (view == VIEW(REQ_VIEW_STAGE) &&
2771 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2772 (view == VIEW(REQ_VIEW_BLOB) &&
2773 view->parent == VIEW(REQ_VIEW_TREE))) {
2774 int line;
2776 view = view->parent;
2777 line = view->lineno;
2778 move_view(view, request);
2779 if (view_is_displayed(view))
2780 update_view_title(view);
2781 if (line != view->lineno)
2782 view->ops->request(view, REQ_ENTER,
2783 &view->line[view->lineno]);
2785 } else {
2786 move_view(view, request);
2788 break;
2790 case REQ_VIEW_NEXT:
2792 int nviews = displayed_views();
2793 int next_view = (current_view + 1) % nviews;
2795 if (next_view == current_view) {
2796 report("Only one view is displayed");
2797 break;
2800 current_view = next_view;
2801 /* Blur out the title of the previous view. */
2802 update_view_title(view);
2803 report("");
2804 break;
2806 case REQ_REFRESH:
2807 report("Refreshing is not yet supported for the %s view", view->name);
2808 break;
2810 case REQ_MAXIMIZE:
2811 if (displayed_views() == 2)
2812 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2813 break;
2815 case REQ_TOGGLE_LINENO:
2816 opt_line_number = !opt_line_number;
2817 redraw_display();
2818 break;
2820 case REQ_TOGGLE_DATE:
2821 opt_date = !opt_date;
2822 redraw_display();
2823 break;
2825 case REQ_TOGGLE_AUTHOR:
2826 opt_author = !opt_author;
2827 redraw_display();
2828 break;
2830 case REQ_TOGGLE_REV_GRAPH:
2831 opt_rev_graph = !opt_rev_graph;
2832 redraw_display();
2833 break;
2835 case REQ_TOGGLE_REFS:
2836 opt_show_refs = !opt_show_refs;
2837 redraw_display();
2838 break;
2840 case REQ_SEARCH:
2841 case REQ_SEARCH_BACK:
2842 search_view(view, request);
2843 break;
2845 case REQ_FIND_NEXT:
2846 case REQ_FIND_PREV:
2847 find_next(view, request);
2848 break;
2850 case REQ_STOP_LOADING:
2851 for (i = 0; i < ARRAY_SIZE(views); i++) {
2852 view = &views[i];
2853 if (view->pipe)
2854 report("Stopped loading the %s view", view->name),
2855 end_update(view, TRUE);
2857 break;
2859 case REQ_SHOW_VERSION:
2860 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2861 return TRUE;
2863 case REQ_SCREEN_RESIZE:
2864 resize_display();
2865 /* Fall-through */
2866 case REQ_SCREEN_REDRAW:
2867 redraw_display();
2868 break;
2870 case REQ_EDIT:
2871 report("Nothing to edit");
2872 break;
2874 case REQ_ENTER:
2875 report("Nothing to enter");
2876 break;
2878 case REQ_VIEW_CLOSE:
2879 /* XXX: Mark closed views by letting view->parent point to the
2880 * view itself. Parents to closed view should never be
2881 * followed. */
2882 if (view->parent &&
2883 view->parent->parent != view->parent) {
2884 memset(display, 0, sizeof(display));
2885 current_view = 0;
2886 display[current_view] = view->parent;
2887 view->parent = view;
2888 resize_display();
2889 redraw_display();
2890 report("");
2891 break;
2893 /* Fall-through */
2894 case REQ_QUIT:
2895 return FALSE;
2897 default:
2898 report("Unknown key, press 'h' for help");
2899 return TRUE;
2902 return TRUE;
2907 * Pager backend
2910 static bool
2911 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2913 char *text = line->data;
2915 if (opt_line_number && draw_lineno(view, lineno))
2916 return TRUE;
2918 draw_text(view, line->type, text, TRUE);
2919 return TRUE;
2922 static bool
2923 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
2925 char refbuf[SIZEOF_STR];
2926 char *ref = NULL;
2927 FILE *pipe;
2929 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2930 return TRUE;
2932 pipe = popen(refbuf, "r");
2933 if (!pipe)
2934 return TRUE;
2936 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2937 ref = chomp_string(ref);
2938 pclose(pipe);
2940 if (!ref || !*ref)
2941 return TRUE;
2943 /* This is the only fatal call, since it can "corrupt" the buffer. */
2944 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2945 return FALSE;
2947 return TRUE;
2950 static void
2951 add_pager_refs(struct view *view, struct line *line)
2953 char buf[SIZEOF_STR];
2954 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2955 struct ref **refs;
2956 size_t bufpos = 0, refpos = 0;
2957 const char *sep = "Refs: ";
2958 bool is_tag = FALSE;
2960 assert(line->type == LINE_COMMIT);
2962 refs = get_refs(commit_id);
2963 if (!refs) {
2964 if (view == VIEW(REQ_VIEW_DIFF))
2965 goto try_add_describe_ref;
2966 return;
2969 do {
2970 struct ref *ref = refs[refpos];
2971 const char *fmt = ref->tag ? "%s[%s]" :
2972 ref->remote ? "%s<%s>" : "%s%s";
2974 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2975 return;
2976 sep = ", ";
2977 if (ref->tag)
2978 is_tag = TRUE;
2979 } while (refs[refpos++]->next);
2981 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2982 try_add_describe_ref:
2983 /* Add <tag>-g<commit_id> "fake" reference. */
2984 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2985 return;
2988 if (bufpos == 0)
2989 return;
2991 if (!realloc_lines(view, view->line_size + 1))
2992 return;
2994 add_line_text(view, buf, LINE_PP_REFS);
2997 static bool
2998 pager_read(struct view *view, char *data)
3000 struct line *line;
3002 if (!data)
3003 return TRUE;
3005 line = add_line_text(view, data, get_line_type(data));
3006 if (!line)
3007 return FALSE;
3009 if (line->type == LINE_COMMIT &&
3010 (view == VIEW(REQ_VIEW_DIFF) ||
3011 view == VIEW(REQ_VIEW_LOG)))
3012 add_pager_refs(view, line);
3014 return TRUE;
3017 static enum request
3018 pager_request(struct view *view, enum request request, struct line *line)
3020 int split = 0;
3022 if (request != REQ_ENTER)
3023 return request;
3025 if (line->type == LINE_COMMIT &&
3026 (view == VIEW(REQ_VIEW_LOG) ||
3027 view == VIEW(REQ_VIEW_PAGER))) {
3028 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3029 split = 1;
3032 /* Always scroll the view even if it was split. That way
3033 * you can use Enter to scroll through the log view and
3034 * split open each commit diff. */
3035 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3037 /* FIXME: A minor workaround. Scrolling the view will call report("")
3038 * but if we are scrolling a non-current view this won't properly
3039 * update the view title. */
3040 if (split)
3041 update_view_title(view);
3043 return REQ_NONE;
3046 static bool
3047 pager_grep(struct view *view, struct line *line)
3049 regmatch_t pmatch;
3050 char *text = line->data;
3052 if (!*text)
3053 return FALSE;
3055 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3056 return FALSE;
3058 return TRUE;
3061 static void
3062 pager_select(struct view *view, struct line *line)
3064 if (line->type == LINE_COMMIT) {
3065 char *text = (char *)line->data + STRING_SIZE("commit ");
3067 if (view != VIEW(REQ_VIEW_PAGER))
3068 string_copy_rev(view->ref, text);
3069 string_copy_rev(ref_commit, text);
3073 static struct view_ops pager_ops = {
3074 "line",
3075 NULL,
3076 pager_read,
3077 pager_draw,
3078 pager_request,
3079 pager_grep,
3080 pager_select,
3083 static enum request
3084 log_request(struct view *view, enum request request, struct line *line)
3086 switch (request) {
3087 case REQ_REFRESH:
3088 load_refs();
3089 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3090 return REQ_NONE;
3091 default:
3092 return pager_request(view, request, line);
3096 static struct view_ops log_ops = {
3097 "line",
3098 NULL,
3099 pager_read,
3100 pager_draw,
3101 log_request,
3102 pager_grep,
3103 pager_select,
3108 * Help backend
3111 static bool
3112 help_open(struct view *view)
3114 char buf[BUFSIZ];
3115 int lines = ARRAY_SIZE(req_info) + 2;
3116 int i;
3118 if (view->lines > 0)
3119 return TRUE;
3121 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3122 if (!req_info[i].request)
3123 lines++;
3125 lines += run_requests + 1;
3127 view->line = calloc(lines, sizeof(*view->line));
3128 if (!view->line)
3129 return FALSE;
3131 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3133 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3134 const char *key;
3136 if (req_info[i].request == REQ_NONE)
3137 continue;
3139 if (!req_info[i].request) {
3140 add_line_text(view, "", LINE_DEFAULT);
3141 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3142 continue;
3145 key = get_key(req_info[i].request);
3146 if (!*key)
3147 key = "(no key defined)";
3149 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3150 continue;
3152 add_line_text(view, buf, LINE_DEFAULT);
3155 if (run_requests) {
3156 add_line_text(view, "", LINE_DEFAULT);
3157 add_line_text(view, "External commands:", LINE_DEFAULT);
3160 for (i = 0; i < run_requests; i++) {
3161 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3162 const char *key;
3164 if (!req)
3165 continue;
3167 key = get_key_name(req->key);
3168 if (!*key)
3169 key = "(no key defined)";
3171 if (!string_format(buf, " %-10s %-14s `%s`",
3172 keymap_table[req->keymap].name,
3173 key, req->cmd))
3174 continue;
3176 add_line_text(view, buf, LINE_DEFAULT);
3179 return TRUE;
3182 static struct view_ops help_ops = {
3183 "line",
3184 help_open,
3185 NULL,
3186 pager_draw,
3187 pager_request,
3188 pager_grep,
3189 pager_select,
3194 * Tree backend
3197 struct tree_stack_entry {
3198 struct tree_stack_entry *prev; /* Entry below this in the stack */
3199 unsigned long lineno; /* Line number to restore */
3200 char *name; /* Position of name in opt_path */
3203 /* The top of the path stack. */
3204 static struct tree_stack_entry *tree_stack = NULL;
3205 unsigned long tree_lineno = 0;
3207 static void
3208 pop_tree_stack_entry(void)
3210 struct tree_stack_entry *entry = tree_stack;
3212 tree_lineno = entry->lineno;
3213 entry->name[0] = 0;
3214 tree_stack = entry->prev;
3215 free(entry);
3218 static void
3219 push_tree_stack_entry(const char *name, unsigned long lineno)
3221 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3222 size_t pathlen = strlen(opt_path);
3224 if (!entry)
3225 return;
3227 entry->prev = tree_stack;
3228 entry->name = opt_path + pathlen;
3229 tree_stack = entry;
3231 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3232 pop_tree_stack_entry();
3233 return;
3236 /* Move the current line to the first tree entry. */
3237 tree_lineno = 1;
3238 entry->lineno = lineno;
3241 /* Parse output from git-ls-tree(1):
3243 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3244 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3245 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3246 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3249 #define SIZEOF_TREE_ATTR \
3250 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3252 #define TREE_UP_FORMAT "040000 tree %s\t.."
3254 static int
3255 tree_compare_entry(enum line_type type1, const char *name1,
3256 enum line_type type2, const char *name2)
3258 if (type1 != type2) {
3259 if (type1 == LINE_TREE_DIR)
3260 return -1;
3261 return 1;
3264 return strcmp(name1, name2);
3267 static const char *
3268 tree_path(struct line *line)
3270 const char *path = line->data;
3272 return path + SIZEOF_TREE_ATTR;
3275 static bool
3276 tree_read(struct view *view, char *text)
3278 size_t textlen = text ? strlen(text) : 0;
3279 char buf[SIZEOF_STR];
3280 unsigned long pos;
3281 enum line_type type;
3282 bool first_read = view->lines == 0;
3284 if (!text)
3285 return TRUE;
3286 if (textlen <= SIZEOF_TREE_ATTR)
3287 return FALSE;
3289 type = text[STRING_SIZE("100644 ")] == 't'
3290 ? LINE_TREE_DIR : LINE_TREE_FILE;
3292 if (first_read) {
3293 /* Add path info line */
3294 if (!string_format(buf, "Directory path /%s", opt_path) ||
3295 !realloc_lines(view, view->line_size + 1) ||
3296 !add_line_text(view, buf, LINE_DEFAULT))
3297 return FALSE;
3299 /* Insert "link" to parent directory. */
3300 if (*opt_path) {
3301 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3302 !realloc_lines(view, view->line_size + 1) ||
3303 !add_line_text(view, buf, LINE_TREE_DIR))
3304 return FALSE;
3308 /* Strip the path part ... */
3309 if (*opt_path) {
3310 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3311 size_t striplen = strlen(opt_path);
3312 char *path = text + SIZEOF_TREE_ATTR;
3314 if (pathlen > striplen)
3315 memmove(path, path + striplen,
3316 pathlen - striplen + 1);
3319 /* Skip "Directory ..." and ".." line. */
3320 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3321 struct line *line = &view->line[pos];
3322 const char *path1 = tree_path(line);
3323 char *path2 = text + SIZEOF_TREE_ATTR;
3324 int cmp = tree_compare_entry(line->type, path1, type, path2);
3326 if (cmp <= 0)
3327 continue;
3329 text = strdup(text);
3330 if (!text)
3331 return FALSE;
3333 if (view->lines > pos)
3334 memmove(&view->line[pos + 1], &view->line[pos],
3335 (view->lines - pos) * sizeof(*line));
3337 line = &view->line[pos];
3338 line->data = text;
3339 line->type = type;
3340 view->lines++;
3341 return TRUE;
3344 if (!add_line_text(view, text, type))
3345 return FALSE;
3347 if (tree_lineno > view->lineno) {
3348 view->lineno = tree_lineno;
3349 tree_lineno = 0;
3352 return TRUE;
3355 static enum request
3356 tree_request(struct view *view, enum request request, struct line *line)
3358 enum open_flags flags;
3360 if (request == REQ_VIEW_BLAME) {
3361 const char *filename = tree_path(line);
3363 if (line->type == LINE_TREE_DIR) {
3364 report("Cannot show blame for directory %s", opt_path);
3365 return REQ_NONE;
3368 string_copy(opt_ref, view->vid);
3369 string_format(opt_file, "%s%s", opt_path, filename);
3370 return request;
3372 if (request == REQ_TREE_PARENT) {
3373 if (*opt_path) {
3374 /* fake 'cd ..' */
3375 request = REQ_ENTER;
3376 line = &view->line[1];
3377 } else {
3378 /* quit view if at top of tree */
3379 return REQ_VIEW_CLOSE;
3382 if (request != REQ_ENTER)
3383 return request;
3385 /* Cleanup the stack if the tree view is at a different tree. */
3386 while (!*opt_path && tree_stack)
3387 pop_tree_stack_entry();
3389 switch (line->type) {
3390 case LINE_TREE_DIR:
3391 /* Depending on whether it is a subdir or parent (updir?) link
3392 * mangle the path buffer. */
3393 if (line == &view->line[1] && *opt_path) {
3394 pop_tree_stack_entry();
3396 } else {
3397 const char *basename = tree_path(line);
3399 push_tree_stack_entry(basename, view->lineno);
3402 /* Trees and subtrees share the same ID, so they are not not
3403 * unique like blobs. */
3404 flags = OPEN_RELOAD;
3405 request = REQ_VIEW_TREE;
3406 break;
3408 case LINE_TREE_FILE:
3409 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3410 request = REQ_VIEW_BLOB;
3411 break;
3413 default:
3414 return TRUE;
3417 open_view(view, request, flags);
3418 if (request == REQ_VIEW_TREE) {
3419 view->lineno = tree_lineno;
3422 return REQ_NONE;
3425 static void
3426 tree_select(struct view *view, struct line *line)
3428 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3430 if (line->type == LINE_TREE_FILE) {
3431 string_copy_rev(ref_blob, text);
3433 } else if (line->type != LINE_TREE_DIR) {
3434 return;
3437 string_copy_rev(view->ref, text);
3440 static struct view_ops tree_ops = {
3441 "file",
3442 NULL,
3443 tree_read,
3444 pager_draw,
3445 tree_request,
3446 pager_grep,
3447 tree_select,
3450 static bool
3451 blob_read(struct view *view, char *line)
3453 if (!line)
3454 return TRUE;
3455 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3458 static struct view_ops blob_ops = {
3459 "line",
3460 NULL,
3461 blob_read,
3462 pager_draw,
3463 pager_request,
3464 pager_grep,
3465 pager_select,
3469 * Blame backend
3471 * Loading the blame view is a two phase job:
3473 * 1. File content is read either using opt_file from the
3474 * filesystem or using git-cat-file.
3475 * 2. Then blame information is incrementally added by
3476 * reading output from git-blame.
3479 struct blame_commit {
3480 char id[SIZEOF_REV]; /* SHA1 ID. */
3481 char title[128]; /* First line of the commit message. */
3482 char author[75]; /* Author of the commit. */
3483 struct tm time; /* Date from the author ident. */
3484 char filename[128]; /* Name of file. */
3487 struct blame {
3488 struct blame_commit *commit;
3489 unsigned int header:1;
3490 char text[1];
3493 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3494 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3496 static bool
3497 blame_open(struct view *view)
3499 char path[SIZEOF_STR];
3500 char ref[SIZEOF_STR] = "";
3502 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3503 return FALSE;
3505 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3506 return FALSE;
3508 if (*opt_ref) {
3509 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3510 return FALSE;
3511 } else {
3512 view->pipe = fopen(opt_file, "r");
3513 if (!view->pipe &&
3514 !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3515 return FALSE;
3518 if (!view->pipe)
3519 view->pipe = popen(view->cmd, "r");
3520 if (!view->pipe)
3521 return FALSE;
3523 if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3524 return FALSE;
3526 reset_view(view);
3527 string_format(view->ref, "%s ...", opt_file);
3528 string_copy_rev(view->vid, opt_file);
3529 set_nonblocking_input(TRUE);
3530 view->start_time = time(NULL);
3532 return TRUE;
3535 static struct blame_commit *
3536 get_blame_commit(struct view *view, const char *id)
3538 size_t i;
3540 for (i = 0; i < view->lines; i++) {
3541 struct blame *blame = view->line[i].data;
3543 if (!blame->commit)
3544 continue;
3546 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3547 return blame->commit;
3551 struct blame_commit *commit = calloc(1, sizeof(*commit));
3553 if (commit)
3554 string_ncopy(commit->id, id, SIZEOF_REV);
3555 return commit;
3559 static bool
3560 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3562 const char *pos = *posref;
3564 *posref = NULL;
3565 pos = strchr(pos + 1, ' ');
3566 if (!pos || !isdigit(pos[1]))
3567 return FALSE;
3568 *number = atoi(pos + 1);
3569 if (*number < min || *number > max)
3570 return FALSE;
3572 *posref = pos;
3573 return TRUE;
3576 static struct blame_commit *
3577 parse_blame_commit(struct view *view, const char *text, int *blamed)
3579 struct blame_commit *commit;
3580 struct blame *blame;
3581 const char *pos = text + SIZEOF_REV - 1;
3582 size_t lineno;
3583 size_t group;
3585 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3586 return NULL;
3588 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3589 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3590 return NULL;
3592 commit = get_blame_commit(view, text);
3593 if (!commit)
3594 return NULL;
3596 *blamed += group;
3597 while (group--) {
3598 struct line *line = &view->line[lineno + group - 1];
3600 blame = line->data;
3601 blame->commit = commit;
3602 blame->header = !group;
3603 line->dirty = 1;
3606 return commit;
3609 static bool
3610 blame_read_file(struct view *view, const char *line)
3612 if (!line) {
3613 FILE *pipe = NULL;
3615 if (view->lines > 0)
3616 pipe = popen(view->cmd, "r");
3617 else if (!view->parent)
3618 die("No blame exist for %s", view->vid);
3619 view->cmd[0] = 0;
3620 if (!pipe) {
3621 report("Failed to load blame data");
3622 return TRUE;
3625 fclose(view->pipe);
3626 view->pipe = pipe;
3627 return FALSE;
3629 } else {
3630 size_t linelen = strlen(line);
3631 struct blame *blame = malloc(sizeof(*blame) + linelen);
3633 blame->commit = NULL;
3634 strncpy(blame->text, line, linelen);
3635 blame->text[linelen] = 0;
3636 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3640 static bool
3641 match_blame_header(const char *name, char **line)
3643 size_t namelen = strlen(name);
3644 bool matched = !strncmp(name, *line, namelen);
3646 if (matched)
3647 *line += namelen;
3649 return matched;
3652 static bool
3653 blame_read(struct view *view, char *line)
3655 static struct blame_commit *commit = NULL;
3656 static int blamed = 0;
3657 static time_t author_time;
3659 if (*view->cmd)
3660 return blame_read_file(view, line);
3662 if (!line) {
3663 /* Reset all! */
3664 commit = NULL;
3665 blamed = 0;
3666 string_format(view->ref, "%s", view->vid);
3667 if (view_is_displayed(view)) {
3668 update_view_title(view);
3669 redraw_view_from(view, 0);
3671 return TRUE;
3674 if (!commit) {
3675 commit = parse_blame_commit(view, line, &blamed);
3676 string_format(view->ref, "%s %2d%%", view->vid,
3677 blamed * 100 / view->lines);
3679 } else if (match_blame_header("author ", &line)) {
3680 string_ncopy(commit->author, line, strlen(line));
3682 } else if (match_blame_header("author-time ", &line)) {
3683 author_time = (time_t) atol(line);
3685 } else if (match_blame_header("author-tz ", &line)) {
3686 long tz;
3688 tz = ('0' - line[1]) * 60 * 60 * 10;
3689 tz += ('0' - line[2]) * 60 * 60;
3690 tz += ('0' - line[3]) * 60;
3691 tz += ('0' - line[4]) * 60;
3693 if (line[0] == '-')
3694 tz = -tz;
3696 author_time -= tz;
3697 gmtime_r(&author_time, &commit->time);
3699 } else if (match_blame_header("summary ", &line)) {
3700 string_ncopy(commit->title, line, strlen(line));
3702 } else if (match_blame_header("filename ", &line)) {
3703 string_ncopy(commit->filename, line, strlen(line));
3704 commit = NULL;
3707 return TRUE;
3710 static bool
3711 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3713 struct blame *blame = line->data;
3714 struct tm *time = NULL;
3715 const char *id = NULL, *author = NULL;
3717 if (blame->commit && *blame->commit->filename) {
3718 id = blame->commit->id;
3719 author = blame->commit->author;
3720 time = &blame->commit->time;
3723 if (opt_date && draw_date(view, time))
3724 return TRUE;
3726 if (opt_author &&
3727 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3728 return TRUE;
3730 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3731 return TRUE;
3733 if (draw_lineno(view, lineno))
3734 return TRUE;
3736 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3737 return TRUE;
3740 static enum request
3741 blame_request(struct view *view, enum request request, struct line *line)
3743 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3744 struct blame *blame = line->data;
3746 switch (request) {
3747 case REQ_ENTER:
3748 if (!blame->commit) {
3749 report("No commit loaded yet");
3750 break;
3753 if (!strcmp(blame->commit->id, NULL_ID)) {
3754 char path[SIZEOF_STR];
3756 if (sq_quote(path, 0, view->vid) >= sizeof(path))
3757 break;
3758 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3761 open_view(view, REQ_VIEW_DIFF, flags);
3762 break;
3764 default:
3765 return request;
3768 return REQ_NONE;
3771 static bool
3772 blame_grep(struct view *view, struct line *line)
3774 struct blame *blame = line->data;
3775 struct blame_commit *commit = blame->commit;
3776 regmatch_t pmatch;
3778 #define MATCH(text, on) \
3779 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3781 if (commit) {
3782 char buf[DATE_COLS + 1];
3784 if (MATCH(commit->title, 1) ||
3785 MATCH(commit->author, opt_author) ||
3786 MATCH(commit->id, opt_date))
3787 return TRUE;
3789 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3790 MATCH(buf, 1))
3791 return TRUE;
3794 return MATCH(blame->text, 1);
3796 #undef MATCH
3799 static void
3800 blame_select(struct view *view, struct line *line)
3802 struct blame *blame = line->data;
3803 struct blame_commit *commit = blame->commit;
3805 if (!commit)
3806 return;
3808 if (!strcmp(commit->id, NULL_ID))
3809 string_ncopy(ref_commit, "HEAD", 4);
3810 else
3811 string_copy_rev(ref_commit, commit->id);
3814 static struct view_ops blame_ops = {
3815 "line",
3816 blame_open,
3817 blame_read,
3818 blame_draw,
3819 blame_request,
3820 blame_grep,
3821 blame_select,
3825 * Status backend
3828 struct status {
3829 char status;
3830 struct {
3831 mode_t mode;
3832 char rev[SIZEOF_REV];
3833 char name[SIZEOF_STR];
3834 } old;
3835 struct {
3836 mode_t mode;
3837 char rev[SIZEOF_REV];
3838 char name[SIZEOF_STR];
3839 } new;
3842 static char status_onbranch[SIZEOF_STR];
3843 static struct status stage_status;
3844 static enum line_type stage_line_type;
3845 static size_t stage_chunks;
3846 static int *stage_chunk;
3848 /* This should work even for the "On branch" line. */
3849 static inline bool
3850 status_has_none(struct view *view, struct line *line)
3852 return line < view->line + view->lines && !line[1].data;
3855 /* Get fields from the diff line:
3856 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3858 static inline bool
3859 status_get_diff(struct status *file, const char *buf, size_t bufsize)
3861 const char *old_mode = buf + 1;
3862 const char *new_mode = buf + 8;
3863 const char *old_rev = buf + 15;
3864 const char *new_rev = buf + 56;
3865 const char *status = buf + 97;
3867 if (bufsize < 99 ||
3868 old_mode[-1] != ':' ||
3869 new_mode[-1] != ' ' ||
3870 old_rev[-1] != ' ' ||
3871 new_rev[-1] != ' ' ||
3872 status[-1] != ' ')
3873 return FALSE;
3875 file->status = *status;
3877 string_copy_rev(file->old.rev, old_rev);
3878 string_copy_rev(file->new.rev, new_rev);
3880 file->old.mode = strtoul(old_mode, NULL, 8);
3881 file->new.mode = strtoul(new_mode, NULL, 8);
3883 file->old.name[0] = file->new.name[0] = 0;
3885 return TRUE;
3888 static bool
3889 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3891 struct status *file = NULL;
3892 struct status *unmerged = NULL;
3893 char buf[SIZEOF_STR * 4];
3894 size_t bufsize = 0;
3895 FILE *pipe;
3897 pipe = popen(cmd, "r");
3898 if (!pipe)
3899 return FALSE;
3901 add_line_data(view, NULL, type);
3903 while (!feof(pipe) && !ferror(pipe)) {
3904 char *sep;
3905 size_t readsize;
3907 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3908 if (!readsize)
3909 break;
3910 bufsize += readsize;
3912 /* Process while we have NUL chars. */
3913 while ((sep = memchr(buf, 0, bufsize))) {
3914 size_t sepsize = sep - buf + 1;
3916 if (!file) {
3917 if (!realloc_lines(view, view->line_size + 1))
3918 goto error_out;
3920 file = calloc(1, sizeof(*file));
3921 if (!file)
3922 goto error_out;
3924 add_line_data(view, file, type);
3927 /* Parse diff info part. */
3928 if (status) {
3929 file->status = status;
3930 if (status == 'A')
3931 string_copy(file->old.rev, NULL_ID);
3933 } else if (!file->status) {
3934 if (!status_get_diff(file, buf, sepsize))
3935 goto error_out;
3937 bufsize -= sepsize;
3938 memmove(buf, sep + 1, bufsize);
3940 sep = memchr(buf, 0, bufsize);
3941 if (!sep)
3942 break;
3943 sepsize = sep - buf + 1;
3945 /* Collapse all 'M'odified entries that
3946 * follow a associated 'U'nmerged entry.
3948 if (file->status == 'U') {
3949 unmerged = file;
3951 } else if (unmerged) {
3952 int collapse = !strcmp(buf, unmerged->new.name);
3954 unmerged = NULL;
3955 if (collapse) {
3956 free(file);
3957 view->lines--;
3958 continue;
3963 /* Grab the old name for rename/copy. */
3964 if (!*file->old.name &&
3965 (file->status == 'R' || file->status == 'C')) {
3966 sepsize = sep - buf + 1;
3967 string_ncopy(file->old.name, buf, sepsize);
3968 bufsize -= sepsize;
3969 memmove(buf, sep + 1, bufsize);
3971 sep = memchr(buf, 0, bufsize);
3972 if (!sep)
3973 break;
3974 sepsize = sep - buf + 1;
3977 /* git-ls-files just delivers a NUL separated
3978 * list of file names similar to the second half
3979 * of the git-diff-* output. */
3980 string_ncopy(file->new.name, buf, sepsize);
3981 if (!*file->old.name)
3982 string_copy(file->old.name, file->new.name);
3983 bufsize -= sepsize;
3984 memmove(buf, sep + 1, bufsize);
3985 file = NULL;
3989 if (ferror(pipe)) {
3990 error_out:
3991 pclose(pipe);
3992 return FALSE;
3995 if (!view->line[view->lines - 1].data)
3996 add_line_data(view, NULL, LINE_STAT_NONE);
3998 pclose(pipe);
3999 return TRUE;
4002 /* Don't show unmerged entries in the staged section. */
4003 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4004 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4005 #define STATUS_LIST_OTHER_CMD \
4006 "git ls-files -z --others --exclude-standard"
4007 #define STATUS_LIST_NO_HEAD_CMD \
4008 "git ls-files -z --cached --exclude-standard"
4010 #define STATUS_DIFF_INDEX_SHOW_CMD \
4011 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4013 #define STATUS_DIFF_FILES_SHOW_CMD \
4014 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4016 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4017 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4019 /* First parse staged info using git-diff-index(1), then parse unstaged
4020 * info using git-diff-files(1), and finally untracked files using
4021 * git-ls-files(1). */
4022 static bool
4023 status_open(struct view *view)
4025 unsigned long prev_lineno = view->lineno;
4027 reset_view(view);
4029 if (!realloc_lines(view, view->line_size + 7))
4030 return FALSE;
4032 add_line_data(view, NULL, LINE_STAT_HEAD);
4033 if (opt_no_head)
4034 string_copy(status_onbranch, "Initial commit");
4035 else if (!*opt_head)
4036 string_copy(status_onbranch, "Not currently on any branch");
4037 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4038 return FALSE;
4040 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4042 if (opt_no_head) {
4043 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4044 return FALSE;
4045 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4046 return FALSE;
4049 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4050 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4051 return FALSE;
4053 /* If all went well restore the previous line number to stay in
4054 * the context or select a line with something that can be
4055 * updated. */
4056 if (prev_lineno >= view->lines)
4057 prev_lineno = view->lines - 1;
4058 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4059 prev_lineno++;
4060 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4061 prev_lineno--;
4063 /* If the above fails, always skip the "On branch" line. */
4064 if (prev_lineno < view->lines)
4065 view->lineno = prev_lineno;
4066 else
4067 view->lineno = 1;
4069 if (view->lineno < view->offset)
4070 view->offset = view->lineno;
4071 else if (view->offset + view->height <= view->lineno)
4072 view->offset = view->lineno - view->height + 1;
4074 return TRUE;
4077 static bool
4078 status_draw(struct view *view, struct line *line, unsigned int lineno)
4080 struct status *status = line->data;
4081 enum line_type type;
4082 const char *text;
4084 if (!status) {
4085 switch (line->type) {
4086 case LINE_STAT_STAGED:
4087 type = LINE_STAT_SECTION;
4088 text = "Changes to be committed:";
4089 break;
4091 case LINE_STAT_UNSTAGED:
4092 type = LINE_STAT_SECTION;
4093 text = "Changed but not updated:";
4094 break;
4096 case LINE_STAT_UNTRACKED:
4097 type = LINE_STAT_SECTION;
4098 text = "Untracked files:";
4099 break;
4101 case LINE_STAT_NONE:
4102 type = LINE_DEFAULT;
4103 text = " (no files)";
4104 break;
4106 case LINE_STAT_HEAD:
4107 type = LINE_STAT_HEAD;
4108 text = status_onbranch;
4109 break;
4111 default:
4112 return FALSE;
4114 } else {
4115 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4117 buf[0] = status->status;
4118 if (draw_text(view, line->type, buf, TRUE))
4119 return TRUE;
4120 type = LINE_DEFAULT;
4121 text = status->new.name;
4124 draw_text(view, type, text, TRUE);
4125 return TRUE;
4128 static enum request
4129 status_enter(struct view *view, struct line *line)
4131 struct status *status = line->data;
4132 char oldpath[SIZEOF_STR] = "";
4133 char newpath[SIZEOF_STR] = "";
4134 const char *info;
4135 size_t cmdsize = 0;
4136 enum open_flags split;
4138 if (line->type == LINE_STAT_NONE ||
4139 (!status && line[1].type == LINE_STAT_NONE)) {
4140 report("No file to diff");
4141 return REQ_NONE;
4144 if (status) {
4145 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4146 return REQ_QUIT;
4147 /* Diffs for unmerged entries are empty when pasing the
4148 * new path, so leave it empty. */
4149 if (status->status != 'U' &&
4150 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4151 return REQ_QUIT;
4154 if (opt_cdup[0] &&
4155 line->type != LINE_STAT_UNTRACKED &&
4156 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4157 return REQ_QUIT;
4159 switch (line->type) {
4160 case LINE_STAT_STAGED:
4161 if (opt_no_head) {
4162 if (!string_format_from(opt_cmd, &cmdsize,
4163 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4164 newpath))
4165 return REQ_QUIT;
4166 } else {
4167 if (!string_format_from(opt_cmd, &cmdsize,
4168 STATUS_DIFF_INDEX_SHOW_CMD,
4169 oldpath, newpath))
4170 return REQ_QUIT;
4173 if (status)
4174 info = "Staged changes to %s";
4175 else
4176 info = "Staged changes";
4177 break;
4179 case LINE_STAT_UNSTAGED:
4180 if (!string_format_from(opt_cmd, &cmdsize,
4181 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4182 return REQ_QUIT;
4183 if (status)
4184 info = "Unstaged changes to %s";
4185 else
4186 info = "Unstaged changes";
4187 break;
4189 case LINE_STAT_UNTRACKED:
4190 if (opt_pipe)
4191 return REQ_QUIT;
4193 if (!status) {
4194 report("No file to show");
4195 return REQ_NONE;
4198 opt_pipe = fopen(status->new.name, "r");
4199 info = "Untracked file %s";
4200 break;
4202 case LINE_STAT_HEAD:
4203 return REQ_NONE;
4205 default:
4206 die("line type %d not handled in switch", line->type);
4209 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4210 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4211 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4212 if (status) {
4213 stage_status = *status;
4214 } else {
4215 memset(&stage_status, 0, sizeof(stage_status));
4218 stage_line_type = line->type;
4219 stage_chunks = 0;
4220 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4223 return REQ_NONE;
4226 static bool
4227 status_exists(struct status *status, enum line_type type)
4229 struct view *view = VIEW(REQ_VIEW_STATUS);
4230 struct line *line;
4232 for (line = view->line; line < view->line + view->lines; line++) {
4233 struct status *pos = line->data;
4235 if (line->type == type && pos &&
4236 !strcmp(status->new.name, pos->new.name))
4237 return TRUE;
4240 return FALSE;
4244 static FILE *
4245 status_update_prepare(enum line_type type)
4247 char cmd[SIZEOF_STR];
4248 size_t cmdsize = 0;
4250 if (opt_cdup[0] &&
4251 type != LINE_STAT_UNTRACKED &&
4252 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4253 return NULL;
4255 switch (type) {
4256 case LINE_STAT_STAGED:
4257 string_add(cmd, cmdsize, "git update-index -z --index-info");
4258 break;
4260 case LINE_STAT_UNSTAGED:
4261 case LINE_STAT_UNTRACKED:
4262 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4263 break;
4265 default:
4266 die("line type %d not handled in switch", type);
4269 return popen(cmd, "w");
4272 static bool
4273 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4275 char buf[SIZEOF_STR];
4276 size_t bufsize = 0;
4277 size_t written = 0;
4279 switch (type) {
4280 case LINE_STAT_STAGED:
4281 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4282 status->old.mode,
4283 status->old.rev,
4284 status->old.name, 0))
4285 return FALSE;
4286 break;
4288 case LINE_STAT_UNSTAGED:
4289 case LINE_STAT_UNTRACKED:
4290 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4291 return FALSE;
4292 break;
4294 default:
4295 die("line type %d not handled in switch", type);
4298 while (!ferror(pipe) && written < bufsize) {
4299 written += fwrite(buf + written, 1, bufsize - written, pipe);
4302 return written == bufsize;
4305 static bool
4306 status_update_file(struct status *status, enum line_type type)
4308 FILE *pipe = status_update_prepare(type);
4309 bool result;
4311 if (!pipe)
4312 return FALSE;
4314 result = status_update_write(pipe, status, type);
4315 pclose(pipe);
4316 return result;
4319 static bool
4320 status_update_files(struct view *view, struct line *line)
4322 FILE *pipe = status_update_prepare(line->type);
4323 bool result = TRUE;
4324 struct line *pos = view->line + view->lines;
4325 int files = 0;
4326 int file, done;
4328 if (!pipe)
4329 return FALSE;
4331 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4332 files++;
4334 for (file = 0, done = 0; result && file < files; line++, file++) {
4335 int almost_done = file * 100 / files;
4337 if (almost_done > done) {
4338 done = almost_done;
4339 string_format(view->ref, "updating file %u of %u (%d%% done)",
4340 file, files, done);
4341 update_view_title(view);
4343 result = status_update_write(pipe, line->data, line->type);
4346 pclose(pipe);
4347 return result;
4350 static bool
4351 status_update(struct view *view)
4353 struct line *line = &view->line[view->lineno];
4355 assert(view->lines);
4357 if (!line->data) {
4358 /* This should work even for the "On branch" line. */
4359 if (line < view->line + view->lines && !line[1].data) {
4360 report("Nothing to update");
4361 return FALSE;
4364 if (!status_update_files(view, line + 1)) {
4365 report("Failed to update file status");
4366 return FALSE;
4369 } else if (!status_update_file(line->data, line->type)) {
4370 report("Failed to update file status");
4371 return FALSE;
4374 return TRUE;
4377 static bool
4378 status_revert(struct status *status, enum line_type type, bool has_none)
4380 if (!status || type != LINE_STAT_UNSTAGED) {
4381 if (type == LINE_STAT_STAGED) {
4382 report("Cannot revert changes to staged files");
4383 } else if (type == LINE_STAT_UNTRACKED) {
4384 report("Cannot revert changes to untracked files");
4385 } else if (has_none) {
4386 report("Nothing to revert");
4387 } else {
4388 report("Cannot revert changes to multiple files");
4390 return FALSE;
4392 } else {
4393 char cmd[SIZEOF_STR];
4394 char file_sq[SIZEOF_STR];
4396 if (sq_quote(file_sq, 0, status->old.name) >= sizeof(file_sq) ||
4397 !string_format(cmd, "git checkout %s%s", opt_cdup, file_sq))
4398 return FALSE;
4400 return run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4404 static enum request
4405 status_request(struct view *view, enum request request, struct line *line)
4407 struct status *status = line->data;
4409 switch (request) {
4410 case REQ_STATUS_UPDATE:
4411 if (!status_update(view))
4412 return REQ_NONE;
4413 break;
4415 case REQ_STATUS_REVERT:
4416 if (!status_revert(status, line->type, status_has_none(view, line)))
4417 return REQ_NONE;
4418 break;
4420 case REQ_STATUS_MERGE:
4421 if (!status || status->status != 'U') {
4422 report("Merging only possible for files with unmerged status ('U').");
4423 return REQ_NONE;
4425 open_mergetool(status->new.name);
4426 break;
4428 case REQ_EDIT:
4429 if (!status)
4430 return request;
4432 open_editor(status->status != '?', status->new.name);
4433 break;
4435 case REQ_VIEW_BLAME:
4436 if (status) {
4437 string_copy(opt_file, status->new.name);
4438 opt_ref[0] = 0;
4440 return request;
4442 case REQ_ENTER:
4443 /* After returning the status view has been split to
4444 * show the stage view. No further reloading is
4445 * necessary. */
4446 status_enter(view, line);
4447 return REQ_NONE;
4449 case REQ_REFRESH:
4450 /* Simply reload the view. */
4451 break;
4453 default:
4454 return request;
4457 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4459 return REQ_NONE;
4462 static void
4463 status_select(struct view *view, struct line *line)
4465 struct status *status = line->data;
4466 char file[SIZEOF_STR] = "all files";
4467 const char *text;
4468 const char *key;
4470 if (status && !string_format(file, "'%s'", status->new.name))
4471 return;
4473 if (!status && line[1].type == LINE_STAT_NONE)
4474 line++;
4476 switch (line->type) {
4477 case LINE_STAT_STAGED:
4478 text = "Press %s to unstage %s for commit";
4479 break;
4481 case LINE_STAT_UNSTAGED:
4482 text = "Press %s to stage %s for commit";
4483 break;
4485 case LINE_STAT_UNTRACKED:
4486 text = "Press %s to stage %s for addition";
4487 break;
4489 case LINE_STAT_HEAD:
4490 case LINE_STAT_NONE:
4491 text = "Nothing to update";
4492 break;
4494 default:
4495 die("line type %d not handled in switch", line->type);
4498 if (status && status->status == 'U') {
4499 text = "Press %s to resolve conflict in %s";
4500 key = get_key(REQ_STATUS_MERGE);
4502 } else {
4503 key = get_key(REQ_STATUS_UPDATE);
4506 string_format(view->ref, text, key, file);
4509 static bool
4510 status_grep(struct view *view, struct line *line)
4512 struct status *status = line->data;
4513 enum { S_STATUS, S_NAME, S_END } state;
4514 char buf[2] = "?";
4515 regmatch_t pmatch;
4517 if (!status)
4518 return FALSE;
4520 for (state = S_STATUS; state < S_END; state++) {
4521 const char *text;
4523 switch (state) {
4524 case S_NAME: text = status->new.name; break;
4525 case S_STATUS:
4526 buf[0] = status->status;
4527 text = buf;
4528 break;
4530 default:
4531 return FALSE;
4534 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4535 return TRUE;
4538 return FALSE;
4541 static struct view_ops status_ops = {
4542 "file",
4543 status_open,
4544 NULL,
4545 status_draw,
4546 status_request,
4547 status_grep,
4548 status_select,
4552 static bool
4553 stage_diff_line(FILE *pipe, struct line *line)
4555 const char *buf = line->data;
4556 size_t bufsize = strlen(buf);
4557 size_t written = 0;
4559 while (!ferror(pipe) && written < bufsize) {
4560 written += fwrite(buf + written, 1, bufsize - written, pipe);
4563 fputc('\n', pipe);
4565 return written == bufsize;
4568 static bool
4569 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4571 while (line < end) {
4572 if (!stage_diff_line(pipe, line++))
4573 return FALSE;
4574 if (line->type == LINE_DIFF_CHUNK ||
4575 line->type == LINE_DIFF_HEADER)
4576 break;
4579 return TRUE;
4582 static struct line *
4583 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4585 for (; view->line < line; line--)
4586 if (line->type == type)
4587 return line;
4589 return NULL;
4592 static bool
4593 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4595 char cmd[SIZEOF_STR];
4596 size_t cmdsize = 0;
4597 struct line *diff_hdr;
4598 FILE *pipe;
4600 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4601 if (!diff_hdr)
4602 return FALSE;
4604 if (opt_cdup[0] &&
4605 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4606 return FALSE;
4608 if (!string_format_from(cmd, &cmdsize,
4609 "git apply --whitespace=nowarn %s %s - && "
4610 "git update-index -q --unmerged --refresh 2>/dev/null",
4611 revert ? "" : "--cached",
4612 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4613 return FALSE;
4615 pipe = popen(cmd, "w");
4616 if (!pipe)
4617 return FALSE;
4619 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4620 !stage_diff_write(pipe, chunk, view->line + view->lines))
4621 chunk = NULL;
4623 pclose(pipe);
4625 return chunk ? TRUE : FALSE;
4628 static bool
4629 stage_update(struct view *view, struct line *line)
4631 struct line *chunk = NULL;
4633 if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4634 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4636 if (chunk) {
4637 if (!stage_apply_chunk(view, chunk, FALSE)) {
4638 report("Failed to apply chunk");
4639 return FALSE;
4642 } else if (!stage_status.status) {
4643 view = VIEW(REQ_VIEW_STATUS);
4645 for (line = view->line; line < view->line + view->lines; line++)
4646 if (line->type == stage_line_type)
4647 break;
4649 if (!status_update_files(view, line + 1)) {
4650 report("Failed to update files");
4651 return FALSE;
4654 } else if (!status_update_file(&stage_status, stage_line_type)) {
4655 report("Failed to update file");
4656 return FALSE;
4659 return TRUE;
4662 static bool
4663 stage_revert(struct view *view, struct line *line)
4665 struct line *chunk = NULL;
4667 if (!opt_no_head && stage_line_type == LINE_STAT_UNSTAGED)
4668 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4670 if (chunk) {
4671 if (!prompt_yesno("Are you sure you want to revert changes?"))
4672 return FALSE;
4674 if (!stage_apply_chunk(view, chunk, TRUE)) {
4675 report("Failed to revert chunk");
4676 return FALSE;
4678 return TRUE;
4680 } else {
4681 return status_revert(stage_status.status ? &stage_status : NULL,
4682 stage_line_type, FALSE);
4687 static void
4688 stage_next(struct view *view, struct line *line)
4690 int i;
4692 if (!stage_chunks) {
4693 static size_t alloc = 0;
4694 int *tmp;
4696 for (line = view->line; line < view->line + view->lines; line++) {
4697 if (line->type != LINE_DIFF_CHUNK)
4698 continue;
4700 tmp = realloc_items(stage_chunk, &alloc,
4701 stage_chunks, sizeof(*tmp));
4702 if (!tmp) {
4703 report("Allocation failure");
4704 return;
4707 stage_chunk = tmp;
4708 stage_chunk[stage_chunks++] = line - view->line;
4712 for (i = 0; i < stage_chunks; i++) {
4713 if (stage_chunk[i] > view->lineno) {
4714 do_scroll_view(view, stage_chunk[i] - view->lineno);
4715 report("Chunk %d of %d", i + 1, stage_chunks);
4716 return;
4720 report("No next chunk found");
4723 static enum request
4724 stage_request(struct view *view, enum request request, struct line *line)
4726 switch (request) {
4727 case REQ_STATUS_UPDATE:
4728 if (!stage_update(view, line))
4729 return REQ_NONE;
4730 break;
4732 case REQ_STATUS_REVERT:
4733 if (!stage_revert(view, line))
4734 return REQ_NONE;
4735 break;
4737 case REQ_STAGE_NEXT:
4738 if (stage_line_type == LINE_STAT_UNTRACKED) {
4739 report("File is untracked; press %s to add",
4740 get_key(REQ_STATUS_UPDATE));
4741 return REQ_NONE;
4743 stage_next(view, line);
4744 return REQ_NONE;
4746 case REQ_EDIT:
4747 if (!stage_status.new.name[0])
4748 return request;
4750 open_editor(stage_status.status != '?', stage_status.new.name);
4751 break;
4753 case REQ_REFRESH:
4754 /* Reload everything ... */
4755 break;
4757 case REQ_VIEW_BLAME:
4758 if (stage_status.new.name[0]) {
4759 string_copy(opt_file, stage_status.new.name);
4760 opt_ref[0] = 0;
4762 return request;
4764 case REQ_ENTER:
4765 return pager_request(view, request, line);
4767 default:
4768 return request;
4771 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4773 /* Check whether the staged entry still exists, and close the
4774 * stage view if it doesn't. */
4775 if (!status_exists(&stage_status, stage_line_type))
4776 return REQ_VIEW_CLOSE;
4778 if (stage_line_type == LINE_STAT_UNTRACKED)
4779 opt_pipe = fopen(stage_status.new.name, "r");
4780 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
4782 return REQ_NONE;
4785 static struct view_ops stage_ops = {
4786 "line",
4787 NULL,
4788 pager_read,
4789 pager_draw,
4790 stage_request,
4791 pager_grep,
4792 pager_select,
4797 * Revision graph
4800 struct commit {
4801 char id[SIZEOF_REV]; /* SHA1 ID. */
4802 char title[128]; /* First line of the commit message. */
4803 char author[75]; /* Author of the commit. */
4804 struct tm time; /* Date from the author ident. */
4805 struct ref **refs; /* Repository references. */
4806 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
4807 size_t graph_size; /* The width of the graph array. */
4808 bool has_parents; /* Rewritten --parents seen. */
4811 /* Size of rev graph with no "padding" columns */
4812 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4814 struct rev_graph {
4815 struct rev_graph *prev, *next, *parents;
4816 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4817 size_t size;
4818 struct commit *commit;
4819 size_t pos;
4820 unsigned int boundary:1;
4823 /* Parents of the commit being visualized. */
4824 static struct rev_graph graph_parents[4];
4826 /* The current stack of revisions on the graph. */
4827 static struct rev_graph graph_stacks[4] = {
4828 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4829 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4830 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4831 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4834 static inline bool
4835 graph_parent_is_merge(struct rev_graph *graph)
4837 return graph->parents->size > 1;
4840 static inline void
4841 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4843 struct commit *commit = graph->commit;
4845 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4846 commit->graph[commit->graph_size++] = symbol;
4849 static void
4850 clear_rev_graph(struct rev_graph *graph)
4852 graph->boundary = 0;
4853 graph->size = graph->pos = 0;
4854 graph->commit = NULL;
4855 memset(graph->parents, 0, sizeof(*graph->parents));
4858 static void
4859 done_rev_graph(struct rev_graph *graph)
4861 if (graph_parent_is_merge(graph) &&
4862 graph->pos < graph->size - 1 &&
4863 graph->next->size == graph->size + graph->parents->size - 1) {
4864 size_t i = graph->pos + graph->parents->size - 1;
4866 graph->commit->graph_size = i * 2;
4867 while (i < graph->next->size - 1) {
4868 append_to_rev_graph(graph, ' ');
4869 append_to_rev_graph(graph, '\\');
4870 i++;
4874 clear_rev_graph(graph);
4877 static void
4878 push_rev_graph(struct rev_graph *graph, const char *parent)
4880 int i;
4882 /* "Collapse" duplicate parents lines.
4884 * FIXME: This needs to also update update the drawn graph but
4885 * for now it just serves as a method for pruning graph lines. */
4886 for (i = 0; i < graph->size; i++)
4887 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4888 return;
4890 if (graph->size < SIZEOF_REVITEMS) {
4891 string_copy_rev(graph->rev[graph->size++], parent);
4895 static chtype
4896 get_rev_graph_symbol(struct rev_graph *graph)
4898 chtype symbol;
4900 if (graph->boundary)
4901 symbol = REVGRAPH_BOUND;
4902 else if (graph->parents->size == 0)
4903 symbol = REVGRAPH_INIT;
4904 else if (graph_parent_is_merge(graph))
4905 symbol = REVGRAPH_MERGE;
4906 else if (graph->pos >= graph->size)
4907 symbol = REVGRAPH_BRANCH;
4908 else
4909 symbol = REVGRAPH_COMMIT;
4911 return symbol;
4914 static void
4915 draw_rev_graph(struct rev_graph *graph)
4917 struct rev_filler {
4918 chtype separator, line;
4920 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4921 static struct rev_filler fillers[] = {
4922 { ' ', '|' },
4923 { '`', '.' },
4924 { '\'', ' ' },
4925 { '/', ' ' },
4927 chtype symbol = get_rev_graph_symbol(graph);
4928 struct rev_filler *filler;
4929 size_t i;
4931 if (opt_line_graphics)
4932 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4934 filler = &fillers[DEFAULT];
4936 for (i = 0; i < graph->pos; i++) {
4937 append_to_rev_graph(graph, filler->line);
4938 if (graph_parent_is_merge(graph->prev) &&
4939 graph->prev->pos == i)
4940 filler = &fillers[RSHARP];
4942 append_to_rev_graph(graph, filler->separator);
4945 /* Place the symbol for this revision. */
4946 append_to_rev_graph(graph, symbol);
4948 if (graph->prev->size > graph->size)
4949 filler = &fillers[RDIAG];
4950 else
4951 filler = &fillers[DEFAULT];
4953 i++;
4955 for (; i < graph->size; i++) {
4956 append_to_rev_graph(graph, filler->separator);
4957 append_to_rev_graph(graph, filler->line);
4958 if (graph_parent_is_merge(graph->prev) &&
4959 i < graph->prev->pos + graph->parents->size)
4960 filler = &fillers[RSHARP];
4961 if (graph->prev->size > graph->size)
4962 filler = &fillers[LDIAG];
4965 if (graph->prev->size > graph->size) {
4966 append_to_rev_graph(graph, filler->separator);
4967 if (filler->line != ' ')
4968 append_to_rev_graph(graph, filler->line);
4972 /* Prepare the next rev graph */
4973 static void
4974 prepare_rev_graph(struct rev_graph *graph)
4976 size_t i;
4978 /* First, traverse all lines of revisions up to the active one. */
4979 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4980 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4981 break;
4983 push_rev_graph(graph->next, graph->rev[graph->pos]);
4986 /* Interleave the new revision parent(s). */
4987 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4988 push_rev_graph(graph->next, graph->parents->rev[i]);
4990 /* Lastly, put any remaining revisions. */
4991 for (i = graph->pos + 1; i < graph->size; i++)
4992 push_rev_graph(graph->next, graph->rev[i]);
4995 static void
4996 update_rev_graph(struct rev_graph *graph)
4998 /* If this is the finalizing update ... */
4999 if (graph->commit)
5000 prepare_rev_graph(graph);
5002 /* Graph visualization needs a one rev look-ahead,
5003 * so the first update doesn't visualize anything. */
5004 if (!graph->prev->commit)
5005 return;
5007 draw_rev_graph(graph->prev);
5008 done_rev_graph(graph->prev->prev);
5013 * Main view backend
5016 static bool
5017 main_draw(struct view *view, struct line *line, unsigned int lineno)
5019 struct commit *commit = line->data;
5021 if (!*commit->author)
5022 return FALSE;
5024 if (opt_date && draw_date(view, &commit->time))
5025 return TRUE;
5027 if (opt_author &&
5028 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5029 return TRUE;
5031 if (opt_rev_graph && commit->graph_size &&
5032 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5033 return TRUE;
5035 if (opt_show_refs && commit->refs) {
5036 size_t i = 0;
5038 do {
5039 enum line_type type;
5041 if (commit->refs[i]->head)
5042 type = LINE_MAIN_HEAD;
5043 else if (commit->refs[i]->ltag)
5044 type = LINE_MAIN_LOCAL_TAG;
5045 else if (commit->refs[i]->tag)
5046 type = LINE_MAIN_TAG;
5047 else if (commit->refs[i]->tracked)
5048 type = LINE_MAIN_TRACKED;
5049 else if (commit->refs[i]->remote)
5050 type = LINE_MAIN_REMOTE;
5051 else
5052 type = LINE_MAIN_REF;
5054 if (draw_text(view, type, "[", TRUE) ||
5055 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5056 draw_text(view, type, "]", TRUE))
5057 return TRUE;
5059 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5060 return TRUE;
5061 } while (commit->refs[i++]->next);
5064 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5065 return TRUE;
5068 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5069 static bool
5070 main_read(struct view *view, char *line)
5072 static struct rev_graph *graph = graph_stacks;
5073 enum line_type type;
5074 struct commit *commit;
5076 if (!line) {
5077 int i;
5079 if (!view->lines && !view->parent)
5080 die("No revisions match the given arguments.");
5081 if (view->lines > 0) {
5082 commit = view->line[view->lines - 1].data;
5083 if (!*commit->author) {
5084 view->lines--;
5085 free(commit);
5086 graph->commit = NULL;
5089 update_rev_graph(graph);
5091 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5092 clear_rev_graph(&graph_stacks[i]);
5093 return TRUE;
5096 type = get_line_type(line);
5097 if (type == LINE_COMMIT) {
5098 commit = calloc(1, sizeof(struct commit));
5099 if (!commit)
5100 return FALSE;
5102 line += STRING_SIZE("commit ");
5103 if (*line == '-') {
5104 graph->boundary = 1;
5105 line++;
5108 string_copy_rev(commit->id, line);
5109 commit->refs = get_refs(commit->id);
5110 graph->commit = commit;
5111 add_line_data(view, commit, LINE_MAIN_COMMIT);
5113 while ((line = strchr(line, ' '))) {
5114 line++;
5115 push_rev_graph(graph->parents, line);
5116 commit->has_parents = TRUE;
5118 return TRUE;
5121 if (!view->lines)
5122 return TRUE;
5123 commit = view->line[view->lines - 1].data;
5125 switch (type) {
5126 case LINE_PARENT:
5127 if (commit->has_parents)
5128 break;
5129 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5130 break;
5132 case LINE_AUTHOR:
5134 /* Parse author lines where the name may be empty:
5135 * author <email@address.tld> 1138474660 +0100
5137 char *ident = line + STRING_SIZE("author ");
5138 char *nameend = strchr(ident, '<');
5139 char *emailend = strchr(ident, '>');
5141 if (!nameend || !emailend)
5142 break;
5144 update_rev_graph(graph);
5145 graph = graph->next;
5147 *nameend = *emailend = 0;
5148 ident = chomp_string(ident);
5149 if (!*ident) {
5150 ident = chomp_string(nameend + 1);
5151 if (!*ident)
5152 ident = "Unknown";
5155 string_ncopy(commit->author, ident, strlen(ident));
5157 /* Parse epoch and timezone */
5158 if (emailend[1] == ' ') {
5159 char *secs = emailend + 2;
5160 char *zone = strchr(secs, ' ');
5161 time_t time = (time_t) atol(secs);
5163 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5164 long tz;
5166 zone++;
5167 tz = ('0' - zone[1]) * 60 * 60 * 10;
5168 tz += ('0' - zone[2]) * 60 * 60;
5169 tz += ('0' - zone[3]) * 60;
5170 tz += ('0' - zone[4]) * 60;
5172 if (zone[0] == '-')
5173 tz = -tz;
5175 time -= tz;
5178 gmtime_r(&time, &commit->time);
5180 break;
5182 default:
5183 /* Fill in the commit title if it has not already been set. */
5184 if (commit->title[0])
5185 break;
5187 /* Require titles to start with a non-space character at the
5188 * offset used by git log. */
5189 if (strncmp(line, " ", 4))
5190 break;
5191 line += 4;
5192 /* Well, if the title starts with a whitespace character,
5193 * try to be forgiving. Otherwise we end up with no title. */
5194 while (isspace(*line))
5195 line++;
5196 if (*line == '\0')
5197 break;
5198 /* FIXME: More graceful handling of titles; append "..." to
5199 * shortened titles, etc. */
5201 string_ncopy(commit->title, line, strlen(line));
5204 return TRUE;
5207 static enum request
5208 main_request(struct view *view, enum request request, struct line *line)
5210 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5212 switch (request) {
5213 case REQ_ENTER:
5214 open_view(view, REQ_VIEW_DIFF, flags);
5215 break;
5216 case REQ_REFRESH:
5217 load_refs();
5218 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5219 break;
5220 default:
5221 return request;
5224 return REQ_NONE;
5227 static bool
5228 grep_refs(struct ref **refs, regex_t *regex)
5230 regmatch_t pmatch;
5231 size_t i = 0;
5233 if (!refs)
5234 return FALSE;
5235 do {
5236 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5237 return TRUE;
5238 } while (refs[i++]->next);
5240 return FALSE;
5243 static bool
5244 main_grep(struct view *view, struct line *line)
5246 struct commit *commit = line->data;
5247 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5248 char buf[DATE_COLS + 1];
5249 regmatch_t pmatch;
5251 for (state = S_TITLE; state < S_END; state++) {
5252 char *text;
5254 switch (state) {
5255 case S_TITLE: text = commit->title; break;
5256 case S_AUTHOR:
5257 if (!opt_author)
5258 continue;
5259 text = commit->author;
5260 break;
5261 case S_DATE:
5262 if (!opt_date)
5263 continue;
5264 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5265 continue;
5266 text = buf;
5267 break;
5268 case S_REFS:
5269 if (!opt_show_refs)
5270 continue;
5271 if (grep_refs(commit->refs, view->regex) == TRUE)
5272 return TRUE;
5273 continue;
5274 default:
5275 return FALSE;
5278 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5279 return TRUE;
5282 return FALSE;
5285 static void
5286 main_select(struct view *view, struct line *line)
5288 struct commit *commit = line->data;
5290 string_copy_rev(view->ref, commit->id);
5291 string_copy_rev(ref_commit, view->ref);
5294 static struct view_ops main_ops = {
5295 "commit",
5296 NULL,
5297 main_read,
5298 main_draw,
5299 main_request,
5300 main_grep,
5301 main_select,
5306 * Unicode / UTF-8 handling
5308 * NOTE: Much of the following code for dealing with unicode is derived from
5309 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5310 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5313 /* I've (over)annotated a lot of code snippets because I am not entirely
5314 * confident that the approach taken by this small UTF-8 interface is correct.
5315 * --jonas */
5317 static inline int
5318 unicode_width(unsigned long c)
5320 if (c >= 0x1100 &&
5321 (c <= 0x115f /* Hangul Jamo */
5322 || c == 0x2329
5323 || c == 0x232a
5324 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5325 /* CJK ... Yi */
5326 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5327 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5328 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5329 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5330 || (c >= 0xffe0 && c <= 0xffe6)
5331 || (c >= 0x20000 && c <= 0x2fffd)
5332 || (c >= 0x30000 && c <= 0x3fffd)))
5333 return 2;
5335 if (c == '\t')
5336 return opt_tab_size;
5338 return 1;
5341 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5342 * Illegal bytes are set one. */
5343 static const unsigned char utf8_bytes[256] = {
5344 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,
5345 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,
5346 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,
5347 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,
5348 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,
5349 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,
5350 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,
5351 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,
5354 /* Decode UTF-8 multi-byte representation into a unicode character. */
5355 static inline unsigned long
5356 utf8_to_unicode(const char *string, size_t length)
5358 unsigned long unicode;
5360 switch (length) {
5361 case 1:
5362 unicode = string[0];
5363 break;
5364 case 2:
5365 unicode = (string[0] & 0x1f) << 6;
5366 unicode += (string[1] & 0x3f);
5367 break;
5368 case 3:
5369 unicode = (string[0] & 0x0f) << 12;
5370 unicode += ((string[1] & 0x3f) << 6);
5371 unicode += (string[2] & 0x3f);
5372 break;
5373 case 4:
5374 unicode = (string[0] & 0x0f) << 18;
5375 unicode += ((string[1] & 0x3f) << 12);
5376 unicode += ((string[2] & 0x3f) << 6);
5377 unicode += (string[3] & 0x3f);
5378 break;
5379 case 5:
5380 unicode = (string[0] & 0x0f) << 24;
5381 unicode += ((string[1] & 0x3f) << 18);
5382 unicode += ((string[2] & 0x3f) << 12);
5383 unicode += ((string[3] & 0x3f) << 6);
5384 unicode += (string[4] & 0x3f);
5385 break;
5386 case 6:
5387 unicode = (string[0] & 0x01) << 30;
5388 unicode += ((string[1] & 0x3f) << 24);
5389 unicode += ((string[2] & 0x3f) << 18);
5390 unicode += ((string[3] & 0x3f) << 12);
5391 unicode += ((string[4] & 0x3f) << 6);
5392 unicode += (string[5] & 0x3f);
5393 break;
5394 default:
5395 die("Invalid unicode length");
5398 /* Invalid characters could return the special 0xfffd value but NUL
5399 * should be just as good. */
5400 return unicode > 0xffff ? 0 : unicode;
5403 /* Calculates how much of string can be shown within the given maximum width
5404 * and sets trimmed parameter to non-zero value if all of string could not be
5405 * shown. If the reserve flag is TRUE, it will reserve at least one
5406 * trailing character, which can be useful when drawing a delimiter.
5408 * Returns the number of bytes to output from string to satisfy max_width. */
5409 static size_t
5410 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5412 const char *start = string;
5413 const char *end = strchr(string, '\0');
5414 unsigned char last_bytes = 0;
5415 size_t last_ucwidth = 0;
5417 *width = 0;
5418 *trimmed = 0;
5420 while (string < end) {
5421 int c = *(unsigned char *) string;
5422 unsigned char bytes = utf8_bytes[c];
5423 size_t ucwidth;
5424 unsigned long unicode;
5426 if (string + bytes > end)
5427 break;
5429 /* Change representation to figure out whether
5430 * it is a single- or double-width character. */
5432 unicode = utf8_to_unicode(string, bytes);
5433 /* FIXME: Graceful handling of invalid unicode character. */
5434 if (!unicode)
5435 break;
5437 ucwidth = unicode_width(unicode);
5438 *width += ucwidth;
5439 if (*width > max_width) {
5440 *trimmed = 1;
5441 *width -= ucwidth;
5442 if (reserve && *width == max_width) {
5443 string -= last_bytes;
5444 *width -= last_ucwidth;
5446 break;
5449 string += bytes;
5450 last_bytes = bytes;
5451 last_ucwidth = ucwidth;
5454 return string - start;
5459 * Status management
5462 /* Whether or not the curses interface has been initialized. */
5463 static bool cursed = FALSE;
5465 /* The status window is used for polling keystrokes. */
5466 static WINDOW *status_win;
5468 static bool status_empty = TRUE;
5470 /* Update status and title window. */
5471 static void
5472 report(const char *msg, ...)
5474 struct view *view = display[current_view];
5476 if (input_mode)
5477 return;
5479 if (!view) {
5480 char buf[SIZEOF_STR];
5481 va_list args;
5483 va_start(args, msg);
5484 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5485 buf[sizeof(buf) - 1] = 0;
5486 buf[sizeof(buf) - 2] = '.';
5487 buf[sizeof(buf) - 3] = '.';
5488 buf[sizeof(buf) - 4] = '.';
5490 va_end(args);
5491 die("%s", buf);
5494 if (!status_empty || *msg) {
5495 va_list args;
5497 va_start(args, msg);
5499 wmove(status_win, 0, 0);
5500 if (*msg) {
5501 vwprintw(status_win, msg, args);
5502 status_empty = FALSE;
5503 } else {
5504 status_empty = TRUE;
5506 wclrtoeol(status_win);
5507 wrefresh(status_win);
5509 va_end(args);
5512 update_view_title(view);
5513 update_display_cursor(view);
5516 /* Controls when nodelay should be in effect when polling user input. */
5517 static void
5518 set_nonblocking_input(bool loading)
5520 static unsigned int loading_views;
5522 if ((loading == FALSE && loading_views-- == 1) ||
5523 (loading == TRUE && loading_views++ == 0))
5524 nodelay(status_win, loading);
5527 static void
5528 init_display(void)
5530 int x, y;
5532 /* Initialize the curses library */
5533 if (isatty(STDIN_FILENO)) {
5534 cursed = !!initscr();
5535 } else {
5536 /* Leave stdin and stdout alone when acting as a pager. */
5537 FILE *io = fopen("/dev/tty", "r+");
5539 if (!io)
5540 die("Failed to open /dev/tty");
5541 cursed = !!newterm(NULL, io, io);
5544 if (!cursed)
5545 die("Failed to initialize curses");
5547 nonl(); /* Tell curses not to do NL->CR/NL on output */
5548 cbreak(); /* Take input chars one at a time, no wait for \n */
5549 noecho(); /* Don't echo input */
5550 leaveok(stdscr, TRUE);
5552 if (has_colors())
5553 init_colors();
5555 getmaxyx(stdscr, y, x);
5556 status_win = newwin(1, 0, y - 1, 0);
5557 if (!status_win)
5558 die("Failed to create status window");
5560 /* Enable keyboard mapping */
5561 keypad(status_win, TRUE);
5562 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5564 TABSIZE = opt_tab_size;
5565 if (opt_line_graphics) {
5566 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5570 static bool
5571 prompt_yesno(const char *prompt)
5573 enum { WAIT, STOP, CANCEL } status = WAIT;
5574 bool answer = FALSE;
5576 while (status == WAIT) {
5577 struct view *view;
5578 int i, key;
5580 input_mode = TRUE;
5582 foreach_view (view, i)
5583 update_view(view);
5585 input_mode = FALSE;
5587 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5588 wclrtoeol(status_win);
5590 /* Refresh, accept single keystroke of input */
5591 key = wgetch(status_win);
5592 switch (key) {
5593 case ERR:
5594 break;
5596 case 'y':
5597 case 'Y':
5598 answer = TRUE;
5599 status = STOP;
5600 break;
5602 case KEY_ESC:
5603 case KEY_RETURN:
5604 case KEY_ENTER:
5605 case KEY_BACKSPACE:
5606 case 'n':
5607 case 'N':
5608 case '\n':
5609 default:
5610 answer = FALSE;
5611 status = CANCEL;
5615 /* Clear the status window */
5616 status_empty = FALSE;
5617 report("");
5619 return answer;
5622 static char *
5623 read_prompt(const char *prompt)
5625 enum { READING, STOP, CANCEL } status = READING;
5626 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5627 int pos = 0;
5629 while (status == READING) {
5630 struct view *view;
5631 int i, key;
5633 input_mode = TRUE;
5635 foreach_view (view, i)
5636 update_view(view);
5638 input_mode = FALSE;
5640 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5641 wclrtoeol(status_win);
5643 /* Refresh, accept single keystroke of input */
5644 key = wgetch(status_win);
5645 switch (key) {
5646 case KEY_RETURN:
5647 case KEY_ENTER:
5648 case '\n':
5649 status = pos ? STOP : CANCEL;
5650 break;
5652 case KEY_BACKSPACE:
5653 if (pos > 0)
5654 pos--;
5655 else
5656 status = CANCEL;
5657 break;
5659 case KEY_ESC:
5660 status = CANCEL;
5661 break;
5663 case ERR:
5664 break;
5666 default:
5667 if (pos >= sizeof(buf)) {
5668 report("Input string too long");
5669 return NULL;
5672 if (isprint(key))
5673 buf[pos++] = (char) key;
5677 /* Clear the status window */
5678 status_empty = FALSE;
5679 report("");
5681 if (status == CANCEL)
5682 return NULL;
5684 buf[pos++] = 0;
5686 return buf;
5690 * Repository references
5693 static struct ref *refs = NULL;
5694 static size_t refs_alloc = 0;
5695 static size_t refs_size = 0;
5697 /* Id <-> ref store */
5698 static struct ref ***id_refs = NULL;
5699 static size_t id_refs_alloc = 0;
5700 static size_t id_refs_size = 0;
5702 static int
5703 compare_refs(const void *ref1_, const void *ref2_)
5705 const struct ref *ref1 = *(const struct ref **)ref1_;
5706 const struct ref *ref2 = *(const struct ref **)ref2_;
5708 if (ref1->tag != ref2->tag)
5709 return ref2->tag - ref1->tag;
5710 if (ref1->ltag != ref2->ltag)
5711 return ref2->ltag - ref2->ltag;
5712 if (ref1->head != ref2->head)
5713 return ref2->head - ref1->head;
5714 if (ref1->tracked != ref2->tracked)
5715 return ref2->tracked - ref1->tracked;
5716 if (ref1->remote != ref2->remote)
5717 return ref2->remote - ref1->remote;
5718 return strcmp(ref1->name, ref2->name);
5721 static struct ref **
5722 get_refs(const char *id)
5724 struct ref ***tmp_id_refs;
5725 struct ref **ref_list = NULL;
5726 size_t ref_list_alloc = 0;
5727 size_t ref_list_size = 0;
5728 size_t i;
5730 for (i = 0; i < id_refs_size; i++)
5731 if (!strcmp(id, id_refs[i][0]->id))
5732 return id_refs[i];
5734 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5735 sizeof(*id_refs));
5736 if (!tmp_id_refs)
5737 return NULL;
5739 id_refs = tmp_id_refs;
5741 for (i = 0; i < refs_size; i++) {
5742 struct ref **tmp;
5744 if (strcmp(id, refs[i].id))
5745 continue;
5747 tmp = realloc_items(ref_list, &ref_list_alloc,
5748 ref_list_size + 1, sizeof(*ref_list));
5749 if (!tmp) {
5750 if (ref_list)
5751 free(ref_list);
5752 return NULL;
5755 ref_list = tmp;
5756 ref_list[ref_list_size] = &refs[i];
5757 /* XXX: The properties of the commit chains ensures that we can
5758 * safely modify the shared ref. The repo references will
5759 * always be similar for the same id. */
5760 ref_list[ref_list_size]->next = 1;
5762 ref_list_size++;
5765 if (ref_list) {
5766 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
5767 ref_list[ref_list_size - 1]->next = 0;
5768 id_refs[id_refs_size++] = ref_list;
5771 return ref_list;
5774 static int
5775 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5777 struct ref *ref;
5778 bool tag = FALSE;
5779 bool ltag = FALSE;
5780 bool remote = FALSE;
5781 bool tracked = FALSE;
5782 bool check_replace = FALSE;
5783 bool head = FALSE;
5785 if (!prefixcmp(name, "refs/tags/")) {
5786 if (!strcmp(name + namelen - 3, "^{}")) {
5787 namelen -= 3;
5788 name[namelen] = 0;
5789 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5790 check_replace = TRUE;
5791 } else {
5792 ltag = TRUE;
5795 tag = TRUE;
5796 namelen -= STRING_SIZE("refs/tags/");
5797 name += STRING_SIZE("refs/tags/");
5799 } else if (!prefixcmp(name, "refs/remotes/")) {
5800 remote = TRUE;
5801 namelen -= STRING_SIZE("refs/remotes/");
5802 name += STRING_SIZE("refs/remotes/");
5803 tracked = !strcmp(opt_remote, name);
5805 } else if (!prefixcmp(name, "refs/heads/")) {
5806 namelen -= STRING_SIZE("refs/heads/");
5807 name += STRING_SIZE("refs/heads/");
5808 head = !strncmp(opt_head, name, namelen);
5810 } else if (!strcmp(name, "HEAD")) {
5811 opt_no_head = FALSE;
5812 return OK;
5815 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5816 /* it's an annotated tag, replace the previous sha1 with the
5817 * resolved commit id; relies on the fact git-ls-remote lists
5818 * the commit id of an annotated tag right before the commit id
5819 * it points to. */
5820 refs[refs_size - 1].ltag = ltag;
5821 string_copy_rev(refs[refs_size - 1].id, id);
5823 return OK;
5825 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5826 if (!refs)
5827 return ERR;
5829 ref = &refs[refs_size++];
5830 ref->name = malloc(namelen + 1);
5831 if (!ref->name)
5832 return ERR;
5834 strncpy(ref->name, name, namelen);
5835 ref->name[namelen] = 0;
5836 ref->head = head;
5837 ref->tag = tag;
5838 ref->ltag = ltag;
5839 ref->remote = remote;
5840 ref->tracked = tracked;
5841 string_copy_rev(ref->id, id);
5843 return OK;
5846 static int
5847 load_refs(void)
5849 const char *cmd_env = getenv("TIG_LS_REMOTE");
5850 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5852 if (!*opt_git_dir)
5853 return OK;
5855 while (refs_size > 0)
5856 free(refs[--refs_size].name);
5857 while (id_refs_size > 0)
5858 free(id_refs[--id_refs_size]);
5860 return read_properties(popen(cmd, "r"), "\t", read_ref);
5863 static int
5864 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5866 if (!strcmp(name, "i18n.commitencoding"))
5867 string_ncopy(opt_encoding, value, valuelen);
5869 if (!strcmp(name, "core.editor"))
5870 string_ncopy(opt_editor, value, valuelen);
5872 /* branch.<head>.remote */
5873 if (*opt_head &&
5874 !strncmp(name, "branch.", 7) &&
5875 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5876 !strcmp(name + 7 + strlen(opt_head), ".remote"))
5877 string_ncopy(opt_remote, value, valuelen);
5879 if (*opt_head && *opt_remote &&
5880 !strncmp(name, "branch.", 7) &&
5881 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5882 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5883 size_t from = strlen(opt_remote);
5885 if (!prefixcmp(value, "refs/heads/")) {
5886 value += STRING_SIZE("refs/heads/");
5887 valuelen -= STRING_SIZE("refs/heads/");
5890 if (!string_format_from(opt_remote, &from, "/%s", value))
5891 opt_remote[0] = 0;
5894 return OK;
5897 static int
5898 load_git_config(void)
5900 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
5901 "=", read_repo_config_option);
5904 static int
5905 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5907 if (!opt_git_dir[0]) {
5908 string_ncopy(opt_git_dir, name, namelen);
5910 } else if (opt_is_inside_work_tree == -1) {
5911 /* This can be 3 different values depending on the
5912 * version of git being used. If git-rev-parse does not
5913 * understand --is-inside-work-tree it will simply echo
5914 * the option else either "true" or "false" is printed.
5915 * Default to true for the unknown case. */
5916 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5918 } else if (opt_cdup[0] == ' ') {
5919 string_ncopy(opt_cdup, name, namelen);
5920 } else {
5921 if (!prefixcmp(name, "refs/heads/")) {
5922 namelen -= STRING_SIZE("refs/heads/");
5923 name += STRING_SIZE("refs/heads/");
5924 string_ncopy(opt_head, name, namelen);
5928 return OK;
5931 static int
5932 load_repo_info(void)
5934 int result;
5935 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5936 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5938 /* XXX: The line outputted by "--show-cdup" can be empty so
5939 * initialize it to something invalid to make it possible to
5940 * detect whether it has been set or not. */
5941 opt_cdup[0] = ' ';
5943 result = read_properties(pipe, "=", read_repo_info);
5944 if (opt_cdup[0] == ' ')
5945 opt_cdup[0] = 0;
5947 return result;
5950 static int
5951 read_properties(FILE *pipe, const char *separators,
5952 int (*read_property)(char *, size_t, char *, size_t))
5954 char buffer[BUFSIZ];
5955 char *name;
5956 int state = OK;
5958 if (!pipe)
5959 return ERR;
5961 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5962 char *value;
5963 size_t namelen;
5964 size_t valuelen;
5966 name = chomp_string(name);
5967 namelen = strcspn(name, separators);
5969 if (name[namelen]) {
5970 name[namelen] = 0;
5971 value = chomp_string(name + namelen + 1);
5972 valuelen = strlen(value);
5974 } else {
5975 value = "";
5976 valuelen = 0;
5979 state = read_property(name, namelen, value, valuelen);
5982 if (state != ERR && ferror(pipe))
5983 state = ERR;
5985 pclose(pipe);
5987 return state;
5992 * Main
5995 static void __NORETURN
5996 quit(int sig)
5998 /* XXX: Restore tty modes and let the OS cleanup the rest! */
5999 if (cursed)
6000 endwin();
6001 exit(0);
6004 static void __NORETURN
6005 die(const char *err, ...)
6007 va_list args;
6009 endwin();
6011 va_start(args, err);
6012 fputs("tig: ", stderr);
6013 vfprintf(stderr, err, args);
6014 fputs("\n", stderr);
6015 va_end(args);
6017 exit(1);
6020 static void
6021 warn(const char *msg, ...)
6023 va_list args;
6025 va_start(args, msg);
6026 fputs("tig warning: ", stderr);
6027 vfprintf(stderr, msg, args);
6028 fputs("\n", stderr);
6029 va_end(args);
6033 main(int argc, const char *argv[])
6035 struct view *view;
6036 enum request request;
6037 size_t i;
6039 signal(SIGINT, quit);
6041 if (setlocale(LC_ALL, "")) {
6042 char *codeset = nl_langinfo(CODESET);
6044 string_ncopy(opt_codeset, codeset, strlen(codeset));
6047 if (load_repo_info() == ERR)
6048 die("Failed to load repo info.");
6050 if (load_options() == ERR)
6051 die("Failed to load user config.");
6053 if (load_git_config() == ERR)
6054 die("Failed to load repo config.");
6056 request = parse_options(argc, argv);
6057 if (request == REQ_NONE)
6058 return 0;
6060 /* Require a git repository unless when running in pager mode. */
6061 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6062 die("Not a git repository");
6064 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6065 opt_utf8 = FALSE;
6067 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6068 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6069 if (opt_iconv == ICONV_NONE)
6070 die("Failed to initialize character set conversion");
6073 if (load_refs() == ERR)
6074 die("Failed to load refs.");
6076 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
6077 view->cmd_env = getenv(view->cmd_env);
6079 init_display();
6081 while (view_driver(display[current_view], request)) {
6082 int key;
6083 int i;
6085 foreach_view (view, i)
6086 update_view(view);
6088 /* Refresh, accept single keystroke of input */
6089 key = wgetch(status_win);
6091 /* wgetch() with nodelay() enabled returns ERR when there's no
6092 * input. */
6093 if (key == ERR) {
6094 request = REQ_NONE;
6095 continue;
6098 request = get_keybinding(display[current_view]->keymap, key);
6100 /* Some low-level request handling. This keeps access to
6101 * status_win restricted. */
6102 switch (request) {
6103 case REQ_PROMPT:
6105 char *cmd = read_prompt(":");
6107 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6108 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6109 request = REQ_VIEW_DIFF;
6110 } else {
6111 request = REQ_VIEW_PAGER;
6114 /* Always reload^Wrerun commands from the prompt. */
6115 open_view(view, request, OPEN_RELOAD);
6118 request = REQ_NONE;
6119 break;
6121 case REQ_SEARCH:
6122 case REQ_SEARCH_BACK:
6124 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6125 char *search = read_prompt(prompt);
6127 if (search)
6128 string_ncopy(opt_search, search, strlen(search));
6129 else
6130 request = REQ_NONE;
6131 break;
6133 case REQ_SCREEN_RESIZE:
6135 int height, width;
6137 getmaxyx(stdscr, height, width);
6139 /* Resize the status view and let the view driver take
6140 * care of resizing the displayed views. */
6141 wresize(status_win, 1, width);
6142 mvwin(status_win, height - 1, 0);
6143 wrefresh(status_win);
6144 break;
6146 default:
6147 break;
6151 quit(0);
6153 return 0;