bcbc5fa027907a78e87946c558442595774917f2
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* 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? */
165 };
167 static struct ref **get_refs(const char *id);
169 struct int_map {
170 const char *name;
171 int namelen;
172 int value;
173 };
175 static int
176 set_from_int_map(struct int_map *map, size_t map_size,
177 int *value, const char *name, int namelen)
178 {
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;
187 }
189 return ERR;
190 }
193 /*
194 * String helpers
195 */
197 static inline void
198 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199 {
200 if (srclen > dstlen - 1)
201 srclen = dstlen - 1;
203 strncpy(dst, src, srclen);
204 dst[srclen] = 0;
205 }
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)
223 {
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;
234 }
236 static bool
237 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
238 {
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;
250 }
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)
260 {
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];
275 }
277 return 0;
278 }
280 #define prefixcmp(str1, str2) \
281 strncmp(str1, str2, STRING_SIZE(str2))
283 static inline int
284 suffixcmp(const char *str, int slen, const char *suffix)
285 {
286 size_t len = slen >= 0 ? slen : strlen(str);
287 size_t suffixlen = strlen(suffix);
289 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
290 }
292 /* Shell quoting
293 *
294 * NOTE: The following is a slightly modified copy of the git project's shell
295 * quoting routines found in the quote.c file.
296 *
297 * Help to copy the thing properly quoted for the shell safety. any single
298 * quote is replaced with '\'', any exclamation point is replaced with '\!',
299 * and the whole thing is enclosed in a
300 *
301 * E.g.
302 * original sq_quote result
303 * name ==> name ==> 'name'
304 * a b ==> a b ==> 'a b'
305 * a'b ==> a'\''b ==> 'a'\''b'
306 * a!b ==> a'\!'b ==> 'a'\!'b'
307 */
309 static size_t
310 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
311 {
312 char c;
314 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
316 BUFPUT('\'');
317 while ((c = *src++)) {
318 if (c == '\'' || c == '!') {
319 BUFPUT('\'');
320 BUFPUT('\\');
321 BUFPUT(c);
322 BUFPUT('\'');
323 } else {
324 BUFPUT(c);
325 }
326 }
327 BUFPUT('\'');
329 if (bufsize < SIZEOF_STR)
330 buf[bufsize] = 0;
332 return bufsize;
333 }
336 /*
337 * User requests
338 */
340 #define REQ_INFO \
341 /* XXX: Keep the view request first and in sync with views[]. */ \
342 REQ_GROUP("View switching") \
343 REQ_(VIEW_MAIN, "Show main view"), \
344 REQ_(VIEW_DIFF, "Show diff view"), \
345 REQ_(VIEW_LOG, "Show log view"), \
346 REQ_(VIEW_TREE, "Show tree view"), \
347 REQ_(VIEW_BLOB, "Show blob view"), \
348 REQ_(VIEW_BLAME, "Show blame view"), \
349 REQ_(VIEW_HELP, "Show help page"), \
350 REQ_(VIEW_PAGER, "Show pager view"), \
351 REQ_(VIEW_STATUS, "Show status view"), \
352 REQ_(VIEW_STAGE, "Show stage view"), \
353 \
354 REQ_GROUP("View manipulation") \
355 REQ_(ENTER, "Enter current line and scroll"), \
356 REQ_(NEXT, "Move to next"), \
357 REQ_(PREVIOUS, "Move to previous"), \
358 REQ_(VIEW_NEXT, "Move focus to next view"), \
359 REQ_(REFRESH, "Reload and refresh"), \
360 REQ_(MAXIMIZE, "Maximize the current view"), \
361 REQ_(VIEW_CLOSE, "Close the current view"), \
362 REQ_(QUIT, "Close all views and quit"), \
363 \
364 REQ_GROUP("View specific requests") \
365 REQ_(STATUS_UPDATE, "Update file status"), \
366 REQ_(STATUS_REVERT, "Revert file changes"), \
367 REQ_(STATUS_MERGE, "Merge file using external tool"), \
368 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
369 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
370 \
371 REQ_GROUP("Cursor navigation") \
372 REQ_(MOVE_UP, "Move cursor one line up"), \
373 REQ_(MOVE_DOWN, "Move cursor one line down"), \
374 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
375 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
376 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
377 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
378 \
379 REQ_GROUP("Scrolling") \
380 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
381 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
382 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
383 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
384 \
385 REQ_GROUP("Searching") \
386 REQ_(SEARCH, "Search the view"), \
387 REQ_(SEARCH_BACK, "Search backwards in the view"), \
388 REQ_(FIND_NEXT, "Find next search match"), \
389 REQ_(FIND_PREV, "Find previous search match"), \
390 \
391 REQ_GROUP("Option manipulation") \
392 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
393 REQ_(TOGGLE_DATE, "Toggle date display"), \
394 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
395 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
396 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
397 \
398 REQ_GROUP("Misc") \
399 REQ_(PROMPT, "Bring up the prompt"), \
400 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
401 REQ_(SCREEN_RESIZE, "Resize the screen"), \
402 REQ_(SHOW_VERSION, "Show version information"), \
403 REQ_(STOP_LOADING, "Stop all loading views"), \
404 REQ_(EDIT, "Open in editor"), \
405 REQ_(NONE, "Do nothing")
408 /* User action requests. */
409 enum request {
410 #define REQ_GROUP(help)
411 #define REQ_(req, help) REQ_##req
413 /* Offset all requests to avoid conflicts with ncurses getch values. */
414 REQ_OFFSET = KEY_MAX + 1,
415 REQ_INFO
417 #undef REQ_GROUP
418 #undef REQ_
419 };
421 struct request_info {
422 enum request request;
423 const char *name;
424 int namelen;
425 const char *help;
426 };
428 static struct request_info req_info[] = {
429 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
430 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
431 REQ_INFO
432 #undef REQ_GROUP
433 #undef REQ_
434 };
436 static enum request
437 get_request(const char *name)
438 {
439 int namelen = strlen(name);
440 int i;
442 for (i = 0; i < ARRAY_SIZE(req_info); i++)
443 if (req_info[i].namelen == namelen &&
444 !string_enum_compare(req_info[i].name, name, namelen))
445 return req_info[i].request;
447 return REQ_NONE;
448 }
451 /*
452 * Options
453 */
455 static const char usage[] =
456 "tig " TIG_VERSION " (" __DATE__ ")\n"
457 "\n"
458 "Usage: tig [options] [revs] [--] [paths]\n"
459 " or: tig show [options] [revs] [--] [paths]\n"
460 " or: tig blame [rev] path\n"
461 " or: tig status\n"
462 " or: tig < [git command output]\n"
463 "\n"
464 "Options:\n"
465 " -v, --version Show version and exit\n"
466 " -h, --help Show help message and exit";
468 /* Option and state variables. */
469 static bool opt_date = TRUE;
470 static bool opt_author = TRUE;
471 static bool opt_line_number = FALSE;
472 static bool opt_line_graphics = TRUE;
473 static bool opt_rev_graph = FALSE;
474 static bool opt_show_refs = TRUE;
475 static int opt_num_interval = NUMBER_INTERVAL;
476 static int opt_tab_size = TAB_SIZE;
477 static int opt_author_cols = AUTHOR_COLS-1;
478 static char opt_cmd[SIZEOF_STR] = "";
479 static char opt_path[SIZEOF_STR] = "";
480 static char opt_file[SIZEOF_STR] = "";
481 static char opt_ref[SIZEOF_REF] = "";
482 static char opt_head[SIZEOF_REF] = "";
483 static char opt_head_rev[SIZEOF_REV] = "";
484 static char opt_remote[SIZEOF_REF] = "";
485 static FILE *opt_pipe = NULL;
486 static char opt_encoding[20] = "UTF-8";
487 static bool opt_utf8 = TRUE;
488 static char opt_codeset[20] = "UTF-8";
489 static iconv_t opt_iconv = ICONV_NONE;
490 static char opt_search[SIZEOF_STR] = "";
491 static char opt_cdup[SIZEOF_STR] = "";
492 static char opt_git_dir[SIZEOF_STR] = "";
493 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
494 static char opt_editor[SIZEOF_STR] = "";
495 static FILE *opt_tty = NULL;
497 #define is_initial_commit() (!*opt_head_rev)
498 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
500 static enum request
501 parse_options(int argc, const char *argv[])
502 {
503 enum request request = REQ_VIEW_MAIN;
504 size_t buf_size;
505 const char *subcommand;
506 bool seen_dashdash = FALSE;
507 int i;
509 if (!isatty(STDIN_FILENO)) {
510 opt_pipe = stdin;
511 return REQ_VIEW_PAGER;
512 }
514 if (argc <= 1)
515 return REQ_VIEW_MAIN;
517 subcommand = argv[1];
518 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
519 if (!strcmp(subcommand, "-S"))
520 warn("`-S' has been deprecated; use `tig status' instead");
521 if (argc > 2)
522 warn("ignoring arguments after `%s'", subcommand);
523 return REQ_VIEW_STATUS;
525 } else if (!strcmp(subcommand, "blame")) {
526 if (argc <= 2 || argc > 4)
527 die("invalid number of options to blame\n\n%s", usage);
529 i = 2;
530 if (argc == 4) {
531 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
532 i++;
533 }
535 string_ncopy(opt_file, argv[i], strlen(argv[i]));
536 return REQ_VIEW_BLAME;
538 } else if (!strcmp(subcommand, "show")) {
539 request = REQ_VIEW_DIFF;
541 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
542 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
543 warn("`tig %s' has been deprecated", subcommand);
545 } else {
546 subcommand = NULL;
547 }
549 if (!subcommand)
550 /* XXX: This is vulnerable to the user overriding
551 * options required for the main view parser. */
552 string_copy(opt_cmd, TIG_MAIN_BASE);
553 else
554 string_format(opt_cmd, "git %s", subcommand);
556 buf_size = strlen(opt_cmd);
558 for (i = 1 + !!subcommand; i < argc; i++) {
559 const char *opt = argv[i];
561 if (seen_dashdash || !strcmp(opt, "--")) {
562 seen_dashdash = TRUE;
564 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
565 printf("tig version %s\n", TIG_VERSION);
566 return REQ_NONE;
568 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
569 printf("%s\n", usage);
570 return REQ_NONE;
571 }
573 opt_cmd[buf_size++] = ' ';
574 buf_size = sq_quote(opt_cmd, buf_size, opt);
575 if (buf_size >= sizeof(opt_cmd))
576 die("command too long");
577 }
579 opt_cmd[buf_size] = 0;
581 return request;
582 }
585 /*
586 * Line-oriented content detection.
587 */
589 #define LINE_INFO \
590 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
591 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
592 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
593 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
594 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
595 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
596 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
597 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
598 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
599 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
600 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
601 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
602 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
603 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
604 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
605 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
606 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
607 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
608 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
609 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
610 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
611 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
612 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
613 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
614 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
615 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
616 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
617 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
618 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
619 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
620 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
621 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
622 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
623 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
624 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
625 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
626 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
627 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
628 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
629 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
630 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
631 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
632 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
633 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
634 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
635 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
636 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
637 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
638 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
639 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
640 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
641 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
642 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
643 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
645 enum line_type {
646 #define LINE(type, line, fg, bg, attr) \
647 LINE_##type
648 LINE_INFO,
649 LINE_NONE
650 #undef LINE
651 };
653 struct line_info {
654 const char *name; /* Option name. */
655 int namelen; /* Size of option name. */
656 const char *line; /* The start of line to match. */
657 int linelen; /* Size of string to match. */
658 int fg, bg, attr; /* Color and text attributes for the lines. */
659 };
661 static struct line_info line_info[] = {
662 #define LINE(type, line, fg, bg, attr) \
663 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
664 LINE_INFO
665 #undef LINE
666 };
668 static enum line_type
669 get_line_type(const char *line)
670 {
671 int linelen = strlen(line);
672 enum line_type type;
674 for (type = 0; type < ARRAY_SIZE(line_info); type++)
675 /* Case insensitive search matches Signed-off-by lines better. */
676 if (linelen >= line_info[type].linelen &&
677 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
678 return type;
680 return LINE_DEFAULT;
681 }
683 static inline int
684 get_line_attr(enum line_type type)
685 {
686 assert(type < ARRAY_SIZE(line_info));
687 return COLOR_PAIR(type) | line_info[type].attr;
688 }
690 static struct line_info *
691 get_line_info(const char *name)
692 {
693 size_t namelen = strlen(name);
694 enum line_type type;
696 for (type = 0; type < ARRAY_SIZE(line_info); type++)
697 if (namelen == line_info[type].namelen &&
698 !string_enum_compare(line_info[type].name, name, namelen))
699 return &line_info[type];
701 return NULL;
702 }
704 static void
705 init_colors(void)
706 {
707 int default_bg = line_info[LINE_DEFAULT].bg;
708 int default_fg = line_info[LINE_DEFAULT].fg;
709 enum line_type type;
711 start_color();
713 if (assume_default_colors(default_fg, default_bg) == ERR) {
714 default_bg = COLOR_BLACK;
715 default_fg = COLOR_WHITE;
716 }
718 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
719 struct line_info *info = &line_info[type];
720 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
721 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
723 init_pair(type, fg, bg);
724 }
725 }
727 struct line {
728 enum line_type type;
730 /* State flags */
731 unsigned int selected:1;
732 unsigned int dirty:1;
734 void *data; /* User data */
735 };
738 /*
739 * Keys
740 */
742 struct keybinding {
743 int alias;
744 enum request request;
745 struct keybinding *next;
746 };
748 static struct keybinding default_keybindings[] = {
749 /* View switching */
750 { 'm', REQ_VIEW_MAIN },
751 { 'd', REQ_VIEW_DIFF },
752 { 'l', REQ_VIEW_LOG },
753 { 't', REQ_VIEW_TREE },
754 { 'f', REQ_VIEW_BLOB },
755 { 'B', REQ_VIEW_BLAME },
756 { 'p', REQ_VIEW_PAGER },
757 { 'h', REQ_VIEW_HELP },
758 { 'S', REQ_VIEW_STATUS },
759 { 'c', REQ_VIEW_STAGE },
761 /* View manipulation */
762 { 'q', REQ_VIEW_CLOSE },
763 { KEY_TAB, REQ_VIEW_NEXT },
764 { KEY_RETURN, REQ_ENTER },
765 { KEY_UP, REQ_PREVIOUS },
766 { KEY_DOWN, REQ_NEXT },
767 { 'R', REQ_REFRESH },
768 { KEY_F(5), REQ_REFRESH },
769 { 'O', REQ_MAXIMIZE },
771 /* Cursor navigation */
772 { 'k', REQ_MOVE_UP },
773 { 'j', REQ_MOVE_DOWN },
774 { KEY_HOME, REQ_MOVE_FIRST_LINE },
775 { KEY_END, REQ_MOVE_LAST_LINE },
776 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
777 { ' ', REQ_MOVE_PAGE_DOWN },
778 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
779 { 'b', REQ_MOVE_PAGE_UP },
780 { '-', REQ_MOVE_PAGE_UP },
782 /* Scrolling */
783 { KEY_IC, REQ_SCROLL_LINE_UP },
784 { KEY_DC, REQ_SCROLL_LINE_DOWN },
785 { 'w', REQ_SCROLL_PAGE_UP },
786 { 's', REQ_SCROLL_PAGE_DOWN },
788 /* Searching */
789 { '/', REQ_SEARCH },
790 { '?', REQ_SEARCH_BACK },
791 { 'n', REQ_FIND_NEXT },
792 { 'N', REQ_FIND_PREV },
794 /* Misc */
795 { 'Q', REQ_QUIT },
796 { 'z', REQ_STOP_LOADING },
797 { 'v', REQ_SHOW_VERSION },
798 { 'r', REQ_SCREEN_REDRAW },
799 { '.', REQ_TOGGLE_LINENO },
800 { 'D', REQ_TOGGLE_DATE },
801 { 'A', REQ_TOGGLE_AUTHOR },
802 { 'g', REQ_TOGGLE_REV_GRAPH },
803 { 'F', REQ_TOGGLE_REFS },
804 { ':', REQ_PROMPT },
805 { 'u', REQ_STATUS_UPDATE },
806 { '!', REQ_STATUS_REVERT },
807 { 'M', REQ_STATUS_MERGE },
808 { '@', REQ_STAGE_NEXT },
809 { ',', REQ_TREE_PARENT },
810 { 'e', REQ_EDIT },
812 /* Using the ncurses SIGWINCH handler. */
813 { KEY_RESIZE, REQ_SCREEN_RESIZE },
814 };
816 #define KEYMAP_INFO \
817 KEYMAP_(GENERIC), \
818 KEYMAP_(MAIN), \
819 KEYMAP_(DIFF), \
820 KEYMAP_(LOG), \
821 KEYMAP_(TREE), \
822 KEYMAP_(BLOB), \
823 KEYMAP_(BLAME), \
824 KEYMAP_(PAGER), \
825 KEYMAP_(HELP), \
826 KEYMAP_(STATUS), \
827 KEYMAP_(STAGE)
829 enum keymap {
830 #define KEYMAP_(name) KEYMAP_##name
831 KEYMAP_INFO
832 #undef KEYMAP_
833 };
835 static struct int_map keymap_table[] = {
836 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
837 KEYMAP_INFO
838 #undef KEYMAP_
839 };
841 #define set_keymap(map, name) \
842 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
844 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
846 static void
847 add_keybinding(enum keymap keymap, enum request request, int key)
848 {
849 struct keybinding *keybinding;
851 keybinding = calloc(1, sizeof(*keybinding));
852 if (!keybinding)
853 die("Failed to allocate keybinding");
855 keybinding->alias = key;
856 keybinding->request = request;
857 keybinding->next = keybindings[keymap];
858 keybindings[keymap] = keybinding;
859 }
861 /* Looks for a key binding first in the given map, then in the generic map, and
862 * lastly in the default keybindings. */
863 static enum request
864 get_keybinding(enum keymap keymap, int key)
865 {
866 struct keybinding *kbd;
867 int i;
869 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
870 if (kbd->alias == key)
871 return kbd->request;
873 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
874 if (kbd->alias == key)
875 return kbd->request;
877 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
878 if (default_keybindings[i].alias == key)
879 return default_keybindings[i].request;
881 return (enum request) key;
882 }
885 struct key {
886 const char *name;
887 int value;
888 };
890 static struct key key_table[] = {
891 { "Enter", KEY_RETURN },
892 { "Space", ' ' },
893 { "Backspace", KEY_BACKSPACE },
894 { "Tab", KEY_TAB },
895 { "Escape", KEY_ESC },
896 { "Left", KEY_LEFT },
897 { "Right", KEY_RIGHT },
898 { "Up", KEY_UP },
899 { "Down", KEY_DOWN },
900 { "Insert", KEY_IC },
901 { "Delete", KEY_DC },
902 { "Hash", '#' },
903 { "Home", KEY_HOME },
904 { "End", KEY_END },
905 { "PageUp", KEY_PPAGE },
906 { "PageDown", KEY_NPAGE },
907 { "F1", KEY_F(1) },
908 { "F2", KEY_F(2) },
909 { "F3", KEY_F(3) },
910 { "F4", KEY_F(4) },
911 { "F5", KEY_F(5) },
912 { "F6", KEY_F(6) },
913 { "F7", KEY_F(7) },
914 { "F8", KEY_F(8) },
915 { "F9", KEY_F(9) },
916 { "F10", KEY_F(10) },
917 { "F11", KEY_F(11) },
918 { "F12", KEY_F(12) },
919 };
921 static int
922 get_key_value(const char *name)
923 {
924 int i;
926 for (i = 0; i < ARRAY_SIZE(key_table); i++)
927 if (!strcasecmp(key_table[i].name, name))
928 return key_table[i].value;
930 if (strlen(name) == 1 && isprint(*name))
931 return (int) *name;
933 return ERR;
934 }
936 static const char *
937 get_key_name(int key_value)
938 {
939 static char key_char[] = "'X'";
940 const char *seq = NULL;
941 int key;
943 for (key = 0; key < ARRAY_SIZE(key_table); key++)
944 if (key_table[key].value == key_value)
945 seq = key_table[key].name;
947 if (seq == NULL &&
948 key_value < 127 &&
949 isprint(key_value)) {
950 key_char[1] = (char) key_value;
951 seq = key_char;
952 }
954 return seq ? seq : "(no key)";
955 }
957 static const char *
958 get_key(enum request request)
959 {
960 static char buf[BUFSIZ];
961 size_t pos = 0;
962 char *sep = "";
963 int i;
965 buf[pos] = 0;
967 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
968 struct keybinding *keybinding = &default_keybindings[i];
970 if (keybinding->request != request)
971 continue;
973 if (!string_format_from(buf, &pos, "%s%s", sep,
974 get_key_name(keybinding->alias)))
975 return "Too many keybindings!";
976 sep = ", ";
977 }
979 return buf;
980 }
982 struct run_request {
983 enum keymap keymap;
984 int key;
985 char cmd[SIZEOF_STR];
986 };
988 static struct run_request *run_request;
989 static size_t run_requests;
991 static enum request
992 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
993 {
994 struct run_request *req;
995 char cmd[SIZEOF_STR];
996 size_t bufpos;
998 for (bufpos = 0; argc > 0; argc--, argv++)
999 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
1000 return REQ_NONE;
1002 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1003 if (!req)
1004 return REQ_NONE;
1006 run_request = req;
1007 req = &run_request[run_requests++];
1008 string_copy(req->cmd, cmd);
1009 req->keymap = keymap;
1010 req->key = key;
1012 return REQ_NONE + run_requests;
1013 }
1015 static struct run_request *
1016 get_run_request(enum request request)
1017 {
1018 if (request <= REQ_NONE)
1019 return NULL;
1020 return &run_request[request - REQ_NONE - 1];
1021 }
1023 static void
1024 add_builtin_run_requests(void)
1025 {
1026 struct {
1027 enum keymap keymap;
1028 int key;
1029 const char *argv[1];
1030 } reqs[] = {
1031 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
1032 { KEYMAP_GENERIC, 'G', { "git gc" } },
1033 };
1034 int i;
1036 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1037 enum request req;
1039 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1040 if (req != REQ_NONE)
1041 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1042 }
1043 }
1045 /*
1046 * User config file handling.
1047 */
1049 static struct int_map color_map[] = {
1050 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1051 COLOR_MAP(DEFAULT),
1052 COLOR_MAP(BLACK),
1053 COLOR_MAP(BLUE),
1054 COLOR_MAP(CYAN),
1055 COLOR_MAP(GREEN),
1056 COLOR_MAP(MAGENTA),
1057 COLOR_MAP(RED),
1058 COLOR_MAP(WHITE),
1059 COLOR_MAP(YELLOW),
1060 };
1062 #define set_color(color, name) \
1063 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1065 static struct int_map attr_map[] = {
1066 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1067 ATTR_MAP(NORMAL),
1068 ATTR_MAP(BLINK),
1069 ATTR_MAP(BOLD),
1070 ATTR_MAP(DIM),
1071 ATTR_MAP(REVERSE),
1072 ATTR_MAP(STANDOUT),
1073 ATTR_MAP(UNDERLINE),
1074 };
1076 #define set_attribute(attr, name) \
1077 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1079 static int config_lineno;
1080 static bool config_errors;
1081 static const char *config_msg;
1083 /* Wants: object fgcolor bgcolor [attr] */
1084 static int
1085 option_color_command(int argc, const char *argv[])
1086 {
1087 struct line_info *info;
1089 if (argc != 3 && argc != 4) {
1090 config_msg = "Wrong number of arguments given to color command";
1091 return ERR;
1092 }
1094 info = get_line_info(argv[0]);
1095 if (!info) {
1096 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1097 info = get_line_info("delimiter");
1099 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1100 info = get_line_info("date");
1102 } else {
1103 config_msg = "Unknown color name";
1104 return ERR;
1105 }
1106 }
1108 if (set_color(&info->fg, argv[1]) == ERR ||
1109 set_color(&info->bg, argv[2]) == ERR) {
1110 config_msg = "Unknown color";
1111 return ERR;
1112 }
1114 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1115 config_msg = "Unknown attribute";
1116 return ERR;
1117 }
1119 return OK;
1120 }
1122 static bool parse_bool(const char *s)
1123 {
1124 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1125 !strcmp(s, "yes")) ? TRUE : FALSE;
1126 }
1128 static int
1129 parse_int(const char *s, int default_value, int min, int max)
1130 {
1131 int value = atoi(s);
1133 return (value < min || value > max) ? default_value : value;
1134 }
1136 /* Wants: name = value */
1137 static int
1138 option_set_command(int argc, const char *argv[])
1139 {
1140 if (argc != 3) {
1141 config_msg = "Wrong number of arguments given to set command";
1142 return ERR;
1143 }
1145 if (strcmp(argv[1], "=")) {
1146 config_msg = "No value assigned";
1147 return ERR;
1148 }
1150 if (!strcmp(argv[0], "show-author")) {
1151 opt_author = parse_bool(argv[2]);
1152 return OK;
1153 }
1155 if (!strcmp(argv[0], "show-date")) {
1156 opt_date = parse_bool(argv[2]);
1157 return OK;
1158 }
1160 if (!strcmp(argv[0], "show-rev-graph")) {
1161 opt_rev_graph = parse_bool(argv[2]);
1162 return OK;
1163 }
1165 if (!strcmp(argv[0], "show-refs")) {
1166 opt_show_refs = parse_bool(argv[2]);
1167 return OK;
1168 }
1170 if (!strcmp(argv[0], "show-line-numbers")) {
1171 opt_line_number = parse_bool(argv[2]);
1172 return OK;
1173 }
1175 if (!strcmp(argv[0], "line-graphics")) {
1176 opt_line_graphics = parse_bool(argv[2]);
1177 return OK;
1178 }
1180 if (!strcmp(argv[0], "line-number-interval")) {
1181 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1182 return OK;
1183 }
1185 if (!strcmp(argv[0], "author-width")) {
1186 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1187 return OK;
1188 }
1190 if (!strcmp(argv[0], "tab-size")) {
1191 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1192 return OK;
1193 }
1195 if (!strcmp(argv[0], "commit-encoding")) {
1196 const char *arg = argv[2];
1197 int arglen = strlen(arg);
1199 switch (arg[0]) {
1200 case '"':
1201 case '\'':
1202 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1203 config_msg = "Unmatched quotation";
1204 return ERR;
1205 }
1206 arg += 1; arglen -= 2;
1207 default:
1208 string_ncopy(opt_encoding, arg, strlen(arg));
1209 return OK;
1210 }
1211 }
1213 config_msg = "Unknown variable name";
1214 return ERR;
1215 }
1217 /* Wants: mode request key */
1218 static int
1219 option_bind_command(int argc, const char *argv[])
1220 {
1221 enum request request;
1222 int keymap;
1223 int key;
1225 if (argc < 3) {
1226 config_msg = "Wrong number of arguments given to bind command";
1227 return ERR;
1228 }
1230 if (set_keymap(&keymap, argv[0]) == ERR) {
1231 config_msg = "Unknown key map";
1232 return ERR;
1233 }
1235 key = get_key_value(argv[1]);
1236 if (key == ERR) {
1237 config_msg = "Unknown key";
1238 return ERR;
1239 }
1241 request = get_request(argv[2]);
1242 if (request == REQ_NONE) {
1243 const char *obsolete[] = { "cherry-pick" };
1244 size_t namelen = strlen(argv[2]);
1245 int i;
1247 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1248 if (namelen == strlen(obsolete[i]) &&
1249 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1250 config_msg = "Obsolete request name";
1251 return ERR;
1252 }
1253 }
1254 }
1255 if (request == REQ_NONE && *argv[2]++ == '!')
1256 request = add_run_request(keymap, key, argc - 2, argv + 2);
1257 if (request == REQ_NONE) {
1258 config_msg = "Unknown request name";
1259 return ERR;
1260 }
1262 add_keybinding(keymap, request, key);
1264 return OK;
1265 }
1267 static int
1268 set_option(const char *opt, char *value)
1269 {
1270 const char *argv[SIZEOF_ARG];
1271 int valuelen;
1272 int argc = 0;
1274 /* Tokenize */
1275 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1276 argv[argc++] = value;
1277 value += valuelen;
1279 /* Nothing more to tokenize or last available token. */
1280 if (!*value || argc >= ARRAY_SIZE(argv))
1281 break;
1283 *value++ = 0;
1284 while (isspace(*value))
1285 value++;
1286 }
1288 if (!strcmp(opt, "color"))
1289 return option_color_command(argc, argv);
1291 if (!strcmp(opt, "set"))
1292 return option_set_command(argc, argv);
1294 if (!strcmp(opt, "bind"))
1295 return option_bind_command(argc, argv);
1297 config_msg = "Unknown option command";
1298 return ERR;
1299 }
1301 static int
1302 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1303 {
1304 int status = OK;
1306 config_lineno++;
1307 config_msg = "Internal error";
1309 /* Check for comment markers, since read_properties() will
1310 * only ensure opt and value are split at first " \t". */
1311 optlen = strcspn(opt, "#");
1312 if (optlen == 0)
1313 return OK;
1315 if (opt[optlen] != 0) {
1316 config_msg = "No option value";
1317 status = ERR;
1319 } else {
1320 /* Look for comment endings in the value. */
1321 size_t len = strcspn(value, "#");
1323 if (len < valuelen) {
1324 valuelen = len;
1325 value[valuelen] = 0;
1326 }
1328 status = set_option(opt, value);
1329 }
1331 if (status == ERR) {
1332 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1333 config_lineno, (int) optlen, opt, config_msg);
1334 config_errors = TRUE;
1335 }
1337 /* Always keep going if errors are encountered. */
1338 return OK;
1339 }
1341 static void
1342 load_option_file(const char *path)
1343 {
1344 FILE *file;
1346 /* It's ok that the file doesn't exist. */
1347 file = fopen(path, "r");
1348 if (!file)
1349 return;
1351 config_lineno = 0;
1352 config_errors = FALSE;
1354 if (read_properties(file, " \t", read_option) == ERR ||
1355 config_errors == TRUE)
1356 fprintf(stderr, "Errors while loading %s.\n", path);
1357 }
1359 static int
1360 load_options(void)
1361 {
1362 const char *home = getenv("HOME");
1363 const char *tigrc_user = getenv("TIGRC_USER");
1364 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1365 char buf[SIZEOF_STR];
1367 add_builtin_run_requests();
1369 if (!tigrc_system) {
1370 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1371 return ERR;
1372 tigrc_system = buf;
1373 }
1374 load_option_file(tigrc_system);
1376 if (!tigrc_user) {
1377 if (!home || !string_format(buf, "%s/.tigrc", home))
1378 return ERR;
1379 tigrc_user = buf;
1380 }
1381 load_option_file(tigrc_user);
1383 return OK;
1384 }
1387 /*
1388 * The viewer
1389 */
1391 struct view;
1392 struct view_ops;
1394 /* The display array of active views and the index of the current view. */
1395 static struct view *display[2];
1396 static unsigned int current_view;
1398 /* Reading from the prompt? */
1399 static bool input_mode = FALSE;
1401 #define foreach_displayed_view(view, i) \
1402 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1404 #define displayed_views() (display[1] != NULL ? 2 : 1)
1406 /* Current head and commit ID */
1407 static char ref_blob[SIZEOF_REF] = "";
1408 static char ref_commit[SIZEOF_REF] = "HEAD";
1409 static char ref_head[SIZEOF_REF] = "HEAD";
1411 struct view {
1412 const char *name; /* View name */
1413 const char *cmd_fmt; /* Default command line format */
1414 const char *cmd_env; /* Command line set via environment */
1415 const char *id; /* Points to either of ref_{head,commit,blob} */
1417 struct view_ops *ops; /* View operations */
1419 enum keymap keymap; /* What keymap does this view have */
1420 bool git_dir; /* Whether the view requires a git directory. */
1422 char cmd[SIZEOF_STR]; /* Command buffer */
1423 char ref[SIZEOF_REF]; /* Hovered commit reference */
1424 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1426 int height, width; /* The width and height of the main window */
1427 WINDOW *win; /* The main window */
1428 WINDOW *title; /* The title window living below the main window */
1430 /* Navigation */
1431 unsigned long offset; /* Offset of the window top */
1432 unsigned long lineno; /* Current line number */
1434 /* Searching */
1435 char grep[SIZEOF_STR]; /* Search string */
1436 regex_t *regex; /* Pre-compiled regex */
1438 /* If non-NULL, points to the view that opened this view. If this view
1439 * is closed tig will switch back to the parent view. */
1440 struct view *parent;
1442 /* Buffering */
1443 size_t lines; /* Total number of lines */
1444 struct line *line; /* Line index */
1445 size_t line_alloc; /* Total number of allocated lines */
1446 size_t line_size; /* Total number of used lines */
1447 unsigned int digits; /* Number of digits in the lines member. */
1449 /* Drawing */
1450 struct line *curline; /* Line currently being drawn. */
1451 enum line_type curtype; /* Attribute currently used for drawing. */
1452 unsigned long col; /* Column when drawing. */
1454 /* Loading */
1455 FILE *pipe;
1456 time_t start_time;
1457 };
1459 struct view_ops {
1460 /* What type of content being displayed. Used in the title bar. */
1461 const char *type;
1462 /* Open and reads in all view content. */
1463 bool (*open)(struct view *view);
1464 /* Read one line; updates view->line. */
1465 bool (*read)(struct view *view, char *data);
1466 /* Draw one line; @lineno must be < view->height. */
1467 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1468 /* Depending on view handle a special requests. */
1469 enum request (*request)(struct view *view, enum request request, struct line *line);
1470 /* Search for regex in a line. */
1471 bool (*grep)(struct view *view, struct line *line);
1472 /* Select line */
1473 void (*select)(struct view *view, struct line *line);
1474 };
1476 static struct view_ops blame_ops;
1477 static struct view_ops blob_ops;
1478 static struct view_ops help_ops;
1479 static struct view_ops log_ops;
1480 static struct view_ops main_ops;
1481 static struct view_ops pager_ops;
1482 static struct view_ops stage_ops;
1483 static struct view_ops status_ops;
1484 static struct view_ops tree_ops;
1486 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1487 { name, cmd, #env, ref, ops, map, git }
1489 #define VIEW_(id, name, ops, git, ref) \
1490 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1493 static struct view views[] = {
1494 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1495 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1496 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1497 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1498 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1499 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1500 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1501 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1502 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1503 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1504 };
1506 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1507 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1509 #define foreach_view(view, i) \
1510 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1512 #define view_is_displayed(view) \
1513 (view == display[0] || view == display[1])
1516 enum line_graphic {
1517 LINE_GRAPHIC_VLINE
1518 };
1520 static int line_graphics[] = {
1521 /* LINE_GRAPHIC_VLINE: */ '|'
1522 };
1524 static inline void
1525 set_view_attr(struct view *view, enum line_type type)
1526 {
1527 if (!view->curline->selected && view->curtype != type) {
1528 wattrset(view->win, get_line_attr(type));
1529 wchgat(view->win, -1, 0, type, NULL);
1530 view->curtype = type;
1531 }
1532 }
1534 static int
1535 draw_chars(struct view *view, enum line_type type, const char *string,
1536 int max_len, bool use_tilde)
1537 {
1538 int len = 0;
1539 int col = 0;
1540 int trimmed = FALSE;
1542 if (max_len <= 0)
1543 return 0;
1545 if (opt_utf8) {
1546 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1547 } else {
1548 col = len = strlen(string);
1549 if (len > max_len) {
1550 if (use_tilde) {
1551 max_len -= 1;
1552 }
1553 col = len = max_len;
1554 trimmed = TRUE;
1555 }
1556 }
1558 set_view_attr(view, type);
1559 waddnstr(view->win, string, len);
1560 if (trimmed && use_tilde) {
1561 set_view_attr(view, LINE_DELIMITER);
1562 waddch(view->win, '~');
1563 col++;
1564 }
1566 return col;
1567 }
1569 static int
1570 draw_space(struct view *view, enum line_type type, int max, int spaces)
1571 {
1572 static char space[] = " ";
1573 int col = 0;
1575 spaces = MIN(max, spaces);
1577 while (spaces > 0) {
1578 int len = MIN(spaces, sizeof(space) - 1);
1580 col += draw_chars(view, type, space, spaces, FALSE);
1581 spaces -= len;
1582 }
1584 return col;
1585 }
1587 static bool
1588 draw_lineno(struct view *view, unsigned int lineno)
1589 {
1590 char number[10];
1591 int digits3 = view->digits < 3 ? 3 : view->digits;
1592 int max_number = MIN(digits3, STRING_SIZE(number));
1593 int max = view->width - view->col;
1594 int col;
1596 if (max < max_number)
1597 max_number = max;
1599 lineno += view->offset + 1;
1600 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1601 static char fmt[] = "%1ld";
1603 if (view->digits <= 9)
1604 fmt[1] = '0' + digits3;
1606 if (!string_format(number, fmt, lineno))
1607 number[0] = 0;
1608 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1609 } else {
1610 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1611 }
1613 if (col < max) {
1614 set_view_attr(view, LINE_DEFAULT);
1615 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1616 col++;
1617 }
1619 if (col < max)
1620 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1621 view->col += col;
1623 return view->width - view->col <= 0;
1624 }
1626 static bool
1627 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1628 {
1629 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1630 return view->width - view->col <= 0;
1631 }
1633 static bool
1634 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1635 {
1636 int max = view->width - view->col;
1637 int i;
1639 if (max < size)
1640 size = max;
1642 set_view_attr(view, type);
1643 /* Using waddch() instead of waddnstr() ensures that
1644 * they'll be rendered correctly for the cursor line. */
1645 for (i = 0; i < size; i++)
1646 waddch(view->win, graphic[i]);
1648 view->col += size;
1649 if (size < max) {
1650 waddch(view->win, ' ');
1651 view->col++;
1652 }
1654 return view->width - view->col <= 0;
1655 }
1657 static bool
1658 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1659 {
1660 int max = MIN(view->width - view->col, len);
1661 int col;
1663 if (text)
1664 col = draw_chars(view, type, text, max - 1, trim);
1665 else
1666 col = draw_space(view, type, max - 1, max - 1);
1668 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1669 return view->width - view->col <= 0;
1670 }
1672 static bool
1673 draw_date(struct view *view, struct tm *time)
1674 {
1675 char buf[DATE_COLS];
1676 char *date;
1677 int timelen = 0;
1679 if (time)
1680 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1681 date = timelen ? buf : NULL;
1683 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1684 }
1686 static bool
1687 draw_view_line(struct view *view, unsigned int lineno)
1688 {
1689 struct line *line;
1690 bool selected = (view->offset + lineno == view->lineno);
1691 bool draw_ok;
1693 assert(view_is_displayed(view));
1695 if (view->offset + lineno >= view->lines)
1696 return FALSE;
1698 line = &view->line[view->offset + lineno];
1700 wmove(view->win, lineno, 0);
1701 view->col = 0;
1702 view->curline = line;
1703 view->curtype = LINE_NONE;
1704 line->selected = FALSE;
1706 if (selected) {
1707 set_view_attr(view, LINE_CURSOR);
1708 line->selected = TRUE;
1709 view->ops->select(view, line);
1710 } else if (line->selected) {
1711 wclrtoeol(view->win);
1712 }
1714 scrollok(view->win, FALSE);
1715 draw_ok = view->ops->draw(view, line, lineno);
1716 scrollok(view->win, TRUE);
1718 return draw_ok;
1719 }
1721 static void
1722 redraw_view_dirty(struct view *view)
1723 {
1724 bool dirty = FALSE;
1725 int lineno;
1727 for (lineno = 0; lineno < view->height; lineno++) {
1728 struct line *line = &view->line[view->offset + lineno];
1730 if (!line->dirty)
1731 continue;
1732 line->dirty = 0;
1733 dirty = TRUE;
1734 if (!draw_view_line(view, lineno))
1735 break;
1736 }
1738 if (!dirty)
1739 return;
1740 redrawwin(view->win);
1741 if (input_mode)
1742 wnoutrefresh(view->win);
1743 else
1744 wrefresh(view->win);
1745 }
1747 static void
1748 redraw_view_from(struct view *view, int lineno)
1749 {
1750 assert(0 <= lineno && lineno < view->height);
1752 for (; lineno < view->height; lineno++) {
1753 if (!draw_view_line(view, lineno))
1754 break;
1755 }
1757 redrawwin(view->win);
1758 if (input_mode)
1759 wnoutrefresh(view->win);
1760 else
1761 wrefresh(view->win);
1762 }
1764 static void
1765 redraw_view(struct view *view)
1766 {
1767 wclear(view->win);
1768 redraw_view_from(view, 0);
1769 }
1772 static void
1773 update_view_title(struct view *view)
1774 {
1775 char buf[SIZEOF_STR];
1776 char state[SIZEOF_STR];
1777 size_t bufpos = 0, statelen = 0;
1779 assert(view_is_displayed(view));
1781 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1782 unsigned int view_lines = view->offset + view->height;
1783 unsigned int lines = view->lines
1784 ? MIN(view_lines, view->lines) * 100 / view->lines
1785 : 0;
1787 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1788 view->ops->type,
1789 view->lineno + 1,
1790 view->lines,
1791 lines);
1793 if (view->pipe) {
1794 time_t secs = time(NULL) - view->start_time;
1796 /* Three git seconds are a long time ... */
1797 if (secs > 2)
1798 string_format_from(state, &statelen, " %lds", secs);
1799 }
1800 }
1802 string_format_from(buf, &bufpos, "[%s]", view->name);
1803 if (*view->ref && bufpos < view->width) {
1804 size_t refsize = strlen(view->ref);
1805 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1807 if (minsize < view->width)
1808 refsize = view->width - minsize + 7;
1809 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1810 }
1812 if (statelen && bufpos < view->width) {
1813 string_format_from(buf, &bufpos, " %s", state);
1814 }
1816 if (view == display[current_view])
1817 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1818 else
1819 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1821 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1822 wclrtoeol(view->title);
1823 wmove(view->title, 0, view->width - 1);
1825 if (input_mode)
1826 wnoutrefresh(view->title);
1827 else
1828 wrefresh(view->title);
1829 }
1831 static void
1832 resize_display(void)
1833 {
1834 int offset, i;
1835 struct view *base = display[0];
1836 struct view *view = display[1] ? display[1] : display[0];
1838 /* Setup window dimensions */
1840 getmaxyx(stdscr, base->height, base->width);
1842 /* Make room for the status window. */
1843 base->height -= 1;
1845 if (view != base) {
1846 /* Horizontal split. */
1847 view->width = base->width;
1848 view->height = SCALE_SPLIT_VIEW(base->height);
1849 base->height -= view->height;
1851 /* Make room for the title bar. */
1852 view->height -= 1;
1853 }
1855 /* Make room for the title bar. */
1856 base->height -= 1;
1858 offset = 0;
1860 foreach_displayed_view (view, i) {
1861 if (!view->win) {
1862 view->win = newwin(view->height, 0, offset, 0);
1863 if (!view->win)
1864 die("Failed to create %s view", view->name);
1866 scrollok(view->win, TRUE);
1868 view->title = newwin(1, 0, offset + view->height, 0);
1869 if (!view->title)
1870 die("Failed to create title window");
1872 } else {
1873 wresize(view->win, view->height, view->width);
1874 mvwin(view->win, offset, 0);
1875 mvwin(view->title, offset + view->height, 0);
1876 }
1878 offset += view->height + 1;
1879 }
1880 }
1882 static void
1883 redraw_display(void)
1884 {
1885 struct view *view;
1886 int i;
1888 foreach_displayed_view (view, i) {
1889 redraw_view(view);
1890 update_view_title(view);
1891 }
1892 }
1894 static void
1895 update_display_cursor(struct view *view)
1896 {
1897 /* Move the cursor to the right-most column of the cursor line.
1898 *
1899 * XXX: This could turn out to be a bit expensive, but it ensures that
1900 * the cursor does not jump around. */
1901 if (view->lines) {
1902 wmove(view->win, view->lineno - view->offset, view->width - 1);
1903 wrefresh(view->win);
1904 }
1905 }
1907 /*
1908 * Navigation
1909 */
1911 /* Scrolling backend */
1912 static void
1913 do_scroll_view(struct view *view, int lines)
1914 {
1915 bool redraw_current_line = FALSE;
1917 /* The rendering expects the new offset. */
1918 view->offset += lines;
1920 assert(0 <= view->offset && view->offset < view->lines);
1921 assert(lines);
1923 /* Move current line into the view. */
1924 if (view->lineno < view->offset) {
1925 view->lineno = view->offset;
1926 redraw_current_line = TRUE;
1927 } else if (view->lineno >= view->offset + view->height) {
1928 view->lineno = view->offset + view->height - 1;
1929 redraw_current_line = TRUE;
1930 }
1932 assert(view->offset <= view->lineno && view->lineno < view->lines);
1934 /* Redraw the whole screen if scrolling is pointless. */
1935 if (view->height < ABS(lines)) {
1936 redraw_view(view);
1938 } else {
1939 int line = lines > 0 ? view->height - lines : 0;
1940 int end = line + ABS(lines);
1942 wscrl(view->win, lines);
1944 for (; line < end; line++) {
1945 if (!draw_view_line(view, line))
1946 break;
1947 }
1949 if (redraw_current_line)
1950 draw_view_line(view, view->lineno - view->offset);
1951 }
1953 redrawwin(view->win);
1954 wrefresh(view->win);
1955 report("");
1956 }
1958 /* Scroll frontend */
1959 static void
1960 scroll_view(struct view *view, enum request request)
1961 {
1962 int lines = 1;
1964 assert(view_is_displayed(view));
1966 switch (request) {
1967 case REQ_SCROLL_PAGE_DOWN:
1968 lines = view->height;
1969 case REQ_SCROLL_LINE_DOWN:
1970 if (view->offset + lines > view->lines)
1971 lines = view->lines - view->offset;
1973 if (lines == 0 || view->offset + view->height >= view->lines) {
1974 report("Cannot scroll beyond the last line");
1975 return;
1976 }
1977 break;
1979 case REQ_SCROLL_PAGE_UP:
1980 lines = view->height;
1981 case REQ_SCROLL_LINE_UP:
1982 if (lines > view->offset)
1983 lines = view->offset;
1985 if (lines == 0) {
1986 report("Cannot scroll beyond the first line");
1987 return;
1988 }
1990 lines = -lines;
1991 break;
1993 default:
1994 die("request %d not handled in switch", request);
1995 }
1997 do_scroll_view(view, lines);
1998 }
2000 /* Cursor moving */
2001 static void
2002 move_view(struct view *view, enum request request)
2003 {
2004 int scroll_steps = 0;
2005 int steps;
2007 switch (request) {
2008 case REQ_MOVE_FIRST_LINE:
2009 steps = -view->lineno;
2010 break;
2012 case REQ_MOVE_LAST_LINE:
2013 steps = view->lines - view->lineno - 1;
2014 break;
2016 case REQ_MOVE_PAGE_UP:
2017 steps = view->height > view->lineno
2018 ? -view->lineno : -view->height;
2019 break;
2021 case REQ_MOVE_PAGE_DOWN:
2022 steps = view->lineno + view->height >= view->lines
2023 ? view->lines - view->lineno - 1 : view->height;
2024 break;
2026 case REQ_MOVE_UP:
2027 steps = -1;
2028 break;
2030 case REQ_MOVE_DOWN:
2031 steps = 1;
2032 break;
2034 default:
2035 die("request %d not handled in switch", request);
2036 }
2038 if (steps <= 0 && view->lineno == 0) {
2039 report("Cannot move beyond the first line");
2040 return;
2042 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2043 report("Cannot move beyond the last line");
2044 return;
2045 }
2047 /* Move the current line */
2048 view->lineno += steps;
2049 assert(0 <= view->lineno && view->lineno < view->lines);
2051 /* Check whether the view needs to be scrolled */
2052 if (view->lineno < view->offset ||
2053 view->lineno >= view->offset + view->height) {
2054 scroll_steps = steps;
2055 if (steps < 0 && -steps > view->offset) {
2056 scroll_steps = -view->offset;
2058 } else if (steps > 0) {
2059 if (view->lineno == view->lines - 1 &&
2060 view->lines > view->height) {
2061 scroll_steps = view->lines - view->offset - 1;
2062 if (scroll_steps >= view->height)
2063 scroll_steps -= view->height - 1;
2064 }
2065 }
2066 }
2068 if (!view_is_displayed(view)) {
2069 view->offset += scroll_steps;
2070 assert(0 <= view->offset && view->offset < view->lines);
2071 view->ops->select(view, &view->line[view->lineno]);
2072 return;
2073 }
2075 /* Repaint the old "current" line if we be scrolling */
2076 if (ABS(steps) < view->height)
2077 draw_view_line(view, view->lineno - steps - view->offset);
2079 if (scroll_steps) {
2080 do_scroll_view(view, scroll_steps);
2081 return;
2082 }
2084 /* Draw the current line */
2085 draw_view_line(view, view->lineno - view->offset);
2087 redrawwin(view->win);
2088 wrefresh(view->win);
2089 report("");
2090 }
2093 /*
2094 * Searching
2095 */
2097 static void search_view(struct view *view, enum request request);
2099 static bool
2100 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2101 {
2102 assert(view_is_displayed(view));
2104 if (!view->ops->grep(view, line))
2105 return FALSE;
2107 if (lineno - view->offset >= view->height) {
2108 view->offset = lineno;
2109 view->lineno = lineno;
2110 redraw_view(view);
2112 } else {
2113 unsigned long old_lineno = view->lineno - view->offset;
2115 view->lineno = lineno;
2116 draw_view_line(view, old_lineno);
2118 draw_view_line(view, view->lineno - view->offset);
2119 redrawwin(view->win);
2120 wrefresh(view->win);
2121 }
2123 report("Line %ld matches '%s'", lineno + 1, view->grep);
2124 return TRUE;
2125 }
2127 static void
2128 find_next(struct view *view, enum request request)
2129 {
2130 unsigned long lineno = view->lineno;
2131 int direction;
2133 if (!*view->grep) {
2134 if (!*opt_search)
2135 report("No previous search");
2136 else
2137 search_view(view, request);
2138 return;
2139 }
2141 switch (request) {
2142 case REQ_SEARCH:
2143 case REQ_FIND_NEXT:
2144 direction = 1;
2145 break;
2147 case REQ_SEARCH_BACK:
2148 case REQ_FIND_PREV:
2149 direction = -1;
2150 break;
2152 default:
2153 return;
2154 }
2156 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2157 lineno += direction;
2159 /* Note, lineno is unsigned long so will wrap around in which case it
2160 * will become bigger than view->lines. */
2161 for (; lineno < view->lines; lineno += direction) {
2162 struct line *line = &view->line[lineno];
2164 if (find_next_line(view, lineno, line))
2165 return;
2166 }
2168 report("No match found for '%s'", view->grep);
2169 }
2171 static void
2172 search_view(struct view *view, enum request request)
2173 {
2174 int regex_err;
2176 if (view->regex) {
2177 regfree(view->regex);
2178 *view->grep = 0;
2179 } else {
2180 view->regex = calloc(1, sizeof(*view->regex));
2181 if (!view->regex)
2182 return;
2183 }
2185 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2186 if (regex_err != 0) {
2187 char buf[SIZEOF_STR] = "unknown error";
2189 regerror(regex_err, view->regex, buf, sizeof(buf));
2190 report("Search failed: %s", buf);
2191 return;
2192 }
2194 string_copy(view->grep, opt_search);
2196 find_next(view, request);
2197 }
2199 /*
2200 * Incremental updating
2201 */
2203 static void
2204 reset_view(struct view *view)
2205 {
2206 int i;
2208 for (i = 0; i < view->lines; i++)
2209 free(view->line[i].data);
2210 free(view->line);
2212 view->line = NULL;
2213 view->offset = 0;
2214 view->lines = 0;
2215 view->lineno = 0;
2216 view->line_size = 0;
2217 view->line_alloc = 0;
2218 view->vid[0] = 0;
2219 }
2221 static void
2222 end_update(struct view *view, bool force)
2223 {
2224 if (!view->pipe)
2225 return;
2226 while (!view->ops->read(view, NULL))
2227 if (!force)
2228 return;
2229 set_nonblocking_input(FALSE);
2230 if (view->pipe == stdin)
2231 fclose(view->pipe);
2232 else
2233 pclose(view->pipe);
2234 view->pipe = NULL;
2235 }
2237 static bool
2238 begin_update(struct view *view, bool refresh)
2239 {
2240 if (opt_cmd[0]) {
2241 string_copy(view->cmd, opt_cmd);
2242 opt_cmd[0] = 0;
2243 /* When running random commands, initially show the
2244 * command in the title. However, it maybe later be
2245 * overwritten if a commit line is selected. */
2246 if (view == VIEW(REQ_VIEW_PAGER))
2247 string_copy(view->ref, view->cmd);
2248 else
2249 view->ref[0] = 0;
2251 } else if (view == VIEW(REQ_VIEW_TREE)) {
2252 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2253 char path[SIZEOF_STR];
2255 if (strcmp(view->vid, view->id))
2256 opt_path[0] = path[0] = 0;
2257 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2258 return FALSE;
2260 if (!string_format(view->cmd, format, view->id, path))
2261 return FALSE;
2263 } else if (!refresh) {
2264 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2265 const char *id = view->id;
2267 if (!string_format(view->cmd, format, id, id, id, id, id))
2268 return FALSE;
2270 /* Put the current ref_* value to the view title ref
2271 * member. This is needed by the blob view. Most other
2272 * views sets it automatically after loading because the
2273 * first line is a commit line. */
2274 string_copy_rev(view->ref, view->id);
2275 }
2277 /* Special case for the pager view. */
2278 if (opt_pipe) {
2279 view->pipe = opt_pipe;
2280 opt_pipe = NULL;
2281 } else {
2282 view->pipe = popen(view->cmd, "r");
2283 }
2285 if (!view->pipe)
2286 return FALSE;
2288 set_nonblocking_input(TRUE);
2289 reset_view(view);
2290 string_copy_rev(view->vid, view->id);
2292 view->start_time = time(NULL);
2294 return TRUE;
2295 }
2297 #define ITEM_CHUNK_SIZE 256
2298 static void *
2299 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2300 {
2301 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2302 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2304 if (mem == NULL || num_chunks != num_chunks_new) {
2305 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2306 mem = realloc(mem, *size * item_size);
2307 }
2309 return mem;
2310 }
2312 static struct line *
2313 realloc_lines(struct view *view, size_t line_size)
2314 {
2315 size_t alloc = view->line_alloc;
2316 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2317 sizeof(*view->line));
2319 if (!tmp)
2320 return NULL;
2322 view->line = tmp;
2323 view->line_alloc = alloc;
2324 view->line_size = line_size;
2325 return view->line;
2326 }
2328 static bool
2329 update_view(struct view *view)
2330 {
2331 char in_buffer[BUFSIZ];
2332 char out_buffer[BUFSIZ * 2];
2333 char *line;
2334 /* The number of lines to read. If too low it will cause too much
2335 * redrawing (and possible flickering), if too high responsiveness
2336 * will suffer. */
2337 unsigned long lines = view->height;
2338 int redraw_from = -1;
2340 if (!view->pipe)
2341 return TRUE;
2343 /* Only redraw if lines are visible. */
2344 if (view->offset + view->height >= view->lines)
2345 redraw_from = view->lines - view->offset;
2347 /* FIXME: This is probably not perfect for backgrounded views. */
2348 if (!realloc_lines(view, view->lines + lines))
2349 goto alloc_error;
2351 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2352 size_t linelen = strlen(line);
2354 if (linelen)
2355 line[linelen - 1] = 0;
2357 if (opt_iconv != ICONV_NONE) {
2358 ICONV_CONST char *inbuf = line;
2359 size_t inlen = linelen;
2361 char *outbuf = out_buffer;
2362 size_t outlen = sizeof(out_buffer);
2364 size_t ret;
2366 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2367 if (ret != (size_t) -1) {
2368 line = out_buffer;
2369 linelen = strlen(out_buffer);
2370 }
2371 }
2373 if (!view->ops->read(view, line))
2374 goto alloc_error;
2376 if (lines-- == 1)
2377 break;
2378 }
2380 {
2381 int digits;
2383 lines = view->lines;
2384 for (digits = 0; lines; digits++)
2385 lines /= 10;
2387 /* Keep the displayed view in sync with line number scaling. */
2388 if (digits != view->digits) {
2389 view->digits = digits;
2390 redraw_from = 0;
2391 }
2392 }
2394 if (ferror(view->pipe) && errno != 0) {
2395 report("Failed to read: %s", strerror(errno));
2396 end_update(view, TRUE);
2398 } else if (feof(view->pipe)) {
2399 report("");
2400 end_update(view, FALSE);
2401 }
2403 if (!view_is_displayed(view))
2404 return TRUE;
2406 if (view == VIEW(REQ_VIEW_TREE)) {
2407 /* Clear the view and redraw everything since the tree sorting
2408 * might have rearranged things. */
2409 redraw_view(view);
2411 } else if (redraw_from >= 0) {
2412 /* If this is an incremental update, redraw the previous line
2413 * since for commits some members could have changed when
2414 * loading the main view. */
2415 if (redraw_from > 0)
2416 redraw_from--;
2418 /* Since revision graph visualization requires knowledge
2419 * about the parent commit, it causes a further one-off
2420 * needed to be redrawn for incremental updates. */
2421 if (redraw_from > 0 && opt_rev_graph)
2422 redraw_from--;
2424 /* Incrementally draw avoids flickering. */
2425 redraw_view_from(view, redraw_from);
2426 }
2428 if (view == VIEW(REQ_VIEW_BLAME))
2429 redraw_view_dirty(view);
2431 /* Update the title _after_ the redraw so that if the redraw picks up a
2432 * commit reference in view->ref it'll be available here. */
2433 update_view_title(view);
2434 return TRUE;
2436 alloc_error:
2437 report("Allocation failure");
2438 end_update(view, TRUE);
2439 return FALSE;
2440 }
2442 static struct line *
2443 add_line_data(struct view *view, void *data, enum line_type type)
2444 {
2445 struct line *line = &view->line[view->lines++];
2447 memset(line, 0, sizeof(*line));
2448 line->type = type;
2449 line->data = data;
2451 return line;
2452 }
2454 static struct line *
2455 add_line_text(struct view *view, const char *text, enum line_type type)
2456 {
2457 char *data = text ? strdup(text) : NULL;
2459 return data ? add_line_data(view, data, type) : NULL;
2460 }
2463 /*
2464 * View opening
2465 */
2467 enum open_flags {
2468 OPEN_DEFAULT = 0, /* Use default view switching. */
2469 OPEN_SPLIT = 1, /* Split current view. */
2470 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2471 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2472 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2473 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2474 };
2476 static void
2477 open_view(struct view *prev, enum request request, enum open_flags flags)
2478 {
2479 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2480 bool split = !!(flags & OPEN_SPLIT);
2481 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2482 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2483 struct view *view = VIEW(request);
2484 int nviews = displayed_views();
2485 struct view *base_view = display[0];
2487 if (view == prev && nviews == 1 && !reload) {
2488 report("Already in %s view", view->name);
2489 return;
2490 }
2492 if (view->git_dir && !opt_git_dir[0]) {
2493 report("The %s view is disabled in pager view", view->name);
2494 return;
2495 }
2497 if (split) {
2498 display[1] = view;
2499 if (!backgrounded)
2500 current_view = 1;
2501 } else if (!nomaximize) {
2502 /* Maximize the current view. */
2503 memset(display, 0, sizeof(display));
2504 current_view = 0;
2505 display[current_view] = view;
2506 }
2508 /* Resize the view when switching between split- and full-screen,
2509 * or when switching between two different full-screen views. */
2510 if (nviews != displayed_views() ||
2511 (nviews == 1 && base_view != display[0]))
2512 resize_display();
2514 if (view->pipe)
2515 end_update(view, TRUE);
2517 if (view->ops->open) {
2518 if (!view->ops->open(view)) {
2519 report("Failed to load %s view", view->name);
2520 return;
2521 }
2523 } else if ((reload || strcmp(view->vid, view->id)) &&
2524 !begin_update(view, flags & OPEN_REFRESH)) {
2525 report("Failed to load %s view", view->name);
2526 return;
2527 }
2529 if (split && prev->lineno - prev->offset >= prev->height) {
2530 /* Take the title line into account. */
2531 int lines = prev->lineno - prev->offset - prev->height + 1;
2533 /* Scroll the view that was split if the current line is
2534 * outside the new limited view. */
2535 do_scroll_view(prev, lines);
2536 }
2538 if (prev && view != prev) {
2539 if (split && !backgrounded) {
2540 /* "Blur" the previous view. */
2541 update_view_title(prev);
2542 }
2544 view->parent = prev;
2545 }
2547 if (view->pipe && view->lines == 0) {
2548 /* Clear the old view and let the incremental updating refill
2549 * the screen. */
2550 werase(view->win);
2551 report("");
2552 } else if (view_is_displayed(view)) {
2553 redraw_view(view);
2554 report("");
2555 }
2557 /* If the view is backgrounded the above calls to report()
2558 * won't redraw the view title. */
2559 if (backgrounded)
2560 update_view_title(view);
2561 }
2563 static bool
2564 run_confirm(const char *cmd, const char *prompt)
2565 {
2566 bool confirmation = prompt_yesno(prompt);
2568 if (confirmation)
2569 system(cmd);
2571 return confirmation;
2572 }
2574 static void
2575 open_external_viewer(const char *cmd)
2576 {
2577 def_prog_mode(); /* save current tty modes */
2578 endwin(); /* restore original tty modes */
2579 system(cmd);
2580 fprintf(stderr, "Press Enter to continue");
2581 getc(opt_tty);
2582 reset_prog_mode();
2583 redraw_display();
2584 }
2586 static void
2587 open_mergetool(const char *file)
2588 {
2589 char cmd[SIZEOF_STR];
2590 char file_sq[SIZEOF_STR];
2592 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2593 string_format(cmd, "git mergetool %s", file_sq)) {
2594 open_external_viewer(cmd);
2595 }
2596 }
2598 static void
2599 open_editor(bool from_root, const char *file)
2600 {
2601 char cmd[SIZEOF_STR];
2602 char file_sq[SIZEOF_STR];
2603 const char *editor;
2604 char *prefix = from_root ? opt_cdup : "";
2606 editor = getenv("GIT_EDITOR");
2607 if (!editor && *opt_editor)
2608 editor = opt_editor;
2609 if (!editor)
2610 editor = getenv("VISUAL");
2611 if (!editor)
2612 editor = getenv("EDITOR");
2613 if (!editor)
2614 editor = "vi";
2616 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2617 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2618 open_external_viewer(cmd);
2619 }
2620 }
2622 static void
2623 open_run_request(enum request request)
2624 {
2625 struct run_request *req = get_run_request(request);
2626 char buf[SIZEOF_STR * 2];
2627 size_t bufpos;
2628 char *cmd;
2630 if (!req) {
2631 report("Unknown run request");
2632 return;
2633 }
2635 bufpos = 0;
2636 cmd = req->cmd;
2638 while (cmd) {
2639 char *next = strstr(cmd, "%(");
2640 int len = next - cmd;
2641 char *value;
2643 if (!next) {
2644 len = strlen(cmd);
2645 value = "";
2647 } else if (!strncmp(next, "%(head)", 7)) {
2648 value = ref_head;
2650 } else if (!strncmp(next, "%(commit)", 9)) {
2651 value = ref_commit;
2653 } else if (!strncmp(next, "%(blob)", 7)) {
2654 value = ref_blob;
2656 } else {
2657 report("Unknown replacement in run request: `%s`", req->cmd);
2658 return;
2659 }
2661 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2662 return;
2664 if (next)
2665 next = strchr(next, ')') + 1;
2666 cmd = next;
2667 }
2669 open_external_viewer(buf);
2670 }
2672 /*
2673 * User request switch noodle
2674 */
2676 static int
2677 view_driver(struct view *view, enum request request)
2678 {
2679 int i;
2681 if (request == REQ_NONE) {
2682 doupdate();
2683 return TRUE;
2684 }
2686 if (request > REQ_NONE) {
2687 open_run_request(request);
2688 /* FIXME: When all views can refresh always do this. */
2689 if (view == VIEW(REQ_VIEW_STATUS) ||
2690 view == VIEW(REQ_VIEW_MAIN) ||
2691 view == VIEW(REQ_VIEW_LOG) ||
2692 view == VIEW(REQ_VIEW_STAGE))
2693 request = REQ_REFRESH;
2694 else
2695 return TRUE;
2696 }
2698 if (view && view->lines) {
2699 request = view->ops->request(view, request, &view->line[view->lineno]);
2700 if (request == REQ_NONE)
2701 return TRUE;
2702 }
2704 switch (request) {
2705 case REQ_MOVE_UP:
2706 case REQ_MOVE_DOWN:
2707 case REQ_MOVE_PAGE_UP:
2708 case REQ_MOVE_PAGE_DOWN:
2709 case REQ_MOVE_FIRST_LINE:
2710 case REQ_MOVE_LAST_LINE:
2711 move_view(view, request);
2712 break;
2714 case REQ_SCROLL_LINE_DOWN:
2715 case REQ_SCROLL_LINE_UP:
2716 case REQ_SCROLL_PAGE_DOWN:
2717 case REQ_SCROLL_PAGE_UP:
2718 scroll_view(view, request);
2719 break;
2721 case REQ_VIEW_BLAME:
2722 if (!opt_file[0]) {
2723 report("No file chosen, press %s to open tree view",
2724 get_key(REQ_VIEW_TREE));
2725 break;
2726 }
2727 open_view(view, request, OPEN_DEFAULT);
2728 break;
2730 case REQ_VIEW_BLOB:
2731 if (!ref_blob[0]) {
2732 report("No file chosen, press %s to open tree view",
2733 get_key(REQ_VIEW_TREE));
2734 break;
2735 }
2736 open_view(view, request, OPEN_DEFAULT);
2737 break;
2739 case REQ_VIEW_PAGER:
2740 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2741 report("No pager content, press %s to run command from prompt",
2742 get_key(REQ_PROMPT));
2743 break;
2744 }
2745 open_view(view, request, OPEN_DEFAULT);
2746 break;
2748 case REQ_VIEW_STAGE:
2749 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2750 report("No stage content, press %s to open the status view and choose file",
2751 get_key(REQ_VIEW_STATUS));
2752 break;
2753 }
2754 open_view(view, request, OPEN_DEFAULT);
2755 break;
2757 case REQ_VIEW_STATUS:
2758 if (opt_is_inside_work_tree == FALSE) {
2759 report("The status view requires a working tree");
2760 break;
2761 }
2762 open_view(view, request, OPEN_DEFAULT);
2763 break;
2765 case REQ_VIEW_MAIN:
2766 case REQ_VIEW_DIFF:
2767 case REQ_VIEW_LOG:
2768 case REQ_VIEW_TREE:
2769 case REQ_VIEW_HELP:
2770 open_view(view, request, OPEN_DEFAULT);
2771 break;
2773 case REQ_NEXT:
2774 case REQ_PREVIOUS:
2775 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2777 if ((view == VIEW(REQ_VIEW_DIFF) &&
2778 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2779 (view == VIEW(REQ_VIEW_DIFF) &&
2780 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2781 (view == VIEW(REQ_VIEW_STAGE) &&
2782 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2783 (view == VIEW(REQ_VIEW_BLOB) &&
2784 view->parent == VIEW(REQ_VIEW_TREE))) {
2785 int line;
2787 view = view->parent;
2788 line = view->lineno;
2789 move_view(view, request);
2790 if (view_is_displayed(view))
2791 update_view_title(view);
2792 if (line != view->lineno)
2793 view->ops->request(view, REQ_ENTER,
2794 &view->line[view->lineno]);
2796 } else {
2797 move_view(view, request);
2798 }
2799 break;
2801 case REQ_VIEW_NEXT:
2802 {
2803 int nviews = displayed_views();
2804 int next_view = (current_view + 1) % nviews;
2806 if (next_view == current_view) {
2807 report("Only one view is displayed");
2808 break;
2809 }
2811 current_view = next_view;
2812 /* Blur out the title of the previous view. */
2813 update_view_title(view);
2814 report("");
2815 break;
2816 }
2817 case REQ_REFRESH:
2818 report("Refreshing is not yet supported for the %s view", view->name);
2819 break;
2821 case REQ_MAXIMIZE:
2822 if (displayed_views() == 2)
2823 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2824 break;
2826 case REQ_TOGGLE_LINENO:
2827 opt_line_number = !opt_line_number;
2828 redraw_display();
2829 break;
2831 case REQ_TOGGLE_DATE:
2832 opt_date = !opt_date;
2833 redraw_display();
2834 break;
2836 case REQ_TOGGLE_AUTHOR:
2837 opt_author = !opt_author;
2838 redraw_display();
2839 break;
2841 case REQ_TOGGLE_REV_GRAPH:
2842 opt_rev_graph = !opt_rev_graph;
2843 redraw_display();
2844 break;
2846 case REQ_TOGGLE_REFS:
2847 opt_show_refs = !opt_show_refs;
2848 redraw_display();
2849 break;
2851 case REQ_SEARCH:
2852 case REQ_SEARCH_BACK:
2853 search_view(view, request);
2854 break;
2856 case REQ_FIND_NEXT:
2857 case REQ_FIND_PREV:
2858 find_next(view, request);
2859 break;
2861 case REQ_STOP_LOADING:
2862 for (i = 0; i < ARRAY_SIZE(views); i++) {
2863 view = &views[i];
2864 if (view->pipe)
2865 report("Stopped loading the %s view", view->name),
2866 end_update(view, TRUE);
2867 }
2868 break;
2870 case REQ_SHOW_VERSION:
2871 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2872 return TRUE;
2874 case REQ_SCREEN_RESIZE:
2875 resize_display();
2876 /* Fall-through */
2877 case REQ_SCREEN_REDRAW:
2878 redraw_display();
2879 break;
2881 case REQ_EDIT:
2882 report("Nothing to edit");
2883 break;
2885 case REQ_ENTER:
2886 report("Nothing to enter");
2887 break;
2889 case REQ_VIEW_CLOSE:
2890 /* XXX: Mark closed views by letting view->parent point to the
2891 * view itself. Parents to closed view should never be
2892 * followed. */
2893 if (view->parent &&
2894 view->parent->parent != view->parent) {
2895 memset(display, 0, sizeof(display));
2896 current_view = 0;
2897 display[current_view] = view->parent;
2898 view->parent = view;
2899 resize_display();
2900 redraw_display();
2901 report("");
2902 break;
2903 }
2904 /* Fall-through */
2905 case REQ_QUIT:
2906 return FALSE;
2908 default:
2909 report("Unknown key, press 'h' for help");
2910 return TRUE;
2911 }
2913 return TRUE;
2914 }
2917 /*
2918 * Pager backend
2919 */
2921 static bool
2922 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2923 {
2924 char *text = line->data;
2926 if (opt_line_number && draw_lineno(view, lineno))
2927 return TRUE;
2929 draw_text(view, line->type, text, TRUE);
2930 return TRUE;
2931 }
2933 static bool
2934 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
2935 {
2936 char refbuf[SIZEOF_STR];
2937 char *ref = NULL;
2938 FILE *pipe;
2940 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2941 return TRUE;
2943 pipe = popen(refbuf, "r");
2944 if (!pipe)
2945 return TRUE;
2947 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2948 ref = chomp_string(ref);
2949 pclose(pipe);
2951 if (!ref || !*ref)
2952 return TRUE;
2954 /* This is the only fatal call, since it can "corrupt" the buffer. */
2955 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2956 return FALSE;
2958 return TRUE;
2959 }
2961 static void
2962 add_pager_refs(struct view *view, struct line *line)
2963 {
2964 char buf[SIZEOF_STR];
2965 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2966 struct ref **refs;
2967 size_t bufpos = 0, refpos = 0;
2968 const char *sep = "Refs: ";
2969 bool is_tag = FALSE;
2971 assert(line->type == LINE_COMMIT);
2973 refs = get_refs(commit_id);
2974 if (!refs) {
2975 if (view == VIEW(REQ_VIEW_DIFF))
2976 goto try_add_describe_ref;
2977 return;
2978 }
2980 do {
2981 struct ref *ref = refs[refpos];
2982 const char *fmt = ref->tag ? "%s[%s]" :
2983 ref->remote ? "%s<%s>" : "%s%s";
2985 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2986 return;
2987 sep = ", ";
2988 if (ref->tag)
2989 is_tag = TRUE;
2990 } while (refs[refpos++]->next);
2992 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2993 try_add_describe_ref:
2994 /* Add <tag>-g<commit_id> "fake" reference. */
2995 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2996 return;
2997 }
2999 if (bufpos == 0)
3000 return;
3002 if (!realloc_lines(view, view->line_size + 1))
3003 return;
3005 add_line_text(view, buf, LINE_PP_REFS);
3006 }
3008 static bool
3009 pager_read(struct view *view, char *data)
3010 {
3011 struct line *line;
3013 if (!data)
3014 return TRUE;
3016 line = add_line_text(view, data, get_line_type(data));
3017 if (!line)
3018 return FALSE;
3020 if (line->type == LINE_COMMIT &&
3021 (view == VIEW(REQ_VIEW_DIFF) ||
3022 view == VIEW(REQ_VIEW_LOG)))
3023 add_pager_refs(view, line);
3025 return TRUE;
3026 }
3028 static enum request
3029 pager_request(struct view *view, enum request request, struct line *line)
3030 {
3031 int split = 0;
3033 if (request != REQ_ENTER)
3034 return request;
3036 if (line->type == LINE_COMMIT &&
3037 (view == VIEW(REQ_VIEW_LOG) ||
3038 view == VIEW(REQ_VIEW_PAGER))) {
3039 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3040 split = 1;
3041 }
3043 /* Always scroll the view even if it was split. That way
3044 * you can use Enter to scroll through the log view and
3045 * split open each commit diff. */
3046 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3048 /* FIXME: A minor workaround. Scrolling the view will call report("")
3049 * but if we are scrolling a non-current view this won't properly
3050 * update the view title. */
3051 if (split)
3052 update_view_title(view);
3054 return REQ_NONE;
3055 }
3057 static bool
3058 pager_grep(struct view *view, struct line *line)
3059 {
3060 regmatch_t pmatch;
3061 char *text = line->data;
3063 if (!*text)
3064 return FALSE;
3066 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3067 return FALSE;
3069 return TRUE;
3070 }
3072 static void
3073 pager_select(struct view *view, struct line *line)
3074 {
3075 if (line->type == LINE_COMMIT) {
3076 char *text = (char *)line->data + STRING_SIZE("commit ");
3078 if (view != VIEW(REQ_VIEW_PAGER))
3079 string_copy_rev(view->ref, text);
3080 string_copy_rev(ref_commit, text);
3081 }
3082 }
3084 static struct view_ops pager_ops = {
3085 "line",
3086 NULL,
3087 pager_read,
3088 pager_draw,
3089 pager_request,
3090 pager_grep,
3091 pager_select,
3092 };
3094 static enum request
3095 log_request(struct view *view, enum request request, struct line *line)
3096 {
3097 switch (request) {
3098 case REQ_REFRESH:
3099 load_refs();
3100 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3101 return REQ_NONE;
3102 default:
3103 return pager_request(view, request, line);
3104 }
3105 }
3107 static struct view_ops log_ops = {
3108 "line",
3109 NULL,
3110 pager_read,
3111 pager_draw,
3112 log_request,
3113 pager_grep,
3114 pager_select,
3115 };
3118 /*
3119 * Help backend
3120 */
3122 static bool
3123 help_open(struct view *view)
3124 {
3125 char buf[BUFSIZ];
3126 int lines = ARRAY_SIZE(req_info) + 2;
3127 int i;
3129 if (view->lines > 0)
3130 return TRUE;
3132 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3133 if (!req_info[i].request)
3134 lines++;
3136 lines += run_requests + 1;
3138 view->line = calloc(lines, sizeof(*view->line));
3139 if (!view->line)
3140 return FALSE;
3142 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3144 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3145 const char *key;
3147 if (req_info[i].request == REQ_NONE)
3148 continue;
3150 if (!req_info[i].request) {
3151 add_line_text(view, "", LINE_DEFAULT);
3152 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3153 continue;
3154 }
3156 key = get_key(req_info[i].request);
3157 if (!*key)
3158 key = "(no key defined)";
3160 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3161 continue;
3163 add_line_text(view, buf, LINE_DEFAULT);
3164 }
3166 if (run_requests) {
3167 add_line_text(view, "", LINE_DEFAULT);
3168 add_line_text(view, "External commands:", LINE_DEFAULT);
3169 }
3171 for (i = 0; i < run_requests; i++) {
3172 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3173 const char *key;
3175 if (!req)
3176 continue;
3178 key = get_key_name(req->key);
3179 if (!*key)
3180 key = "(no key defined)";
3182 if (!string_format(buf, " %-10s %-14s `%s`",
3183 keymap_table[req->keymap].name,
3184 key, req->cmd))
3185 continue;
3187 add_line_text(view, buf, LINE_DEFAULT);
3188 }
3190 return TRUE;
3191 }
3193 static struct view_ops help_ops = {
3194 "line",
3195 help_open,
3196 NULL,
3197 pager_draw,
3198 pager_request,
3199 pager_grep,
3200 pager_select,
3201 };
3204 /*
3205 * Tree backend
3206 */
3208 struct tree_stack_entry {
3209 struct tree_stack_entry *prev; /* Entry below this in the stack */
3210 unsigned long lineno; /* Line number to restore */
3211 char *name; /* Position of name in opt_path */
3212 };
3214 /* The top of the path stack. */
3215 static struct tree_stack_entry *tree_stack = NULL;
3216 unsigned long tree_lineno = 0;
3218 static void
3219 pop_tree_stack_entry(void)
3220 {
3221 struct tree_stack_entry *entry = tree_stack;
3223 tree_lineno = entry->lineno;
3224 entry->name[0] = 0;
3225 tree_stack = entry->prev;
3226 free(entry);
3227 }
3229 static void
3230 push_tree_stack_entry(const char *name, unsigned long lineno)
3231 {
3232 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3233 size_t pathlen = strlen(opt_path);
3235 if (!entry)
3236 return;
3238 entry->prev = tree_stack;
3239 entry->name = opt_path + pathlen;
3240 tree_stack = entry;
3242 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3243 pop_tree_stack_entry();
3244 return;
3245 }
3247 /* Move the current line to the first tree entry. */
3248 tree_lineno = 1;
3249 entry->lineno = lineno;
3250 }
3252 /* Parse output from git-ls-tree(1):
3253 *
3254 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3255 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3256 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3257 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3258 */
3260 #define SIZEOF_TREE_ATTR \
3261 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3263 #define TREE_UP_FORMAT "040000 tree %s\t.."
3265 static int
3266 tree_compare_entry(enum line_type type1, const char *name1,
3267 enum line_type type2, const char *name2)
3268 {
3269 if (type1 != type2) {
3270 if (type1 == LINE_TREE_DIR)
3271 return -1;
3272 return 1;
3273 }
3275 return strcmp(name1, name2);
3276 }
3278 static const char *
3279 tree_path(struct line *line)
3280 {
3281 const char *path = line->data;
3283 return path + SIZEOF_TREE_ATTR;
3284 }
3286 static bool
3287 tree_read(struct view *view, char *text)
3288 {
3289 size_t textlen = text ? strlen(text) : 0;
3290 char buf[SIZEOF_STR];
3291 unsigned long pos;
3292 enum line_type type;
3293 bool first_read = view->lines == 0;
3295 if (!text)
3296 return TRUE;
3297 if (textlen <= SIZEOF_TREE_ATTR)
3298 return FALSE;
3300 type = text[STRING_SIZE("100644 ")] == 't'
3301 ? LINE_TREE_DIR : LINE_TREE_FILE;
3303 if (first_read) {
3304 /* Add path info line */
3305 if (!string_format(buf, "Directory path /%s", opt_path) ||
3306 !realloc_lines(view, view->line_size + 1) ||
3307 !add_line_text(view, buf, LINE_DEFAULT))
3308 return FALSE;
3310 /* Insert "link" to parent directory. */
3311 if (*opt_path) {
3312 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3313 !realloc_lines(view, view->line_size + 1) ||
3314 !add_line_text(view, buf, LINE_TREE_DIR))
3315 return FALSE;
3316 }
3317 }
3319 /* Strip the path part ... */
3320 if (*opt_path) {
3321 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3322 size_t striplen = strlen(opt_path);
3323 char *path = text + SIZEOF_TREE_ATTR;
3325 if (pathlen > striplen)
3326 memmove(path, path + striplen,
3327 pathlen - striplen + 1);
3328 }
3330 /* Skip "Directory ..." and ".." line. */
3331 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3332 struct line *line = &view->line[pos];
3333 const char *path1 = tree_path(line);
3334 char *path2 = text + SIZEOF_TREE_ATTR;
3335 int cmp = tree_compare_entry(line->type, path1, type, path2);
3337 if (cmp <= 0)
3338 continue;
3340 text = strdup(text);
3341 if (!text)
3342 return FALSE;
3344 if (view->lines > pos)
3345 memmove(&view->line[pos + 1], &view->line[pos],
3346 (view->lines - pos) * sizeof(*line));
3348 line = &view->line[pos];
3349 line->data = text;
3350 line->type = type;
3351 view->lines++;
3352 return TRUE;
3353 }
3355 if (!add_line_text(view, text, type))
3356 return FALSE;
3358 if (tree_lineno > view->lineno) {
3359 view->lineno = tree_lineno;
3360 tree_lineno = 0;
3361 }
3363 return TRUE;
3364 }
3366 static enum request
3367 tree_request(struct view *view, enum request request, struct line *line)
3368 {
3369 enum open_flags flags;
3371 switch (request) {
3372 case REQ_VIEW_BLAME:
3373 if (line->type != LINE_TREE_FILE) {
3374 report("Blame only supported for files");
3375 return REQ_NONE;
3376 }
3378 string_copy(opt_ref, view->vid);
3379 return request;
3381 case REQ_EDIT:
3382 if (line->type != LINE_TREE_FILE) {
3383 report("Edit only supported for files");
3384 } else if (!is_head_commit(view->vid)) {
3385 report("Edit only supported for files in the current work tree");
3386 } else {
3387 open_editor(TRUE, opt_file);
3388 }
3389 return REQ_NONE;
3391 case REQ_TREE_PARENT:
3392 if (!*opt_path) {
3393 /* quit view if at top of tree */
3394 return REQ_VIEW_CLOSE;
3395 }
3396 /* fake 'cd ..' */
3397 line = &view->line[1];
3398 break;
3400 case REQ_ENTER:
3401 break;
3403 default:
3404 return request;
3405 }
3407 /* Cleanup the stack if the tree view is at a different tree. */
3408 while (!*opt_path && tree_stack)
3409 pop_tree_stack_entry();
3411 switch (line->type) {
3412 case LINE_TREE_DIR:
3413 /* Depending on whether it is a subdir or parent (updir?) link
3414 * mangle the path buffer. */
3415 if (line == &view->line[1] && *opt_path) {
3416 pop_tree_stack_entry();
3418 } else {
3419 const char *basename = tree_path(line);
3421 push_tree_stack_entry(basename, view->lineno);
3422 }
3424 /* Trees and subtrees share the same ID, so they are not not
3425 * unique like blobs. */
3426 flags = OPEN_RELOAD;
3427 request = REQ_VIEW_TREE;
3428 break;
3430 case LINE_TREE_FILE:
3431 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3432 request = REQ_VIEW_BLOB;
3433 break;
3435 default:
3436 return TRUE;
3437 }
3439 open_view(view, request, flags);
3440 if (request == REQ_VIEW_TREE) {
3441 view->lineno = tree_lineno;
3442 }
3444 return REQ_NONE;
3445 }
3447 static void
3448 tree_select(struct view *view, struct line *line)
3449 {
3450 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3452 if (line->type == LINE_TREE_FILE) {
3453 string_copy_rev(ref_blob, text);
3454 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3456 } else if (line->type != LINE_TREE_DIR) {
3457 return;
3458 }
3460 string_copy_rev(view->ref, text);
3461 }
3463 static struct view_ops tree_ops = {
3464 "file",
3465 NULL,
3466 tree_read,
3467 pager_draw,
3468 tree_request,
3469 pager_grep,
3470 tree_select,
3471 };
3473 static bool
3474 blob_read(struct view *view, char *line)
3475 {
3476 if (!line)
3477 return TRUE;
3478 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3479 }
3481 static struct view_ops blob_ops = {
3482 "line",
3483 NULL,
3484 blob_read,
3485 pager_draw,
3486 pager_request,
3487 pager_grep,
3488 pager_select,
3489 };
3491 /*
3492 * Blame backend
3493 *
3494 * Loading the blame view is a two phase job:
3495 *
3496 * 1. File content is read either using opt_file from the
3497 * filesystem or using git-cat-file.
3498 * 2. Then blame information is incrementally added by
3499 * reading output from git-blame.
3500 */
3502 struct blame_commit {
3503 char id[SIZEOF_REV]; /* SHA1 ID. */
3504 char title[128]; /* First line of the commit message. */
3505 char author[75]; /* Author of the commit. */
3506 struct tm time; /* Date from the author ident. */
3507 char filename[128]; /* Name of file. */
3508 };
3510 struct blame {
3511 struct blame_commit *commit;
3512 unsigned int header:1;
3513 char text[1];
3514 };
3516 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3517 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3519 static bool
3520 blame_open(struct view *view)
3521 {
3522 char path[SIZEOF_STR];
3523 char ref[SIZEOF_STR] = "";
3525 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3526 return FALSE;
3528 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3529 return FALSE;
3531 if (*opt_ref || !(view->pipe = fopen(opt_file, "r"))) {
3532 const char *id = *opt_ref ? ref : "HEAD";
3534 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, id, path) ||
3535 !(view->pipe = popen(view->cmd, "r")))
3536 return FALSE;
3537 }
3539 if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3540 return FALSE;
3542 reset_view(view);
3543 string_format(view->ref, "%s ...", opt_file);
3544 string_copy_rev(view->vid, opt_file);
3545 set_nonblocking_input(TRUE);
3546 view->start_time = time(NULL);
3548 return TRUE;
3549 }
3551 static struct blame_commit *
3552 get_blame_commit(struct view *view, const char *id)
3553 {
3554 size_t i;
3556 for (i = 0; i < view->lines; i++) {
3557 struct blame *blame = view->line[i].data;
3559 if (!blame->commit)
3560 continue;
3562 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3563 return blame->commit;
3564 }
3566 {
3567 struct blame_commit *commit = calloc(1, sizeof(*commit));
3569 if (commit)
3570 string_ncopy(commit->id, id, SIZEOF_REV);
3571 return commit;
3572 }
3573 }
3575 static bool
3576 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3577 {
3578 const char *pos = *posref;
3580 *posref = NULL;
3581 pos = strchr(pos + 1, ' ');
3582 if (!pos || !isdigit(pos[1]))
3583 return FALSE;
3584 *number = atoi(pos + 1);
3585 if (*number < min || *number > max)
3586 return FALSE;
3588 *posref = pos;
3589 return TRUE;
3590 }
3592 static struct blame_commit *
3593 parse_blame_commit(struct view *view, const char *text, int *blamed)
3594 {
3595 struct blame_commit *commit;
3596 struct blame *blame;
3597 const char *pos = text + SIZEOF_REV - 1;
3598 size_t lineno;
3599 size_t group;
3601 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3602 return NULL;
3604 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3605 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3606 return NULL;
3608 commit = get_blame_commit(view, text);
3609 if (!commit)
3610 return NULL;
3612 *blamed += group;
3613 while (group--) {
3614 struct line *line = &view->line[lineno + group - 1];
3616 blame = line->data;
3617 blame->commit = commit;
3618 blame->header = !group;
3619 line->dirty = 1;
3620 }
3622 return commit;
3623 }
3625 static bool
3626 blame_read_file(struct view *view, const char *line, bool *read_file)
3627 {
3628 if (!line) {
3629 FILE *pipe = NULL;
3631 if (view->lines > 0)
3632 pipe = popen(view->cmd, "r");
3633 else if (!view->parent)
3634 die("No blame exist for %s", view->vid);
3635 if (!pipe) {
3636 report("Failed to load blame data");
3637 return TRUE;
3638 }
3640 fclose(view->pipe);
3641 view->pipe = pipe;
3642 *read_file = FALSE;
3643 return FALSE;
3645 } else {
3646 size_t linelen = strlen(line);
3647 struct blame *blame = malloc(sizeof(*blame) + linelen);
3649 blame->commit = NULL;
3650 strncpy(blame->text, line, linelen);
3651 blame->text[linelen] = 0;
3652 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3653 }
3654 }
3656 static bool
3657 match_blame_header(const char *name, char **line)
3658 {
3659 size_t namelen = strlen(name);
3660 bool matched = !strncmp(name, *line, namelen);
3662 if (matched)
3663 *line += namelen;
3665 return matched;
3666 }
3668 static bool
3669 blame_read(struct view *view, char *line)
3670 {
3671 static struct blame_commit *commit = NULL;
3672 static int blamed = 0;
3673 static time_t author_time;
3674 static bool read_file = TRUE;
3676 if (read_file)
3677 return blame_read_file(view, line, &read_file);
3679 if (!line) {
3680 /* Reset all! */
3681 commit = NULL;
3682 blamed = 0;
3683 read_file = TRUE;
3684 string_format(view->ref, "%s", view->vid);
3685 if (view_is_displayed(view)) {
3686 update_view_title(view);
3687 redraw_view_from(view, 0);
3688 }
3689 return TRUE;
3690 }
3692 if (!commit) {
3693 commit = parse_blame_commit(view, line, &blamed);
3694 string_format(view->ref, "%s %2d%%", view->vid,
3695 blamed * 100 / view->lines);
3697 } else if (match_blame_header("author ", &line)) {
3698 string_ncopy(commit->author, line, strlen(line));
3700 } else if (match_blame_header("author-time ", &line)) {
3701 author_time = (time_t) atol(line);
3703 } else if (match_blame_header("author-tz ", &line)) {
3704 long tz;
3706 tz = ('0' - line[1]) * 60 * 60 * 10;
3707 tz += ('0' - line[2]) * 60 * 60;
3708 tz += ('0' - line[3]) * 60;
3709 tz += ('0' - line[4]) * 60;
3711 if (line[0] == '-')
3712 tz = -tz;
3714 author_time -= tz;
3715 gmtime_r(&author_time, &commit->time);
3717 } else if (match_blame_header("summary ", &line)) {
3718 string_ncopy(commit->title, line, strlen(line));
3720 } else if (match_blame_header("filename ", &line)) {
3721 string_ncopy(commit->filename, line, strlen(line));
3722 commit = NULL;
3723 }
3725 return TRUE;
3726 }
3728 static bool
3729 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3730 {
3731 struct blame *blame = line->data;
3732 struct tm *time = NULL;
3733 const char *id = NULL, *author = NULL;
3735 if (blame->commit && *blame->commit->filename) {
3736 id = blame->commit->id;
3737 author = blame->commit->author;
3738 time = &blame->commit->time;
3739 }
3741 if (opt_date && draw_date(view, time))
3742 return TRUE;
3744 if (opt_author &&
3745 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3746 return TRUE;
3748 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3749 return TRUE;
3751 if (draw_lineno(view, lineno))
3752 return TRUE;
3754 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3755 return TRUE;
3756 }
3758 static enum request
3759 blame_request(struct view *view, enum request request, struct line *line)
3760 {
3761 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3762 struct blame *blame = line->data;
3764 switch (request) {
3765 case REQ_ENTER:
3766 if (!blame->commit) {
3767 report("No commit loaded yet");
3768 break;
3769 }
3771 if (!strcmp(blame->commit->id, NULL_ID)) {
3772 char path[SIZEOF_STR];
3774 if (sq_quote(path, 0, view->vid) >= sizeof(path))
3775 break;
3776 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3777 }
3779 open_view(view, REQ_VIEW_DIFF, flags);
3780 break;
3782 default:
3783 return request;
3784 }
3786 return REQ_NONE;
3787 }
3789 static bool
3790 blame_grep(struct view *view, struct line *line)
3791 {
3792 struct blame *blame = line->data;
3793 struct blame_commit *commit = blame->commit;
3794 regmatch_t pmatch;
3796 #define MATCH(text, on) \
3797 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3799 if (commit) {
3800 char buf[DATE_COLS + 1];
3802 if (MATCH(commit->title, 1) ||
3803 MATCH(commit->author, opt_author) ||
3804 MATCH(commit->id, opt_date))
3805 return TRUE;
3807 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3808 MATCH(buf, 1))
3809 return TRUE;
3810 }
3812 return MATCH(blame->text, 1);
3814 #undef MATCH
3815 }
3817 static void
3818 blame_select(struct view *view, struct line *line)
3819 {
3820 struct blame *blame = line->data;
3821 struct blame_commit *commit = blame->commit;
3823 if (!commit)
3824 return;
3826 if (!strcmp(commit->id, NULL_ID))
3827 string_ncopy(ref_commit, "HEAD", 4);
3828 else
3829 string_copy_rev(ref_commit, commit->id);
3830 }
3832 static struct view_ops blame_ops = {
3833 "line",
3834 blame_open,
3835 blame_read,
3836 blame_draw,
3837 blame_request,
3838 blame_grep,
3839 blame_select,
3840 };
3842 /*
3843 * Status backend
3844 */
3846 struct status {
3847 char status;
3848 struct {
3849 mode_t mode;
3850 char rev[SIZEOF_REV];
3851 char name[SIZEOF_STR];
3852 } old;
3853 struct {
3854 mode_t mode;
3855 char rev[SIZEOF_REV];
3856 char name[SIZEOF_STR];
3857 } new;
3858 };
3860 static char status_onbranch[SIZEOF_STR];
3861 static struct status stage_status;
3862 static enum line_type stage_line_type;
3863 static size_t stage_chunks;
3864 static int *stage_chunk;
3866 /* This should work even for the "On branch" line. */
3867 static inline bool
3868 status_has_none(struct view *view, struct line *line)
3869 {
3870 return line < view->line + view->lines && !line[1].data;
3871 }
3873 /* Get fields from the diff line:
3874 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3875 */
3876 static inline bool
3877 status_get_diff(struct status *file, const char *buf, size_t bufsize)
3878 {
3879 const char *old_mode = buf + 1;
3880 const char *new_mode = buf + 8;
3881 const char *old_rev = buf + 15;
3882 const char *new_rev = buf + 56;
3883 const char *status = buf + 97;
3885 if (bufsize < 99 ||
3886 old_mode[-1] != ':' ||
3887 new_mode[-1] != ' ' ||
3888 old_rev[-1] != ' ' ||
3889 new_rev[-1] != ' ' ||
3890 status[-1] != ' ')
3891 return FALSE;
3893 file->status = *status;
3895 string_copy_rev(file->old.rev, old_rev);
3896 string_copy_rev(file->new.rev, new_rev);
3898 file->old.mode = strtoul(old_mode, NULL, 8);
3899 file->new.mode = strtoul(new_mode, NULL, 8);
3901 file->old.name[0] = file->new.name[0] = 0;
3903 return TRUE;
3904 }
3906 static bool
3907 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3908 {
3909 struct status *file = NULL;
3910 struct status *unmerged = NULL;
3911 char buf[SIZEOF_STR * 4];
3912 size_t bufsize = 0;
3913 FILE *pipe;
3915 pipe = popen(cmd, "r");
3916 if (!pipe)
3917 return FALSE;
3919 add_line_data(view, NULL, type);
3921 while (!feof(pipe) && !ferror(pipe)) {
3922 char *sep;
3923 size_t readsize;
3925 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3926 if (!readsize)
3927 break;
3928 bufsize += readsize;
3930 /* Process while we have NUL chars. */
3931 while ((sep = memchr(buf, 0, bufsize))) {
3932 size_t sepsize = sep - buf + 1;
3934 if (!file) {
3935 if (!realloc_lines(view, view->line_size + 1))
3936 goto error_out;
3938 file = calloc(1, sizeof(*file));
3939 if (!file)
3940 goto error_out;
3942 add_line_data(view, file, type);
3943 }
3945 /* Parse diff info part. */
3946 if (status) {
3947 file->status = status;
3948 if (status == 'A')
3949 string_copy(file->old.rev, NULL_ID);
3951 } else if (!file->status) {
3952 if (!status_get_diff(file, buf, sepsize))
3953 goto error_out;
3955 bufsize -= sepsize;
3956 memmove(buf, sep + 1, bufsize);
3958 sep = memchr(buf, 0, bufsize);
3959 if (!sep)
3960 break;
3961 sepsize = sep - buf + 1;
3963 /* Collapse all 'M'odified entries that
3964 * follow a associated 'U'nmerged entry.
3965 */
3966 if (file->status == 'U') {
3967 unmerged = file;
3969 } else if (unmerged) {
3970 int collapse = !strcmp(buf, unmerged->new.name);
3972 unmerged = NULL;
3973 if (collapse) {
3974 free(file);
3975 view->lines--;
3976 continue;
3977 }
3978 }
3979 }
3981 /* Grab the old name for rename/copy. */
3982 if (!*file->old.name &&
3983 (file->status == 'R' || file->status == 'C')) {
3984 sepsize = sep - buf + 1;
3985 string_ncopy(file->old.name, buf, sepsize);
3986 bufsize -= sepsize;
3987 memmove(buf, sep + 1, bufsize);
3989 sep = memchr(buf, 0, bufsize);
3990 if (!sep)
3991 break;
3992 sepsize = sep - buf + 1;
3993 }
3995 /* git-ls-files just delivers a NUL separated
3996 * list of file names similar to the second half
3997 * of the git-diff-* output. */
3998 string_ncopy(file->new.name, buf, sepsize);
3999 if (!*file->old.name)
4000 string_copy(file->old.name, file->new.name);
4001 bufsize -= sepsize;
4002 memmove(buf, sep + 1, bufsize);
4003 file = NULL;
4004 }
4005 }
4007 if (ferror(pipe)) {
4008 error_out:
4009 pclose(pipe);
4010 return FALSE;
4011 }
4013 if (!view->line[view->lines - 1].data)
4014 add_line_data(view, NULL, LINE_STAT_NONE);
4016 pclose(pipe);
4017 return TRUE;
4018 }
4020 /* Don't show unmerged entries in the staged section. */
4021 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4022 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4023 #define STATUS_LIST_OTHER_CMD \
4024 "git ls-files -z --others --exclude-standard"
4025 #define STATUS_LIST_NO_HEAD_CMD \
4026 "git ls-files -z --cached --exclude-standard"
4028 #define STATUS_DIFF_INDEX_SHOW_CMD \
4029 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4031 #define STATUS_DIFF_FILES_SHOW_CMD \
4032 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4034 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4035 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4037 /* First parse staged info using git-diff-index(1), then parse unstaged
4038 * info using git-diff-files(1), and finally untracked files using
4039 * git-ls-files(1). */
4040 static bool
4041 status_open(struct view *view)
4042 {
4043 unsigned long prev_lineno = view->lineno;
4045 reset_view(view);
4047 if (!realloc_lines(view, view->line_size + 7))
4048 return FALSE;
4050 add_line_data(view, NULL, LINE_STAT_HEAD);
4051 if (is_initial_commit())
4052 string_copy(status_onbranch, "Initial commit");
4053 else if (!*opt_head)
4054 string_copy(status_onbranch, "Not currently on any branch");
4055 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4056 return FALSE;
4058 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4060 if (is_initial_commit()) {
4061 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4062 return FALSE;
4063 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4064 return FALSE;
4065 }
4067 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4068 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4069 return FALSE;
4071 /* If all went well restore the previous line number to stay in
4072 * the context or select a line with something that can be
4073 * updated. */
4074 if (prev_lineno >= view->lines)
4075 prev_lineno = view->lines - 1;
4076 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4077 prev_lineno++;
4078 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4079 prev_lineno--;
4081 /* If the above fails, always skip the "On branch" line. */
4082 if (prev_lineno < view->lines)
4083 view->lineno = prev_lineno;
4084 else
4085 view->lineno = 1;
4087 if (view->lineno < view->offset)
4088 view->offset = view->lineno;
4089 else if (view->offset + view->height <= view->lineno)
4090 view->offset = view->lineno - view->height + 1;
4092 return TRUE;
4093 }
4095 static bool
4096 status_draw(struct view *view, struct line *line, unsigned int lineno)
4097 {
4098 struct status *status = line->data;
4099 enum line_type type;
4100 const char *text;
4102 if (!status) {
4103 switch (line->type) {
4104 case LINE_STAT_STAGED:
4105 type = LINE_STAT_SECTION;
4106 text = "Changes to be committed:";
4107 break;
4109 case LINE_STAT_UNSTAGED:
4110 type = LINE_STAT_SECTION;
4111 text = "Changed but not updated:";
4112 break;
4114 case LINE_STAT_UNTRACKED:
4115 type = LINE_STAT_SECTION;
4116 text = "Untracked files:";
4117 break;
4119 case LINE_STAT_NONE:
4120 type = LINE_DEFAULT;
4121 text = " (no files)";
4122 break;
4124 case LINE_STAT_HEAD:
4125 type = LINE_STAT_HEAD;
4126 text = status_onbranch;
4127 break;
4129 default:
4130 return FALSE;
4131 }
4132 } else {
4133 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4135 buf[0] = status->status;
4136 if (draw_text(view, line->type, buf, TRUE))
4137 return TRUE;
4138 type = LINE_DEFAULT;
4139 text = status->new.name;
4140 }
4142 draw_text(view, type, text, TRUE);
4143 return TRUE;
4144 }
4146 static enum request
4147 status_enter(struct view *view, struct line *line)
4148 {
4149 struct status *status = line->data;
4150 char oldpath[SIZEOF_STR] = "";
4151 char newpath[SIZEOF_STR] = "";
4152 const char *info;
4153 size_t cmdsize = 0;
4154 enum open_flags split;
4156 if (line->type == LINE_STAT_NONE ||
4157 (!status && line[1].type == LINE_STAT_NONE)) {
4158 report("No file to diff");
4159 return REQ_NONE;
4160 }
4162 if (status) {
4163 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4164 return REQ_QUIT;
4165 /* Diffs for unmerged entries are empty when pasing the
4166 * new path, so leave it empty. */
4167 if (status->status != 'U' &&
4168 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4169 return REQ_QUIT;
4170 }
4172 if (opt_cdup[0] &&
4173 line->type != LINE_STAT_UNTRACKED &&
4174 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4175 return REQ_QUIT;
4177 switch (line->type) {
4178 case LINE_STAT_STAGED:
4179 if (is_initial_commit()) {
4180 if (!string_format_from(opt_cmd, &cmdsize,
4181 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4182 newpath))
4183 return REQ_QUIT;
4184 } else {
4185 if (!string_format_from(opt_cmd, &cmdsize,
4186 STATUS_DIFF_INDEX_SHOW_CMD,
4187 oldpath, newpath))
4188 return REQ_QUIT;
4189 }
4191 if (status)
4192 info = "Staged changes to %s";
4193 else
4194 info = "Staged changes";
4195 break;
4197 case LINE_STAT_UNSTAGED:
4198 if (!string_format_from(opt_cmd, &cmdsize,
4199 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4200 return REQ_QUIT;
4201 if (status)
4202 info = "Unstaged changes to %s";
4203 else
4204 info = "Unstaged changes";
4205 break;
4207 case LINE_STAT_UNTRACKED:
4208 if (opt_pipe)
4209 return REQ_QUIT;
4211 if (!status) {
4212 report("No file to show");
4213 return REQ_NONE;
4214 }
4216 if (!suffixcmp(status->new.name, -1, "/")) {
4217 report("Cannot display a directory");
4218 return REQ_NONE;
4219 }
4221 opt_pipe = fopen(status->new.name, "r");
4222 info = "Untracked file %s";
4223 break;
4225 case LINE_STAT_HEAD:
4226 return REQ_NONE;
4228 default:
4229 die("line type %d not handled in switch", line->type);
4230 }
4232 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4233 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4234 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4235 if (status) {
4236 stage_status = *status;
4237 } else {
4238 memset(&stage_status, 0, sizeof(stage_status));
4239 }
4241 stage_line_type = line->type;
4242 stage_chunks = 0;
4243 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4244 }
4246 return REQ_NONE;
4247 }
4249 static bool
4250 status_exists(struct status *status, enum line_type type)
4251 {
4252 struct view *view = VIEW(REQ_VIEW_STATUS);
4253 struct line *line;
4255 for (line = view->line; line < view->line + view->lines; line++) {
4256 struct status *pos = line->data;
4258 if (line->type == type && pos &&
4259 !strcmp(status->new.name, pos->new.name))
4260 return TRUE;
4261 }
4263 return FALSE;
4264 }
4267 static FILE *
4268 status_update_prepare(enum line_type type)
4269 {
4270 char cmd[SIZEOF_STR];
4271 size_t cmdsize = 0;
4273 if (opt_cdup[0] &&
4274 type != LINE_STAT_UNTRACKED &&
4275 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4276 return NULL;
4278 switch (type) {
4279 case LINE_STAT_STAGED:
4280 string_add(cmd, cmdsize, "git update-index -z --index-info");
4281 break;
4283 case LINE_STAT_UNSTAGED:
4284 case LINE_STAT_UNTRACKED:
4285 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4286 break;
4288 default:
4289 die("line type %d not handled in switch", type);
4290 }
4292 return popen(cmd, "w");
4293 }
4295 static bool
4296 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4297 {
4298 char buf[SIZEOF_STR];
4299 size_t bufsize = 0;
4300 size_t written = 0;
4302 switch (type) {
4303 case LINE_STAT_STAGED:
4304 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4305 status->old.mode,
4306 status->old.rev,
4307 status->old.name, 0))
4308 return FALSE;
4309 break;
4311 case LINE_STAT_UNSTAGED:
4312 case LINE_STAT_UNTRACKED:
4313 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4314 return FALSE;
4315 break;
4317 default:
4318 die("line type %d not handled in switch", type);
4319 }
4321 while (!ferror(pipe) && written < bufsize) {
4322 written += fwrite(buf + written, 1, bufsize - written, pipe);
4323 }
4325 return written == bufsize;
4326 }
4328 static bool
4329 status_update_file(struct status *status, enum line_type type)
4330 {
4331 FILE *pipe = status_update_prepare(type);
4332 bool result;
4334 if (!pipe)
4335 return FALSE;
4337 result = status_update_write(pipe, status, type);
4338 pclose(pipe);
4339 return result;
4340 }
4342 static bool
4343 status_update_files(struct view *view, struct line *line)
4344 {
4345 FILE *pipe = status_update_prepare(line->type);
4346 bool result = TRUE;
4347 struct line *pos = view->line + view->lines;
4348 int files = 0;
4349 int file, done;
4351 if (!pipe)
4352 return FALSE;
4354 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4355 files++;
4357 for (file = 0, done = 0; result && file < files; line++, file++) {
4358 int almost_done = file * 100 / files;
4360 if (almost_done > done) {
4361 done = almost_done;
4362 string_format(view->ref, "updating file %u of %u (%d%% done)",
4363 file, files, done);
4364 update_view_title(view);
4365 }
4366 result = status_update_write(pipe, line->data, line->type);
4367 }
4369 pclose(pipe);
4370 return result;
4371 }
4373 static bool
4374 status_update(struct view *view)
4375 {
4376 struct line *line = &view->line[view->lineno];
4378 assert(view->lines);
4380 if (!line->data) {
4381 /* This should work even for the "On branch" line. */
4382 if (line < view->line + view->lines && !line[1].data) {
4383 report("Nothing to update");
4384 return FALSE;
4385 }
4387 if (!status_update_files(view, line + 1)) {
4388 report("Failed to update file status");
4389 return FALSE;
4390 }
4392 } else if (!status_update_file(line->data, line->type)) {
4393 report("Failed to update file status");
4394 return FALSE;
4395 }
4397 return TRUE;
4398 }
4400 static bool
4401 status_revert(struct status *status, enum line_type type, bool has_none)
4402 {
4403 if (!status || type != LINE_STAT_UNSTAGED) {
4404 if (type == LINE_STAT_STAGED) {
4405 report("Cannot revert changes to staged files");
4406 } else if (type == LINE_STAT_UNTRACKED) {
4407 report("Cannot revert changes to untracked files");
4408 } else if (has_none) {
4409 report("Nothing to revert");
4410 } else {
4411 report("Cannot revert changes to multiple files");
4412 }
4413 return FALSE;
4415 } else {
4416 char cmd[SIZEOF_STR];
4417 char file_sq[SIZEOF_STR];
4419 if (sq_quote(file_sq, 0, status->old.name) >= sizeof(file_sq) ||
4420 !string_format(cmd, "git checkout -- %s%s", opt_cdup, file_sq))
4421 return FALSE;
4423 return run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4424 }
4425 }
4427 static enum request
4428 status_request(struct view *view, enum request request, struct line *line)
4429 {
4430 struct status *status = line->data;
4432 switch (request) {
4433 case REQ_STATUS_UPDATE:
4434 if (!status_update(view))
4435 return REQ_NONE;
4436 break;
4438 case REQ_STATUS_REVERT:
4439 if (!status_revert(status, line->type, status_has_none(view, line)))
4440 return REQ_NONE;
4441 break;
4443 case REQ_STATUS_MERGE:
4444 if (!status || status->status != 'U') {
4445 report("Merging only possible for files with unmerged status ('U').");
4446 return REQ_NONE;
4447 }
4448 open_mergetool(status->new.name);
4449 break;
4451 case REQ_EDIT:
4452 if (!status)
4453 return request;
4454 if (status->status == 'D') {
4455 report("File has been deleted.");
4456 return REQ_NONE;
4457 }
4459 open_editor(status->status != '?', status->new.name);
4460 break;
4462 case REQ_VIEW_BLAME:
4463 if (status) {
4464 string_copy(opt_file, status->new.name);
4465 opt_ref[0] = 0;
4466 }
4467 return request;
4469 case REQ_ENTER:
4470 /* After returning the status view has been split to
4471 * show the stage view. No further reloading is
4472 * necessary. */
4473 status_enter(view, line);
4474 return REQ_NONE;
4476 case REQ_REFRESH:
4477 /* Simply reload the view. */
4478 break;
4480 default:
4481 return request;
4482 }
4484 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4486 return REQ_NONE;
4487 }
4489 static void
4490 status_select(struct view *view, struct line *line)
4491 {
4492 struct status *status = line->data;
4493 char file[SIZEOF_STR] = "all files";
4494 const char *text;
4495 const char *key;
4497 if (status && !string_format(file, "'%s'", status->new.name))
4498 return;
4500 if (!status && line[1].type == LINE_STAT_NONE)
4501 line++;
4503 switch (line->type) {
4504 case LINE_STAT_STAGED:
4505 text = "Press %s to unstage %s for commit";
4506 break;
4508 case LINE_STAT_UNSTAGED:
4509 text = "Press %s to stage %s for commit";
4510 break;
4512 case LINE_STAT_UNTRACKED:
4513 text = "Press %s to stage %s for addition";
4514 break;
4516 case LINE_STAT_HEAD:
4517 case LINE_STAT_NONE:
4518 text = "Nothing to update";
4519 break;
4521 default:
4522 die("line type %d not handled in switch", line->type);
4523 }
4525 if (status && status->status == 'U') {
4526 text = "Press %s to resolve conflict in %s";
4527 key = get_key(REQ_STATUS_MERGE);
4529 } else {
4530 key = get_key(REQ_STATUS_UPDATE);
4531 }
4533 string_format(view->ref, text, key, file);
4534 }
4536 static bool
4537 status_grep(struct view *view, struct line *line)
4538 {
4539 struct status *status = line->data;
4540 enum { S_STATUS, S_NAME, S_END } state;
4541 char buf[2] = "?";
4542 regmatch_t pmatch;
4544 if (!status)
4545 return FALSE;
4547 for (state = S_STATUS; state < S_END; state++) {
4548 const char *text;
4550 switch (state) {
4551 case S_NAME: text = status->new.name; break;
4552 case S_STATUS:
4553 buf[0] = status->status;
4554 text = buf;
4555 break;
4557 default:
4558 return FALSE;
4559 }
4561 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4562 return TRUE;
4563 }
4565 return FALSE;
4566 }
4568 static struct view_ops status_ops = {
4569 "file",
4570 status_open,
4571 NULL,
4572 status_draw,
4573 status_request,
4574 status_grep,
4575 status_select,
4576 };
4579 static bool
4580 stage_diff_line(FILE *pipe, struct line *line)
4581 {
4582 const char *buf = line->data;
4583 size_t bufsize = strlen(buf);
4584 size_t written = 0;
4586 while (!ferror(pipe) && written < bufsize) {
4587 written += fwrite(buf + written, 1, bufsize - written, pipe);
4588 }
4590 fputc('\n', pipe);
4592 return written == bufsize;
4593 }
4595 static bool
4596 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4597 {
4598 while (line < end) {
4599 if (!stage_diff_line(pipe, line++))
4600 return FALSE;
4601 if (line->type == LINE_DIFF_CHUNK ||
4602 line->type == LINE_DIFF_HEADER)
4603 break;
4604 }
4606 return TRUE;
4607 }
4609 static struct line *
4610 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4611 {
4612 for (; view->line < line; line--)
4613 if (line->type == type)
4614 return line;
4616 return NULL;
4617 }
4619 static bool
4620 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4621 {
4622 char cmd[SIZEOF_STR];
4623 size_t cmdsize = 0;
4624 struct line *diff_hdr;
4625 FILE *pipe;
4627 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4628 if (!diff_hdr)
4629 return FALSE;
4631 if (opt_cdup[0] &&
4632 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4633 return FALSE;
4635 if (!string_format_from(cmd, &cmdsize,
4636 "git apply --whitespace=nowarn %s %s - && "
4637 "git update-index -q --unmerged --refresh 2>/dev/null",
4638 revert ? "" : "--cached",
4639 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4640 return FALSE;
4642 pipe = popen(cmd, "w");
4643 if (!pipe)
4644 return FALSE;
4646 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4647 !stage_diff_write(pipe, chunk, view->line + view->lines))
4648 chunk = NULL;
4650 pclose(pipe);
4652 return chunk ? TRUE : FALSE;
4653 }
4655 static bool
4656 stage_update(struct view *view, struct line *line)
4657 {
4658 struct line *chunk = NULL;
4660 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4661 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4663 if (chunk) {
4664 if (!stage_apply_chunk(view, chunk, FALSE)) {
4665 report("Failed to apply chunk");
4666 return FALSE;
4667 }
4669 } else if (!stage_status.status) {
4670 view = VIEW(REQ_VIEW_STATUS);
4672 for (line = view->line; line < view->line + view->lines; line++)
4673 if (line->type == stage_line_type)
4674 break;
4676 if (!status_update_files(view, line + 1)) {
4677 report("Failed to update files");
4678 return FALSE;
4679 }
4681 } else if (!status_update_file(&stage_status, stage_line_type)) {
4682 report("Failed to update file");
4683 return FALSE;
4684 }
4686 return TRUE;
4687 }
4689 static bool
4690 stage_revert(struct view *view, struct line *line)
4691 {
4692 struct line *chunk = NULL;
4694 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4695 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4697 if (chunk) {
4698 if (!prompt_yesno("Are you sure you want to revert changes?"))
4699 return FALSE;
4701 if (!stage_apply_chunk(view, chunk, TRUE)) {
4702 report("Failed to revert chunk");
4703 return FALSE;
4704 }
4705 return TRUE;
4707 } else {
4708 return status_revert(stage_status.status ? &stage_status : NULL,
4709 stage_line_type, FALSE);
4710 }
4711 }
4714 static void
4715 stage_next(struct view *view, struct line *line)
4716 {
4717 int i;
4719 if (!stage_chunks) {
4720 static size_t alloc = 0;
4721 int *tmp;
4723 for (line = view->line; line < view->line + view->lines; line++) {
4724 if (line->type != LINE_DIFF_CHUNK)
4725 continue;
4727 tmp = realloc_items(stage_chunk, &alloc,
4728 stage_chunks, sizeof(*tmp));
4729 if (!tmp) {
4730 report("Allocation failure");
4731 return;
4732 }
4734 stage_chunk = tmp;
4735 stage_chunk[stage_chunks++] = line - view->line;
4736 }
4737 }
4739 for (i = 0; i < stage_chunks; i++) {
4740 if (stage_chunk[i] > view->lineno) {
4741 do_scroll_view(view, stage_chunk[i] - view->lineno);
4742 report("Chunk %d of %d", i + 1, stage_chunks);
4743 return;
4744 }
4745 }
4747 report("No next chunk found");
4748 }
4750 static enum request
4751 stage_request(struct view *view, enum request request, struct line *line)
4752 {
4753 switch (request) {
4754 case REQ_STATUS_UPDATE:
4755 if (!stage_update(view, line))
4756 return REQ_NONE;
4757 break;
4759 case REQ_STATUS_REVERT:
4760 if (!stage_revert(view, line))
4761 return REQ_NONE;
4762 break;
4764 case REQ_STAGE_NEXT:
4765 if (stage_line_type == LINE_STAT_UNTRACKED) {
4766 report("File is untracked; press %s to add",
4767 get_key(REQ_STATUS_UPDATE));
4768 return REQ_NONE;
4769 }
4770 stage_next(view, line);
4771 return REQ_NONE;
4773 case REQ_EDIT:
4774 if (!stage_status.new.name[0])
4775 return request;
4776 if (stage_status.status == 'D') {
4777 report("File has been deleted.");
4778 return REQ_NONE;
4779 }
4781 open_editor(stage_status.status != '?', stage_status.new.name);
4782 break;
4784 case REQ_REFRESH:
4785 /* Reload everything ... */
4786 break;
4788 case REQ_VIEW_BLAME:
4789 if (stage_status.new.name[0]) {
4790 string_copy(opt_file, stage_status.new.name);
4791 opt_ref[0] = 0;
4792 }
4793 return request;
4795 case REQ_ENTER:
4796 return pager_request(view, request, line);
4798 default:
4799 return request;
4800 }
4802 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4804 /* Check whether the staged entry still exists, and close the
4805 * stage view if it doesn't. */
4806 if (!status_exists(&stage_status, stage_line_type))
4807 return REQ_VIEW_CLOSE;
4809 if (stage_line_type == LINE_STAT_UNTRACKED) {
4810 if (!suffixcmp(stage_status.new.name, -1, "/")) {
4811 report("Cannot display a directory");
4812 return REQ_NONE;
4813 }
4815 opt_pipe = fopen(stage_status.new.name, "r");
4816 }
4817 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
4819 return REQ_NONE;
4820 }
4822 static struct view_ops stage_ops = {
4823 "line",
4824 NULL,
4825 pager_read,
4826 pager_draw,
4827 stage_request,
4828 pager_grep,
4829 pager_select,
4830 };
4833 /*
4834 * Revision graph
4835 */
4837 struct commit {
4838 char id[SIZEOF_REV]; /* SHA1 ID. */
4839 char title[128]; /* First line of the commit message. */
4840 char author[75]; /* Author of the commit. */
4841 struct tm time; /* Date from the author ident. */
4842 struct ref **refs; /* Repository references. */
4843 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
4844 size_t graph_size; /* The width of the graph array. */
4845 bool has_parents; /* Rewritten --parents seen. */
4846 };
4848 /* Size of rev graph with no "padding" columns */
4849 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4851 struct rev_graph {
4852 struct rev_graph *prev, *next, *parents;
4853 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4854 size_t size;
4855 struct commit *commit;
4856 size_t pos;
4857 unsigned int boundary:1;
4858 };
4860 /* Parents of the commit being visualized. */
4861 static struct rev_graph graph_parents[4];
4863 /* The current stack of revisions on the graph. */
4864 static struct rev_graph graph_stacks[4] = {
4865 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4866 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4867 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4868 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4869 };
4871 static inline bool
4872 graph_parent_is_merge(struct rev_graph *graph)
4873 {
4874 return graph->parents->size > 1;
4875 }
4877 static inline void
4878 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4879 {
4880 struct commit *commit = graph->commit;
4882 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4883 commit->graph[commit->graph_size++] = symbol;
4884 }
4886 static void
4887 clear_rev_graph(struct rev_graph *graph)
4888 {
4889 graph->boundary = 0;
4890 graph->size = graph->pos = 0;
4891 graph->commit = NULL;
4892 memset(graph->parents, 0, sizeof(*graph->parents));
4893 }
4895 static void
4896 done_rev_graph(struct rev_graph *graph)
4897 {
4898 if (graph_parent_is_merge(graph) &&
4899 graph->pos < graph->size - 1 &&
4900 graph->next->size == graph->size + graph->parents->size - 1) {
4901 size_t i = graph->pos + graph->parents->size - 1;
4903 graph->commit->graph_size = i * 2;
4904 while (i < graph->next->size - 1) {
4905 append_to_rev_graph(graph, ' ');
4906 append_to_rev_graph(graph, '\\');
4907 i++;
4908 }
4909 }
4911 clear_rev_graph(graph);
4912 }
4914 static void
4915 push_rev_graph(struct rev_graph *graph, const char *parent)
4916 {
4917 int i;
4919 /* "Collapse" duplicate parents lines.
4920 *
4921 * FIXME: This needs to also update update the drawn graph but
4922 * for now it just serves as a method for pruning graph lines. */
4923 for (i = 0; i < graph->size; i++)
4924 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4925 return;
4927 if (graph->size < SIZEOF_REVITEMS) {
4928 string_copy_rev(graph->rev[graph->size++], parent);
4929 }
4930 }
4932 static chtype
4933 get_rev_graph_symbol(struct rev_graph *graph)
4934 {
4935 chtype symbol;
4937 if (graph->boundary)
4938 symbol = REVGRAPH_BOUND;
4939 else if (graph->parents->size == 0)
4940 symbol = REVGRAPH_INIT;
4941 else if (graph_parent_is_merge(graph))
4942 symbol = REVGRAPH_MERGE;
4943 else if (graph->pos >= graph->size)
4944 symbol = REVGRAPH_BRANCH;
4945 else
4946 symbol = REVGRAPH_COMMIT;
4948 return symbol;
4949 }
4951 static void
4952 draw_rev_graph(struct rev_graph *graph)
4953 {
4954 struct rev_filler {
4955 chtype separator, line;
4956 };
4957 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4958 static struct rev_filler fillers[] = {
4959 { ' ', '|' },
4960 { '`', '.' },
4961 { '\'', ' ' },
4962 { '/', ' ' },
4963 };
4964 chtype symbol = get_rev_graph_symbol(graph);
4965 struct rev_filler *filler;
4966 size_t i;
4968 if (opt_line_graphics)
4969 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4971 filler = &fillers[DEFAULT];
4973 for (i = 0; i < graph->pos; i++) {
4974 append_to_rev_graph(graph, filler->line);
4975 if (graph_parent_is_merge(graph->prev) &&
4976 graph->prev->pos == i)
4977 filler = &fillers[RSHARP];
4979 append_to_rev_graph(graph, filler->separator);
4980 }
4982 /* Place the symbol for this revision. */
4983 append_to_rev_graph(graph, symbol);
4985 if (graph->prev->size > graph->size)
4986 filler = &fillers[RDIAG];
4987 else
4988 filler = &fillers[DEFAULT];
4990 i++;
4992 for (; i < graph->size; i++) {
4993 append_to_rev_graph(graph, filler->separator);
4994 append_to_rev_graph(graph, filler->line);
4995 if (graph_parent_is_merge(graph->prev) &&
4996 i < graph->prev->pos + graph->parents->size)
4997 filler = &fillers[RSHARP];
4998 if (graph->prev->size > graph->size)
4999 filler = &fillers[LDIAG];
5000 }
5002 if (graph->prev->size > graph->size) {
5003 append_to_rev_graph(graph, filler->separator);
5004 if (filler->line != ' ')
5005 append_to_rev_graph(graph, filler->line);
5006 }
5007 }
5009 /* Prepare the next rev graph */
5010 static void
5011 prepare_rev_graph(struct rev_graph *graph)
5012 {
5013 size_t i;
5015 /* First, traverse all lines of revisions up to the active one. */
5016 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5017 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5018 break;
5020 push_rev_graph(graph->next, graph->rev[graph->pos]);
5021 }
5023 /* Interleave the new revision parent(s). */
5024 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5025 push_rev_graph(graph->next, graph->parents->rev[i]);
5027 /* Lastly, put any remaining revisions. */
5028 for (i = graph->pos + 1; i < graph->size; i++)
5029 push_rev_graph(graph->next, graph->rev[i]);
5030 }
5032 static void
5033 update_rev_graph(struct rev_graph *graph)
5034 {
5035 /* If this is the finalizing update ... */
5036 if (graph->commit)
5037 prepare_rev_graph(graph);
5039 /* Graph visualization needs a one rev look-ahead,
5040 * so the first update doesn't visualize anything. */
5041 if (!graph->prev->commit)
5042 return;
5044 draw_rev_graph(graph->prev);
5045 done_rev_graph(graph->prev->prev);
5046 }
5049 /*
5050 * Main view backend
5051 */
5053 static bool
5054 main_draw(struct view *view, struct line *line, unsigned int lineno)
5055 {
5056 struct commit *commit = line->data;
5058 if (!*commit->author)
5059 return FALSE;
5061 if (opt_date && draw_date(view, &commit->time))
5062 return TRUE;
5064 if (opt_author &&
5065 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5066 return TRUE;
5068 if (opt_rev_graph && commit->graph_size &&
5069 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5070 return TRUE;
5072 if (opt_show_refs && commit->refs) {
5073 size_t i = 0;
5075 do {
5076 enum line_type type;
5078 if (commit->refs[i]->head)
5079 type = LINE_MAIN_HEAD;
5080 else if (commit->refs[i]->ltag)
5081 type = LINE_MAIN_LOCAL_TAG;
5082 else if (commit->refs[i]->tag)
5083 type = LINE_MAIN_TAG;
5084 else if (commit->refs[i]->tracked)
5085 type = LINE_MAIN_TRACKED;
5086 else if (commit->refs[i]->remote)
5087 type = LINE_MAIN_REMOTE;
5088 else
5089 type = LINE_MAIN_REF;
5091 if (draw_text(view, type, "[", TRUE) ||
5092 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5093 draw_text(view, type, "]", TRUE))
5094 return TRUE;
5096 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5097 return TRUE;
5098 } while (commit->refs[i++]->next);
5099 }
5101 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5102 return TRUE;
5103 }
5105 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5106 static bool
5107 main_read(struct view *view, char *line)
5108 {
5109 static struct rev_graph *graph = graph_stacks;
5110 enum line_type type;
5111 struct commit *commit;
5113 if (!line) {
5114 int i;
5116 if (!view->lines && !view->parent)
5117 die("No revisions match the given arguments.");
5118 if (view->lines > 0) {
5119 commit = view->line[view->lines - 1].data;
5120 if (!*commit->author) {
5121 view->lines--;
5122 free(commit);
5123 graph->commit = NULL;
5124 }
5125 }
5126 update_rev_graph(graph);
5128 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5129 clear_rev_graph(&graph_stacks[i]);
5130 return TRUE;
5131 }
5133 type = get_line_type(line);
5134 if (type == LINE_COMMIT) {
5135 commit = calloc(1, sizeof(struct commit));
5136 if (!commit)
5137 return FALSE;
5139 line += STRING_SIZE("commit ");
5140 if (*line == '-') {
5141 graph->boundary = 1;
5142 line++;
5143 }
5145 string_copy_rev(commit->id, line);
5146 commit->refs = get_refs(commit->id);
5147 graph->commit = commit;
5148 add_line_data(view, commit, LINE_MAIN_COMMIT);
5150 while ((line = strchr(line, ' '))) {
5151 line++;
5152 push_rev_graph(graph->parents, line);
5153 commit->has_parents = TRUE;
5154 }
5155 return TRUE;
5156 }
5158 if (!view->lines)
5159 return TRUE;
5160 commit = view->line[view->lines - 1].data;
5162 switch (type) {
5163 case LINE_PARENT:
5164 if (commit->has_parents)
5165 break;
5166 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5167 break;
5169 case LINE_AUTHOR:
5170 {
5171 /* Parse author lines where the name may be empty:
5172 * author <email@address.tld> 1138474660 +0100
5173 */
5174 char *ident = line + STRING_SIZE("author ");
5175 char *nameend = strchr(ident, '<');
5176 char *emailend = strchr(ident, '>');
5178 if (!nameend || !emailend)
5179 break;
5181 update_rev_graph(graph);
5182 graph = graph->next;
5184 *nameend = *emailend = 0;
5185 ident = chomp_string(ident);
5186 if (!*ident) {
5187 ident = chomp_string(nameend + 1);
5188 if (!*ident)
5189 ident = "Unknown";
5190 }
5192 string_ncopy(commit->author, ident, strlen(ident));
5194 /* Parse epoch and timezone */
5195 if (emailend[1] == ' ') {
5196 char *secs = emailend + 2;
5197 char *zone = strchr(secs, ' ');
5198 time_t time = (time_t) atol(secs);
5200 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5201 long tz;
5203 zone++;
5204 tz = ('0' - zone[1]) * 60 * 60 * 10;
5205 tz += ('0' - zone[2]) * 60 * 60;
5206 tz += ('0' - zone[3]) * 60;
5207 tz += ('0' - zone[4]) * 60;
5209 if (zone[0] == '-')
5210 tz = -tz;
5212 time -= tz;
5213 }
5215 gmtime_r(&time, &commit->time);
5216 }
5217 break;
5218 }
5219 default:
5220 /* Fill in the commit title if it has not already been set. */
5221 if (commit->title[0])
5222 break;
5224 /* Require titles to start with a non-space character at the
5225 * offset used by git log. */
5226 if (strncmp(line, " ", 4))
5227 break;
5228 line += 4;
5229 /* Well, if the title starts with a whitespace character,
5230 * try to be forgiving. Otherwise we end up with no title. */
5231 while (isspace(*line))
5232 line++;
5233 if (*line == '\0')
5234 break;
5235 /* FIXME: More graceful handling of titles; append "..." to
5236 * shortened titles, etc. */
5238 string_ncopy(commit->title, line, strlen(line));
5239 }
5241 return TRUE;
5242 }
5244 static enum request
5245 main_request(struct view *view, enum request request, struct line *line)
5246 {
5247 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5249 switch (request) {
5250 case REQ_ENTER:
5251 open_view(view, REQ_VIEW_DIFF, flags);
5252 break;
5253 case REQ_REFRESH:
5254 load_refs();
5255 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5256 break;
5257 default:
5258 return request;
5259 }
5261 return REQ_NONE;
5262 }
5264 static bool
5265 grep_refs(struct ref **refs, regex_t *regex)
5266 {
5267 regmatch_t pmatch;
5268 size_t i = 0;
5270 if (!refs)
5271 return FALSE;
5272 do {
5273 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5274 return TRUE;
5275 } while (refs[i++]->next);
5277 return FALSE;
5278 }
5280 static bool
5281 main_grep(struct view *view, struct line *line)
5282 {
5283 struct commit *commit = line->data;
5284 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5285 char buf[DATE_COLS + 1];
5286 regmatch_t pmatch;
5288 for (state = S_TITLE; state < S_END; state++) {
5289 char *text;
5291 switch (state) {
5292 case S_TITLE: text = commit->title; break;
5293 case S_AUTHOR:
5294 if (!opt_author)
5295 continue;
5296 text = commit->author;
5297 break;
5298 case S_DATE:
5299 if (!opt_date)
5300 continue;
5301 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5302 continue;
5303 text = buf;
5304 break;
5305 case S_REFS:
5306 if (!opt_show_refs)
5307 continue;
5308 if (grep_refs(commit->refs, view->regex) == TRUE)
5309 return TRUE;
5310 continue;
5311 default:
5312 return FALSE;
5313 }
5315 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5316 return TRUE;
5317 }
5319 return FALSE;
5320 }
5322 static void
5323 main_select(struct view *view, struct line *line)
5324 {
5325 struct commit *commit = line->data;
5327 string_copy_rev(view->ref, commit->id);
5328 string_copy_rev(ref_commit, view->ref);
5329 }
5331 static struct view_ops main_ops = {
5332 "commit",
5333 NULL,
5334 main_read,
5335 main_draw,
5336 main_request,
5337 main_grep,
5338 main_select,
5339 };
5342 /*
5343 * Unicode / UTF-8 handling
5344 *
5345 * NOTE: Much of the following code for dealing with unicode is derived from
5346 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5347 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5348 */
5350 /* I've (over)annotated a lot of code snippets because I am not entirely
5351 * confident that the approach taken by this small UTF-8 interface is correct.
5352 * --jonas */
5354 static inline int
5355 unicode_width(unsigned long c)
5356 {
5357 if (c >= 0x1100 &&
5358 (c <= 0x115f /* Hangul Jamo */
5359 || c == 0x2329
5360 || c == 0x232a
5361 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5362 /* CJK ... Yi */
5363 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5364 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5365 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5366 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5367 || (c >= 0xffe0 && c <= 0xffe6)
5368 || (c >= 0x20000 && c <= 0x2fffd)
5369 || (c >= 0x30000 && c <= 0x3fffd)))
5370 return 2;
5372 if (c == '\t')
5373 return opt_tab_size;
5375 return 1;
5376 }
5378 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5379 * Illegal bytes are set one. */
5380 static const unsigned char utf8_bytes[256] = {
5381 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,
5382 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,
5383 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,
5384 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,
5385 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,
5386 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,
5387 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,
5388 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,
5389 };
5391 /* Decode UTF-8 multi-byte representation into a unicode character. */
5392 static inline unsigned long
5393 utf8_to_unicode(const char *string, size_t length)
5394 {
5395 unsigned long unicode;
5397 switch (length) {
5398 case 1:
5399 unicode = string[0];
5400 break;
5401 case 2:
5402 unicode = (string[0] & 0x1f) << 6;
5403 unicode += (string[1] & 0x3f);
5404 break;
5405 case 3:
5406 unicode = (string[0] & 0x0f) << 12;
5407 unicode += ((string[1] & 0x3f) << 6);
5408 unicode += (string[2] & 0x3f);
5409 break;
5410 case 4:
5411 unicode = (string[0] & 0x0f) << 18;
5412 unicode += ((string[1] & 0x3f) << 12);
5413 unicode += ((string[2] & 0x3f) << 6);
5414 unicode += (string[3] & 0x3f);
5415 break;
5416 case 5:
5417 unicode = (string[0] & 0x0f) << 24;
5418 unicode += ((string[1] & 0x3f) << 18);
5419 unicode += ((string[2] & 0x3f) << 12);
5420 unicode += ((string[3] & 0x3f) << 6);
5421 unicode += (string[4] & 0x3f);
5422 break;
5423 case 6:
5424 unicode = (string[0] & 0x01) << 30;
5425 unicode += ((string[1] & 0x3f) << 24);
5426 unicode += ((string[2] & 0x3f) << 18);
5427 unicode += ((string[3] & 0x3f) << 12);
5428 unicode += ((string[4] & 0x3f) << 6);
5429 unicode += (string[5] & 0x3f);
5430 break;
5431 default:
5432 die("Invalid unicode length");
5433 }
5435 /* Invalid characters could return the special 0xfffd value but NUL
5436 * should be just as good. */
5437 return unicode > 0xffff ? 0 : unicode;
5438 }
5440 /* Calculates how much of string can be shown within the given maximum width
5441 * and sets trimmed parameter to non-zero value if all of string could not be
5442 * shown. If the reserve flag is TRUE, it will reserve at least one
5443 * trailing character, which can be useful when drawing a delimiter.
5444 *
5445 * Returns the number of bytes to output from string to satisfy max_width. */
5446 static size_t
5447 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5448 {
5449 const char *start = string;
5450 const char *end = strchr(string, '\0');
5451 unsigned char last_bytes = 0;
5452 size_t last_ucwidth = 0;
5454 *width = 0;
5455 *trimmed = 0;
5457 while (string < end) {
5458 int c = *(unsigned char *) string;
5459 unsigned char bytes = utf8_bytes[c];
5460 size_t ucwidth;
5461 unsigned long unicode;
5463 if (string + bytes > end)
5464 break;
5466 /* Change representation to figure out whether
5467 * it is a single- or double-width character. */
5469 unicode = utf8_to_unicode(string, bytes);
5470 /* FIXME: Graceful handling of invalid unicode character. */
5471 if (!unicode)
5472 break;
5474 ucwidth = unicode_width(unicode);
5475 *width += ucwidth;
5476 if (*width > max_width) {
5477 *trimmed = 1;
5478 *width -= ucwidth;
5479 if (reserve && *width == max_width) {
5480 string -= last_bytes;
5481 *width -= last_ucwidth;
5482 }
5483 break;
5484 }
5486 string += bytes;
5487 last_bytes = bytes;
5488 last_ucwidth = ucwidth;
5489 }
5491 return string - start;
5492 }
5495 /*
5496 * Status management
5497 */
5499 /* Whether or not the curses interface has been initialized. */
5500 static bool cursed = FALSE;
5502 /* The status window is used for polling keystrokes. */
5503 static WINDOW *status_win;
5505 static bool status_empty = TRUE;
5507 /* Update status and title window. */
5508 static void
5509 report(const char *msg, ...)
5510 {
5511 struct view *view = display[current_view];
5513 if (input_mode)
5514 return;
5516 if (!view) {
5517 char buf[SIZEOF_STR];
5518 va_list args;
5520 va_start(args, msg);
5521 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5522 buf[sizeof(buf) - 1] = 0;
5523 buf[sizeof(buf) - 2] = '.';
5524 buf[sizeof(buf) - 3] = '.';
5525 buf[sizeof(buf) - 4] = '.';
5526 }
5527 va_end(args);
5528 die("%s", buf);
5529 }
5531 if (!status_empty || *msg) {
5532 va_list args;
5534 va_start(args, msg);
5536 wmove(status_win, 0, 0);
5537 if (*msg) {
5538 vwprintw(status_win, msg, args);
5539 status_empty = FALSE;
5540 } else {
5541 status_empty = TRUE;
5542 }
5543 wclrtoeol(status_win);
5544 wrefresh(status_win);
5546 va_end(args);
5547 }
5549 update_view_title(view);
5550 update_display_cursor(view);
5551 }
5553 /* Controls when nodelay should be in effect when polling user input. */
5554 static void
5555 set_nonblocking_input(bool loading)
5556 {
5557 static unsigned int loading_views;
5559 if ((loading == FALSE && loading_views-- == 1) ||
5560 (loading == TRUE && loading_views++ == 0))
5561 nodelay(status_win, loading);
5562 }
5564 static void
5565 init_display(void)
5566 {
5567 int x, y;
5569 /* Initialize the curses library */
5570 if (isatty(STDIN_FILENO)) {
5571 cursed = !!initscr();
5572 opt_tty = stdin;
5573 } else {
5574 /* Leave stdin and stdout alone when acting as a pager. */
5575 opt_tty = fopen("/dev/tty", "r+");
5576 if (!opt_tty)
5577 die("Failed to open /dev/tty");
5578 cursed = !!newterm(NULL, opt_tty, opt_tty);
5579 }
5581 if (!cursed)
5582 die("Failed to initialize curses");
5584 nonl(); /* Tell curses not to do NL->CR/NL on output */
5585 cbreak(); /* Take input chars one at a time, no wait for \n */
5586 noecho(); /* Don't echo input */
5587 leaveok(stdscr, TRUE);
5589 if (has_colors())
5590 init_colors();
5592 getmaxyx(stdscr, y, x);
5593 status_win = newwin(1, 0, y - 1, 0);
5594 if (!status_win)
5595 die("Failed to create status window");
5597 /* Enable keyboard mapping */
5598 keypad(status_win, TRUE);
5599 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5601 TABSIZE = opt_tab_size;
5602 if (opt_line_graphics) {
5603 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5604 }
5605 }
5607 static bool
5608 prompt_yesno(const char *prompt)
5609 {
5610 enum { WAIT, STOP, CANCEL } status = WAIT;
5611 bool answer = FALSE;
5613 while (status == WAIT) {
5614 struct view *view;
5615 int i, key;
5617 input_mode = TRUE;
5619 foreach_view (view, i)
5620 update_view(view);
5622 input_mode = FALSE;
5624 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5625 wclrtoeol(status_win);
5627 /* Refresh, accept single keystroke of input */
5628 key = wgetch(status_win);
5629 switch (key) {
5630 case ERR:
5631 break;
5633 case 'y':
5634 case 'Y':
5635 answer = TRUE;
5636 status = STOP;
5637 break;
5639 case KEY_ESC:
5640 case KEY_RETURN:
5641 case KEY_ENTER:
5642 case KEY_BACKSPACE:
5643 case 'n':
5644 case 'N':
5645 case '\n':
5646 default:
5647 answer = FALSE;
5648 status = CANCEL;
5649 }
5650 }
5652 /* Clear the status window */
5653 status_empty = FALSE;
5654 report("");
5656 return answer;
5657 }
5659 static char *
5660 read_prompt(const char *prompt)
5661 {
5662 enum { READING, STOP, CANCEL } status = READING;
5663 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5664 int pos = 0;
5666 while (status == READING) {
5667 struct view *view;
5668 int i, key;
5670 input_mode = TRUE;
5672 foreach_view (view, i)
5673 update_view(view);
5675 input_mode = FALSE;
5677 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5678 wclrtoeol(status_win);
5680 /* Refresh, accept single keystroke of input */
5681 key = wgetch(status_win);
5682 switch (key) {
5683 case KEY_RETURN:
5684 case KEY_ENTER:
5685 case '\n':
5686 status = pos ? STOP : CANCEL;
5687 break;
5689 case KEY_BACKSPACE:
5690 if (pos > 0)
5691 pos--;
5692 else
5693 status = CANCEL;
5694 break;
5696 case KEY_ESC:
5697 status = CANCEL;
5698 break;
5700 case ERR:
5701 break;
5703 default:
5704 if (pos >= sizeof(buf)) {
5705 report("Input string too long");
5706 return NULL;
5707 }
5709 if (isprint(key))
5710 buf[pos++] = (char) key;
5711 }
5712 }
5714 /* Clear the status window */
5715 status_empty = FALSE;
5716 report("");
5718 if (status == CANCEL)
5719 return NULL;
5721 buf[pos++] = 0;
5723 return buf;
5724 }
5726 /*
5727 * Repository references
5728 */
5730 static struct ref *refs = NULL;
5731 static size_t refs_alloc = 0;
5732 static size_t refs_size = 0;
5734 /* Id <-> ref store */
5735 static struct ref ***id_refs = NULL;
5736 static size_t id_refs_alloc = 0;
5737 static size_t id_refs_size = 0;
5739 static int
5740 compare_refs(const void *ref1_, const void *ref2_)
5741 {
5742 const struct ref *ref1 = *(const struct ref **)ref1_;
5743 const struct ref *ref2 = *(const struct ref **)ref2_;
5745 if (ref1->tag != ref2->tag)
5746 return ref2->tag - ref1->tag;
5747 if (ref1->ltag != ref2->ltag)
5748 return ref2->ltag - ref2->ltag;
5749 if (ref1->head != ref2->head)
5750 return ref2->head - ref1->head;
5751 if (ref1->tracked != ref2->tracked)
5752 return ref2->tracked - ref1->tracked;
5753 if (ref1->remote != ref2->remote)
5754 return ref2->remote - ref1->remote;
5755 return strcmp(ref1->name, ref2->name);
5756 }
5758 static struct ref **
5759 get_refs(const char *id)
5760 {
5761 struct ref ***tmp_id_refs;
5762 struct ref **ref_list = NULL;
5763 size_t ref_list_alloc = 0;
5764 size_t ref_list_size = 0;
5765 size_t i;
5767 for (i = 0; i < id_refs_size; i++)
5768 if (!strcmp(id, id_refs[i][0]->id))
5769 return id_refs[i];
5771 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5772 sizeof(*id_refs));
5773 if (!tmp_id_refs)
5774 return NULL;
5776 id_refs = tmp_id_refs;
5778 for (i = 0; i < refs_size; i++) {
5779 struct ref **tmp;
5781 if (strcmp(id, refs[i].id))
5782 continue;
5784 tmp = realloc_items(ref_list, &ref_list_alloc,
5785 ref_list_size + 1, sizeof(*ref_list));
5786 if (!tmp) {
5787 if (ref_list)
5788 free(ref_list);
5789 return NULL;
5790 }
5792 ref_list = tmp;
5793 ref_list[ref_list_size] = &refs[i];
5794 /* XXX: The properties of the commit chains ensures that we can
5795 * safely modify the shared ref. The repo references will
5796 * always be similar for the same id. */
5797 ref_list[ref_list_size]->next = 1;
5799 ref_list_size++;
5800 }
5802 if (ref_list) {
5803 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
5804 ref_list[ref_list_size - 1]->next = 0;
5805 id_refs[id_refs_size++] = ref_list;
5806 }
5808 return ref_list;
5809 }
5811 static int
5812 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5813 {
5814 struct ref *ref;
5815 bool tag = FALSE;
5816 bool ltag = FALSE;
5817 bool remote = FALSE;
5818 bool tracked = FALSE;
5819 bool check_replace = FALSE;
5820 bool head = FALSE;
5822 if (!prefixcmp(name, "refs/tags/")) {
5823 if (!suffixcmp(name, namelen, "^{}")) {
5824 namelen -= 3;
5825 name[namelen] = 0;
5826 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5827 check_replace = TRUE;
5828 } else {
5829 ltag = TRUE;
5830 }
5832 tag = TRUE;
5833 namelen -= STRING_SIZE("refs/tags/");
5834 name += STRING_SIZE("refs/tags/");
5836 } else if (!prefixcmp(name, "refs/remotes/")) {
5837 remote = TRUE;
5838 namelen -= STRING_SIZE("refs/remotes/");
5839 name += STRING_SIZE("refs/remotes/");
5840 tracked = !strcmp(opt_remote, name);
5842 } else if (!prefixcmp(name, "refs/heads/")) {
5843 namelen -= STRING_SIZE("refs/heads/");
5844 name += STRING_SIZE("refs/heads/");
5845 head = !strncmp(opt_head, name, namelen);
5847 } else if (!strcmp(name, "HEAD")) {
5848 string_ncopy(opt_head_rev, id, idlen);
5849 return OK;
5850 }
5852 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5853 /* it's an annotated tag, replace the previous sha1 with the
5854 * resolved commit id; relies on the fact git-ls-remote lists
5855 * the commit id of an annotated tag right before the commit id
5856 * it points to. */
5857 refs[refs_size - 1].ltag = ltag;
5858 string_copy_rev(refs[refs_size - 1].id, id);
5860 return OK;
5861 }
5862 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5863 if (!refs)
5864 return ERR;
5866 ref = &refs[refs_size++];
5867 ref->name = malloc(namelen + 1);
5868 if (!ref->name)
5869 return ERR;
5871 strncpy(ref->name, name, namelen);
5872 ref->name[namelen] = 0;
5873 ref->head = head;
5874 ref->tag = tag;
5875 ref->ltag = ltag;
5876 ref->remote = remote;
5877 ref->tracked = tracked;
5878 string_copy_rev(ref->id, id);
5880 return OK;
5881 }
5883 static int
5884 load_refs(void)
5885 {
5886 const char *cmd_env = getenv("TIG_LS_REMOTE");
5887 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5889 if (!*opt_git_dir)
5890 return OK;
5892 while (refs_size > 0)
5893 free(refs[--refs_size].name);
5894 while (id_refs_size > 0)
5895 free(id_refs[--id_refs_size]);
5897 return read_properties(popen(cmd, "r"), "\t", read_ref);
5898 }
5900 static int
5901 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5902 {
5903 if (!strcmp(name, "i18n.commitencoding"))
5904 string_ncopy(opt_encoding, value, valuelen);
5906 if (!strcmp(name, "core.editor"))
5907 string_ncopy(opt_editor, value, valuelen);
5909 /* branch.<head>.remote */
5910 if (*opt_head &&
5911 !strncmp(name, "branch.", 7) &&
5912 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5913 !strcmp(name + 7 + strlen(opt_head), ".remote"))
5914 string_ncopy(opt_remote, value, valuelen);
5916 if (*opt_head && *opt_remote &&
5917 !strncmp(name, "branch.", 7) &&
5918 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5919 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5920 size_t from = strlen(opt_remote);
5922 if (!prefixcmp(value, "refs/heads/")) {
5923 value += STRING_SIZE("refs/heads/");
5924 valuelen -= STRING_SIZE("refs/heads/");
5925 }
5927 if (!string_format_from(opt_remote, &from, "/%s", value))
5928 opt_remote[0] = 0;
5929 }
5931 return OK;
5932 }
5934 static int
5935 load_git_config(void)
5936 {
5937 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
5938 "=", read_repo_config_option);
5939 }
5941 static int
5942 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5943 {
5944 if (!opt_git_dir[0]) {
5945 string_ncopy(opt_git_dir, name, namelen);
5947 } else if (opt_is_inside_work_tree == -1) {
5948 /* This can be 3 different values depending on the
5949 * version of git being used. If git-rev-parse does not
5950 * understand --is-inside-work-tree it will simply echo
5951 * the option else either "true" or "false" is printed.
5952 * Default to true for the unknown case. */
5953 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5955 } else if (opt_cdup[0] == ' ') {
5956 string_ncopy(opt_cdup, name, namelen);
5957 } else {
5958 if (!prefixcmp(name, "refs/heads/")) {
5959 namelen -= STRING_SIZE("refs/heads/");
5960 name += STRING_SIZE("refs/heads/");
5961 string_ncopy(opt_head, name, namelen);
5962 }
5963 }
5965 return OK;
5966 }
5968 static int
5969 load_repo_info(void)
5970 {
5971 int result;
5972 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5973 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5975 /* XXX: The line outputted by "--show-cdup" can be empty so
5976 * initialize it to something invalid to make it possible to
5977 * detect whether it has been set or not. */
5978 opt_cdup[0] = ' ';
5980 result = read_properties(pipe, "=", read_repo_info);
5981 if (opt_cdup[0] == ' ')
5982 opt_cdup[0] = 0;
5984 return result;
5985 }
5987 static int
5988 read_properties(FILE *pipe, const char *separators,
5989 int (*read_property)(char *, size_t, char *, size_t))
5990 {
5991 char buffer[BUFSIZ];
5992 char *name;
5993 int state = OK;
5995 if (!pipe)
5996 return ERR;
5998 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5999 char *value;
6000 size_t namelen;
6001 size_t valuelen;
6003 name = chomp_string(name);
6004 namelen = strcspn(name, separators);
6006 if (name[namelen]) {
6007 name[namelen] = 0;
6008 value = chomp_string(name + namelen + 1);
6009 valuelen = strlen(value);
6011 } else {
6012 value = "";
6013 valuelen = 0;
6014 }
6016 state = read_property(name, namelen, value, valuelen);
6017 }
6019 if (state != ERR && ferror(pipe))
6020 state = ERR;
6022 pclose(pipe);
6024 return state;
6025 }
6028 /*
6029 * Main
6030 */
6032 static void __NORETURN
6033 quit(int sig)
6034 {
6035 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6036 if (cursed)
6037 endwin();
6038 exit(0);
6039 }
6041 static void __NORETURN
6042 die(const char *err, ...)
6043 {
6044 va_list args;
6046 endwin();
6048 va_start(args, err);
6049 fputs("tig: ", stderr);
6050 vfprintf(stderr, err, args);
6051 fputs("\n", stderr);
6052 va_end(args);
6054 exit(1);
6055 }
6057 static void
6058 warn(const char *msg, ...)
6059 {
6060 va_list args;
6062 va_start(args, msg);
6063 fputs("tig warning: ", stderr);
6064 vfprintf(stderr, msg, args);
6065 fputs("\n", stderr);
6066 va_end(args);
6067 }
6069 int
6070 main(int argc, const char *argv[])
6071 {
6072 struct view *view;
6073 enum request request;
6074 size_t i;
6076 signal(SIGINT, quit);
6078 if (setlocale(LC_ALL, "")) {
6079 char *codeset = nl_langinfo(CODESET);
6081 string_ncopy(opt_codeset, codeset, strlen(codeset));
6082 }
6084 if (load_repo_info() == ERR)
6085 die("Failed to load repo info.");
6087 if (load_options() == ERR)
6088 die("Failed to load user config.");
6090 if (load_git_config() == ERR)
6091 die("Failed to load repo config.");
6093 request = parse_options(argc, argv);
6094 if (request == REQ_NONE)
6095 return 0;
6097 /* Require a git repository unless when running in pager mode. */
6098 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6099 die("Not a git repository");
6101 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6102 opt_utf8 = FALSE;
6104 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6105 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6106 if (opt_iconv == ICONV_NONE)
6107 die("Failed to initialize character set conversion");
6108 }
6110 if (load_refs() == ERR)
6111 die("Failed to load refs.");
6113 foreach_view (view, i)
6114 view->cmd_env = getenv(view->cmd_env);
6116 init_display();
6118 while (view_driver(display[current_view], request)) {
6119 int key;
6120 int i;
6122 foreach_view (view, i)
6123 update_view(view);
6124 view = display[current_view];
6126 /* Refresh, accept single keystroke of input */
6127 key = wgetch(status_win);
6129 /* wgetch() with nodelay() enabled returns ERR when there's no
6130 * input. */
6131 if (key == ERR) {
6132 request = REQ_NONE;
6133 continue;
6134 }
6136 request = get_keybinding(view->keymap, key);
6138 /* Some low-level request handling. This keeps access to
6139 * status_win restricted. */
6140 switch (request) {
6141 case REQ_PROMPT:
6142 {
6143 char *cmd = read_prompt(":");
6145 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6146 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6147 request = REQ_VIEW_DIFF;
6148 } else {
6149 request = REQ_VIEW_PAGER;
6150 }
6152 /* Always reload^Wrerun commands from the prompt. */
6153 open_view(view, request, OPEN_RELOAD);
6154 }
6156 request = REQ_NONE;
6157 break;
6158 }
6159 case REQ_SEARCH:
6160 case REQ_SEARCH_BACK:
6161 {
6162 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6163 char *search = read_prompt(prompt);
6165 if (search)
6166 string_ncopy(opt_search, search, strlen(search));
6167 else
6168 request = REQ_NONE;
6169 break;
6170 }
6171 case REQ_SCREEN_RESIZE:
6172 {
6173 int height, width;
6175 getmaxyx(stdscr, height, width);
6177 /* Resize the status view and let the view driver take
6178 * care of resizing the displayed views. */
6179 wresize(status_win, 1, width);
6180 mvwin(status_win, height - 1, 0);
6181 wrefresh(status_win);
6182 break;
6183 }
6184 default:
6185 break;
6186 }
6187 }
6189 quit(0);
6191 return 0;
6192 }