Use the more advanced iconv.m4 script from ELinks
[tig.git] / tig.c
blob0390b3a00276a2ce4060daf9bd323aedd45e6f04
1 /* Copyright (c) 2006-2007 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 #include <curses.h>
47 #if __GNUC__ >= 3
48 #define __NORETURN __attribute__((__noreturn__))
49 #else
50 #define __NORETURN
51 #endif
53 static void __NORETURN die(const char *err, ...);
54 static void report(const char *msg, ...);
55 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
56 static void set_nonblocking_input(bool loading);
57 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
59 #define ABS(x) ((x) >= 0 ? (x) : -(x))
60 #define MIN(x, y) ((x) < (y) ? (x) : (y))
62 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
63 #define STRING_SIZE(x) (sizeof(x) - 1)
65 #define SIZEOF_STR 1024 /* Default string size. */
66 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
67 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
69 /* Revision graph */
71 #define REVGRAPH_INIT 'I'
72 #define REVGRAPH_MERGE 'M'
73 #define REVGRAPH_BRANCH '+'
74 #define REVGRAPH_COMMIT '*'
75 #define REVGRAPH_LINE '|'
77 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
79 /* This color name can be used to refer to the default term colors. */
80 #define COLOR_DEFAULT (-1)
82 #define ICONV_NONE ((iconv_t) -1)
84 /* The format and size of the date column in the main view. */
85 #define DATE_FORMAT "%Y-%m-%d %H:%M"
86 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
88 #define AUTHOR_COLS 20
90 /* The default interval between line numbers. */
91 #define NUMBER_INTERVAL 1
93 #define TABSIZE 8
95 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
97 #ifndef GIT_CONFIG
98 #define GIT_CONFIG "git config"
99 #endif
101 #define TIG_LS_REMOTE \
102 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
104 #define TIG_DIFF_CMD \
105 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
107 #define TIG_LOG_CMD \
108 "git log --cc --stat -n100 %s 2>/dev/null"
110 #define TIG_MAIN_CMD \
111 "git log --topo-order --pretty=raw %s 2>/dev/null"
113 #define TIG_TREE_CMD \
114 "git ls-tree %s %s"
116 #define TIG_BLOB_CMD \
117 "git cat-file blob %s"
119 /* XXX: Needs to be defined to the empty string. */
120 #define TIG_HELP_CMD ""
121 #define TIG_PAGER_CMD ""
122 #define TIG_STATUS_CMD ""
123 #define TIG_STAGE_CMD ""
125 /* Some ascii-shorthands fitted into the ncurses namespace. */
126 #define KEY_TAB '\t'
127 #define KEY_RETURN '\r'
128 #define KEY_ESC 27
131 struct ref {
132 char *name; /* Ref name; tag or head names are shortened. */
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int remote:1; /* Is it a remote ref? */
136 unsigned int next:1; /* For ref lists: are there more refs? */
139 static struct ref **get_refs(char *id);
141 struct int_map {
142 const char *name;
143 int namelen;
144 int value;
147 static int
148 set_from_int_map(struct int_map *map, size_t map_size,
149 int *value, const char *name, int namelen)
152 int i;
154 for (i = 0; i < map_size; i++)
155 if (namelen == map[i].namelen &&
156 !strncasecmp(name, map[i].name, namelen)) {
157 *value = map[i].value;
158 return OK;
161 return ERR;
166 * String helpers
169 static inline void
170 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
172 if (srclen > dstlen - 1)
173 srclen = dstlen - 1;
175 strncpy(dst, src, srclen);
176 dst[srclen] = 0;
179 /* Shorthands for safely copying into a fixed buffer. */
181 #define string_copy(dst, src) \
182 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
184 #define string_ncopy(dst, src, srclen) \
185 string_ncopy_do(dst, sizeof(dst), src, srclen)
187 #define string_copy_rev(dst, src) \
188 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
190 #define string_add(dst, from, src) \
191 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
193 static char *
194 chomp_string(char *name)
196 int namelen;
198 while (isspace(*name))
199 name++;
201 namelen = strlen(name) - 1;
202 while (namelen > 0 && isspace(name[namelen]))
203 name[namelen--] = 0;
205 return name;
208 static bool
209 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
211 va_list args;
212 size_t pos = bufpos ? *bufpos : 0;
214 va_start(args, fmt);
215 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
216 va_end(args);
218 if (bufpos)
219 *bufpos = pos;
221 return pos >= bufsize ? FALSE : TRUE;
224 #define string_format(buf, fmt, args...) \
225 string_nformat(buf, sizeof(buf), NULL, fmt, args)
227 #define string_format_from(buf, from, fmt, args...) \
228 string_nformat(buf, sizeof(buf), from, fmt, args)
230 static int
231 string_enum_compare(const char *str1, const char *str2, int len)
233 size_t i;
235 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
237 /* Diff-Header == DIFF_HEADER */
238 for (i = 0; i < len; i++) {
239 if (toupper(str1[i]) == toupper(str2[i]))
240 continue;
242 if (string_enum_sep(str1[i]) &&
243 string_enum_sep(str2[i]))
244 continue;
246 return str1[i] - str2[i];
249 return 0;
252 /* Shell quoting
254 * NOTE: The following is a slightly modified copy of the git project's shell
255 * quoting routines found in the quote.c file.
257 * Help to copy the thing properly quoted for the shell safety. any single
258 * quote is replaced with '\'', any exclamation point is replaced with '\!',
259 * and the whole thing is enclosed in a
261 * E.g.
262 * original sq_quote result
263 * name ==> name ==> 'name'
264 * a b ==> a b ==> 'a b'
265 * a'b ==> a'\''b ==> 'a'\''b'
266 * a!b ==> a'\!'b ==> 'a'\!'b'
269 static size_t
270 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
272 char c;
274 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
276 BUFPUT('\'');
277 while ((c = *src++)) {
278 if (c == '\'' || c == '!') {
279 BUFPUT('\'');
280 BUFPUT('\\');
281 BUFPUT(c);
282 BUFPUT('\'');
283 } else {
284 BUFPUT(c);
287 BUFPUT('\'');
289 if (bufsize < SIZEOF_STR)
290 buf[bufsize] = 0;
292 return bufsize;
297 * User requests
300 #define REQ_INFO \
301 /* XXX: Keep the view request first and in sync with views[]. */ \
302 REQ_GROUP("View switching") \
303 REQ_(VIEW_MAIN, "Show main view"), \
304 REQ_(VIEW_DIFF, "Show diff view"), \
305 REQ_(VIEW_LOG, "Show log view"), \
306 REQ_(VIEW_TREE, "Show tree view"), \
307 REQ_(VIEW_BLOB, "Show blob view"), \
308 REQ_(VIEW_HELP, "Show help page"), \
309 REQ_(VIEW_PAGER, "Show pager view"), \
310 REQ_(VIEW_STATUS, "Show status view"), \
311 REQ_(VIEW_STAGE, "Show stage view"), \
313 REQ_GROUP("View manipulation") \
314 REQ_(ENTER, "Enter current line and scroll"), \
315 REQ_(NEXT, "Move to next"), \
316 REQ_(PREVIOUS, "Move to previous"), \
317 REQ_(VIEW_NEXT, "Move focus to next view"), \
318 REQ_(VIEW_CLOSE, "Close the current view"), \
319 REQ_(QUIT, "Close all views and quit"), \
321 REQ_GROUP("Cursor navigation") \
322 REQ_(MOVE_UP, "Move cursor one line up"), \
323 REQ_(MOVE_DOWN, "Move cursor one line down"), \
324 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
325 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
326 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
327 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
329 REQ_GROUP("Scrolling") \
330 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
331 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
332 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
333 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
335 REQ_GROUP("Searching") \
336 REQ_(SEARCH, "Search the view"), \
337 REQ_(SEARCH_BACK, "Search backwards in the view"), \
338 REQ_(FIND_NEXT, "Find next search match"), \
339 REQ_(FIND_PREV, "Find previous search match"), \
341 REQ_GROUP("Misc") \
342 REQ_(NONE, "Do nothing"), \
343 REQ_(PROMPT, "Bring up the prompt"), \
344 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
345 REQ_(SCREEN_RESIZE, "Resize the screen"), \
346 REQ_(SHOW_VERSION, "Show version information"), \
347 REQ_(STOP_LOADING, "Stop all loading views"), \
348 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
349 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
350 REQ_(STATUS_UPDATE, "Update file status"), \
351 REQ_(EDIT, "Open in editor")
354 /* User action requests. */
355 enum request {
356 #define REQ_GROUP(help)
357 #define REQ_(req, help) REQ_##req
359 /* Offset all requests to avoid conflicts with ncurses getch values. */
360 REQ_OFFSET = KEY_MAX + 1,
361 REQ_INFO,
362 REQ_UNKNOWN,
364 #undef REQ_GROUP
365 #undef REQ_
368 struct request_info {
369 enum request request;
370 char *name;
371 int namelen;
372 char *help;
375 static struct request_info req_info[] = {
376 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
377 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
378 REQ_INFO
379 #undef REQ_GROUP
380 #undef REQ_
383 static enum request
384 get_request(const char *name)
386 int namelen = strlen(name);
387 int i;
389 for (i = 0; i < ARRAY_SIZE(req_info); i++)
390 if (req_info[i].namelen == namelen &&
391 !string_enum_compare(req_info[i].name, name, namelen))
392 return req_info[i].request;
394 return REQ_UNKNOWN;
399 * Options
402 static const char usage[] =
403 "tig " TIG_VERSION " (" __DATE__ ")\n"
404 "\n"
405 "Usage: tig [options]\n"
406 " or: tig [options] [--] [git log options]\n"
407 " or: tig [options] log [git log options]\n"
408 " or: tig [options] diff [git diff options]\n"
409 " or: tig [options] show [git show options]\n"
410 " or: tig [options] < [git command output]\n"
411 "\n"
412 "Options:\n"
413 " -l Start up in log view\n"
414 " -d Start up in diff view\n"
415 " -S Start up in status view\n"
416 " -n[I], --line-number[=I] Show line numbers with given interval\n"
417 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
418 " -- Mark end of tig options\n"
419 " -v, --version Show version and exit\n"
420 " -h, --help Show help message and exit\n";
422 /* Option and state variables. */
423 static bool opt_line_number = FALSE;
424 static bool opt_rev_graph = FALSE;
425 static int opt_num_interval = NUMBER_INTERVAL;
426 static int opt_tab_size = TABSIZE;
427 static enum request opt_request = REQ_VIEW_MAIN;
428 static char opt_cmd[SIZEOF_STR] = "";
429 static char opt_path[SIZEOF_STR] = "";
430 static FILE *opt_pipe = NULL;
431 static char opt_encoding[20] = "UTF-8";
432 static bool opt_utf8 = TRUE;
433 static char opt_codeset[20] = "UTF-8";
434 static iconv_t opt_iconv = ICONV_NONE;
435 static char opt_search[SIZEOF_STR] = "";
436 static char opt_cdup[SIZEOF_STR] = "";
437 static char opt_git_dir[SIZEOF_STR] = "";
438 static char opt_editor[SIZEOF_STR] = "";
440 enum option_type {
441 OPT_NONE,
442 OPT_INT,
445 static bool
446 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
448 va_list args;
449 char *value = "";
450 int *number;
452 if (opt[0] != '-')
453 return FALSE;
455 if (opt[1] == '-') {
456 int namelen = strlen(name);
458 opt += 2;
460 if (strncmp(opt, name, namelen))
461 return FALSE;
463 if (opt[namelen] == '=')
464 value = opt + namelen + 1;
466 } else {
467 if (!short_name || opt[1] != short_name)
468 return FALSE;
469 value = opt + 2;
472 va_start(args, type);
473 if (type == OPT_INT) {
474 number = va_arg(args, int *);
475 if (isdigit(*value))
476 *number = atoi(value);
478 va_end(args);
480 return TRUE;
483 /* Returns the index of log or diff command or -1 to exit. */
484 static bool
485 parse_options(int argc, char *argv[])
487 int i;
489 for (i = 1; i < argc; i++) {
490 char *opt = argv[i];
492 if (!strcmp(opt, "log") ||
493 !strcmp(opt, "diff") ||
494 !strcmp(opt, "show")) {
495 opt_request = opt[0] == 'l'
496 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
497 break;
500 if (opt[0] && opt[0] != '-')
501 break;
503 if (!strcmp(opt, "-l")) {
504 opt_request = REQ_VIEW_LOG;
505 continue;
508 if (!strcmp(opt, "-d")) {
509 opt_request = REQ_VIEW_DIFF;
510 continue;
513 if (!strcmp(opt, "-S")) {
514 opt_request = REQ_VIEW_STATUS;
515 continue;
518 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
519 opt_line_number = TRUE;
520 continue;
523 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
524 opt_tab_size = MIN(opt_tab_size, TABSIZE);
525 continue;
528 if (check_option(opt, 'v', "version", OPT_NONE)) {
529 printf("tig version %s\n", TIG_VERSION);
530 return FALSE;
533 if (check_option(opt, 'h', "help", OPT_NONE)) {
534 printf(usage);
535 return FALSE;
538 if (!strcmp(opt, "--")) {
539 i++;
540 break;
543 die("unknown option '%s'\n\n%s", opt, usage);
546 if (!isatty(STDIN_FILENO)) {
547 opt_request = REQ_VIEW_PAGER;
548 opt_pipe = stdin;
550 } else if (i < argc) {
551 size_t buf_size;
553 if (opt_request == REQ_VIEW_MAIN)
554 /* XXX: This is vulnerable to the user overriding
555 * options required for the main view parser. */
556 string_copy(opt_cmd, "git log --pretty=raw");
557 else
558 string_copy(opt_cmd, "git");
559 buf_size = strlen(opt_cmd);
561 while (buf_size < sizeof(opt_cmd) && i < argc) {
562 opt_cmd[buf_size++] = ' ';
563 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
566 if (buf_size >= sizeof(opt_cmd))
567 die("command too long");
569 opt_cmd[buf_size] = 0;
572 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
573 opt_utf8 = FALSE;
575 return TRUE;
580 * Line-oriented content detection.
583 #define LINE_INFO \
584 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
585 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
586 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
587 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
588 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
589 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
590 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
591 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
592 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
593 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
594 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
595 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
596 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
597 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
598 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
599 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
600 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
601 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
602 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
603 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
604 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
605 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
606 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
607 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
608 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
609 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
610 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
611 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
612 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
613 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
614 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
615 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
616 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
617 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
618 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
619 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
620 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
621 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
622 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
623 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
624 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
625 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
626 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
627 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
628 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
629 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
630 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
632 enum line_type {
633 #define LINE(type, line, fg, bg, attr) \
634 LINE_##type
635 LINE_INFO
636 #undef LINE
639 struct line_info {
640 const char *name; /* Option name. */
641 int namelen; /* Size of option name. */
642 const char *line; /* The start of line to match. */
643 int linelen; /* Size of string to match. */
644 int fg, bg, attr; /* Color and text attributes for the lines. */
647 static struct line_info line_info[] = {
648 #define LINE(type, line, fg, bg, attr) \
649 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
650 LINE_INFO
651 #undef LINE
654 static enum line_type
655 get_line_type(char *line)
657 int linelen = strlen(line);
658 enum line_type type;
660 for (type = 0; type < ARRAY_SIZE(line_info); type++)
661 /* Case insensitive search matches Signed-off-by lines better. */
662 if (linelen >= line_info[type].linelen &&
663 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
664 return type;
666 return LINE_DEFAULT;
669 static inline int
670 get_line_attr(enum line_type type)
672 assert(type < ARRAY_SIZE(line_info));
673 return COLOR_PAIR(type) | line_info[type].attr;
676 static struct line_info *
677 get_line_info(char *name, int namelen)
679 enum line_type type;
681 for (type = 0; type < ARRAY_SIZE(line_info); type++)
682 if (namelen == line_info[type].namelen &&
683 !string_enum_compare(line_info[type].name, name, namelen))
684 return &line_info[type];
686 return NULL;
689 static void
690 init_colors(void)
692 int default_bg = COLOR_BLACK;
693 int default_fg = COLOR_WHITE;
694 enum line_type type;
696 start_color();
698 if (use_default_colors() != ERR) {
699 default_bg = -1;
700 default_fg = -1;
703 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
704 struct line_info *info = &line_info[type];
705 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
706 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
708 init_pair(type, fg, bg);
712 struct line {
713 enum line_type type;
715 /* State flags */
716 unsigned int selected:1;
718 void *data; /* User data */
723 * Keys
726 struct keybinding {
727 int alias;
728 enum request request;
729 struct keybinding *next;
732 static struct keybinding default_keybindings[] = {
733 /* View switching */
734 { 'm', REQ_VIEW_MAIN },
735 { 'd', REQ_VIEW_DIFF },
736 { 'l', REQ_VIEW_LOG },
737 { 't', REQ_VIEW_TREE },
738 { 'f', REQ_VIEW_BLOB },
739 { 'p', REQ_VIEW_PAGER },
740 { 'h', REQ_VIEW_HELP },
741 { 'S', REQ_VIEW_STATUS },
742 { 'c', REQ_VIEW_STAGE },
744 /* View manipulation */
745 { 'q', REQ_VIEW_CLOSE },
746 { KEY_TAB, REQ_VIEW_NEXT },
747 { KEY_RETURN, REQ_ENTER },
748 { KEY_UP, REQ_PREVIOUS },
749 { KEY_DOWN, REQ_NEXT },
751 /* Cursor navigation */
752 { 'k', REQ_MOVE_UP },
753 { 'j', REQ_MOVE_DOWN },
754 { KEY_HOME, REQ_MOVE_FIRST_LINE },
755 { KEY_END, REQ_MOVE_LAST_LINE },
756 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
757 { ' ', REQ_MOVE_PAGE_DOWN },
758 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
759 { 'b', REQ_MOVE_PAGE_UP },
760 { '-', REQ_MOVE_PAGE_UP },
762 /* Scrolling */
763 { KEY_IC, REQ_SCROLL_LINE_UP },
764 { KEY_DC, REQ_SCROLL_LINE_DOWN },
765 { 'w', REQ_SCROLL_PAGE_UP },
766 { 's', REQ_SCROLL_PAGE_DOWN },
768 /* Searching */
769 { '/', REQ_SEARCH },
770 { '?', REQ_SEARCH_BACK },
771 { 'n', REQ_FIND_NEXT },
772 { 'N', REQ_FIND_PREV },
774 /* Misc */
775 { 'Q', REQ_QUIT },
776 { 'z', REQ_STOP_LOADING },
777 { 'v', REQ_SHOW_VERSION },
778 { 'r', REQ_SCREEN_REDRAW },
779 { '.', REQ_TOGGLE_LINENO },
780 { 'g', REQ_TOGGLE_REV_GRAPH },
781 { ':', REQ_PROMPT },
782 { 'u', REQ_STATUS_UPDATE },
783 { 'e', REQ_EDIT },
785 /* Using the ncurses SIGWINCH handler. */
786 { KEY_RESIZE, REQ_SCREEN_RESIZE },
789 #define KEYMAP_INFO \
790 KEYMAP_(GENERIC), \
791 KEYMAP_(MAIN), \
792 KEYMAP_(DIFF), \
793 KEYMAP_(LOG), \
794 KEYMAP_(TREE), \
795 KEYMAP_(BLOB), \
796 KEYMAP_(PAGER), \
797 KEYMAP_(HELP), \
798 KEYMAP_(STATUS), \
799 KEYMAP_(STAGE)
801 enum keymap {
802 #define KEYMAP_(name) KEYMAP_##name
803 KEYMAP_INFO
804 #undef KEYMAP_
807 static struct int_map keymap_table[] = {
808 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
809 KEYMAP_INFO
810 #undef KEYMAP_
813 #define set_keymap(map, name) \
814 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
816 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
818 static void
819 add_keybinding(enum keymap keymap, enum request request, int key)
821 struct keybinding *keybinding;
823 keybinding = calloc(1, sizeof(*keybinding));
824 if (!keybinding)
825 die("Failed to allocate keybinding");
827 keybinding->alias = key;
828 keybinding->request = request;
829 keybinding->next = keybindings[keymap];
830 keybindings[keymap] = keybinding;
833 /* Looks for a key binding first in the given map, then in the generic map, and
834 * lastly in the default keybindings. */
835 static enum request
836 get_keybinding(enum keymap keymap, int key)
838 struct keybinding *kbd;
839 int i;
841 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
842 if (kbd->alias == key)
843 return kbd->request;
845 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
846 if (kbd->alias == key)
847 return kbd->request;
849 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
850 if (default_keybindings[i].alias == key)
851 return default_keybindings[i].request;
853 return (enum request) key;
857 struct key {
858 char *name;
859 int value;
862 static struct key key_table[] = {
863 { "Enter", KEY_RETURN },
864 { "Space", ' ' },
865 { "Backspace", KEY_BACKSPACE },
866 { "Tab", KEY_TAB },
867 { "Escape", KEY_ESC },
868 { "Left", KEY_LEFT },
869 { "Right", KEY_RIGHT },
870 { "Up", KEY_UP },
871 { "Down", KEY_DOWN },
872 { "Insert", KEY_IC },
873 { "Delete", KEY_DC },
874 { "Hash", '#' },
875 { "Home", KEY_HOME },
876 { "End", KEY_END },
877 { "PageUp", KEY_PPAGE },
878 { "PageDown", KEY_NPAGE },
879 { "F1", KEY_F(1) },
880 { "F2", KEY_F(2) },
881 { "F3", KEY_F(3) },
882 { "F4", KEY_F(4) },
883 { "F5", KEY_F(5) },
884 { "F6", KEY_F(6) },
885 { "F7", KEY_F(7) },
886 { "F8", KEY_F(8) },
887 { "F9", KEY_F(9) },
888 { "F10", KEY_F(10) },
889 { "F11", KEY_F(11) },
890 { "F12", KEY_F(12) },
893 static int
894 get_key_value(const char *name)
896 int i;
898 for (i = 0; i < ARRAY_SIZE(key_table); i++)
899 if (!strcasecmp(key_table[i].name, name))
900 return key_table[i].value;
902 if (strlen(name) == 1 && isprint(*name))
903 return (int) *name;
905 return ERR;
908 static char *
909 get_key(enum request request)
911 static char buf[BUFSIZ];
912 static char key_char[] = "'X'";
913 size_t pos = 0;
914 char *sep = "";
915 int i;
917 buf[pos] = 0;
919 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
920 struct keybinding *keybinding = &default_keybindings[i];
921 char *seq = NULL;
922 int key;
924 if (keybinding->request != request)
925 continue;
927 for (key = 0; key < ARRAY_SIZE(key_table); key++)
928 if (key_table[key].value == keybinding->alias)
929 seq = key_table[key].name;
931 if (seq == NULL &&
932 keybinding->alias < 127 &&
933 isprint(keybinding->alias)) {
934 key_char[1] = (char) keybinding->alias;
935 seq = key_char;
938 if (!seq)
939 seq = "'?'";
941 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
942 return "Too many keybindings!";
943 sep = ", ";
946 return buf;
951 * User config file handling.
954 static struct int_map color_map[] = {
955 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
956 COLOR_MAP(DEFAULT),
957 COLOR_MAP(BLACK),
958 COLOR_MAP(BLUE),
959 COLOR_MAP(CYAN),
960 COLOR_MAP(GREEN),
961 COLOR_MAP(MAGENTA),
962 COLOR_MAP(RED),
963 COLOR_MAP(WHITE),
964 COLOR_MAP(YELLOW),
967 #define set_color(color, name) \
968 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
970 static struct int_map attr_map[] = {
971 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
972 ATTR_MAP(NORMAL),
973 ATTR_MAP(BLINK),
974 ATTR_MAP(BOLD),
975 ATTR_MAP(DIM),
976 ATTR_MAP(REVERSE),
977 ATTR_MAP(STANDOUT),
978 ATTR_MAP(UNDERLINE),
981 #define set_attribute(attr, name) \
982 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
984 static int config_lineno;
985 static bool config_errors;
986 static char *config_msg;
988 /* Wants: object fgcolor bgcolor [attr] */
989 static int
990 option_color_command(int argc, char *argv[])
992 struct line_info *info;
994 if (argc != 3 && argc != 4) {
995 config_msg = "Wrong number of arguments given to color command";
996 return ERR;
999 info = get_line_info(argv[0], strlen(argv[0]));
1000 if (!info) {
1001 config_msg = "Unknown color name";
1002 return ERR;
1005 if (set_color(&info->fg, argv[1]) == ERR ||
1006 set_color(&info->bg, argv[2]) == ERR) {
1007 config_msg = "Unknown color";
1008 return ERR;
1011 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1012 config_msg = "Unknown attribute";
1013 return ERR;
1016 return OK;
1019 /* Wants: name = value */
1020 static int
1021 option_set_command(int argc, char *argv[])
1023 if (argc != 3) {
1024 config_msg = "Wrong number of arguments given to set command";
1025 return ERR;
1028 if (strcmp(argv[1], "=")) {
1029 config_msg = "No value assigned";
1030 return ERR;
1033 if (!strcmp(argv[0], "show-rev-graph")) {
1034 opt_rev_graph = (!strcmp(argv[2], "1") ||
1035 !strcmp(argv[2], "true") ||
1036 !strcmp(argv[2], "yes"));
1037 return OK;
1040 if (!strcmp(argv[0], "line-number-interval")) {
1041 opt_num_interval = atoi(argv[2]);
1042 return OK;
1045 if (!strcmp(argv[0], "tab-size")) {
1046 opt_tab_size = atoi(argv[2]);
1047 return OK;
1050 if (!strcmp(argv[0], "commit-encoding")) {
1051 char *arg = argv[2];
1052 int delimiter = *arg;
1053 int i;
1055 switch (delimiter) {
1056 case '"':
1057 case '\'':
1058 for (arg++, i = 0; arg[i]; i++)
1059 if (arg[i] == delimiter) {
1060 arg[i] = 0;
1061 break;
1063 default:
1064 string_ncopy(opt_encoding, arg, strlen(arg));
1065 return OK;
1069 config_msg = "Unknown variable name";
1070 return ERR;
1073 /* Wants: mode request key */
1074 static int
1075 option_bind_command(int argc, char *argv[])
1077 enum request request;
1078 int keymap;
1079 int key;
1081 if (argc != 3) {
1082 config_msg = "Wrong number of arguments given to bind command";
1083 return ERR;
1086 if (set_keymap(&keymap, argv[0]) == ERR) {
1087 config_msg = "Unknown key map";
1088 return ERR;
1091 key = get_key_value(argv[1]);
1092 if (key == ERR) {
1093 config_msg = "Unknown key";
1094 return ERR;
1097 request = get_request(argv[2]);
1098 if (request == REQ_UNKNOWN) {
1099 config_msg = "Unknown request name";
1100 return ERR;
1103 add_keybinding(keymap, request, key);
1105 return OK;
1108 static int
1109 set_option(char *opt, char *value)
1111 char *argv[16];
1112 int valuelen;
1113 int argc = 0;
1115 /* Tokenize */
1116 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1117 argv[argc++] = value;
1119 value += valuelen;
1120 if (!*value)
1121 break;
1123 *value++ = 0;
1124 while (isspace(*value))
1125 value++;
1128 if (!strcmp(opt, "color"))
1129 return option_color_command(argc, argv);
1131 if (!strcmp(opt, "set"))
1132 return option_set_command(argc, argv);
1134 if (!strcmp(opt, "bind"))
1135 return option_bind_command(argc, argv);
1137 config_msg = "Unknown option command";
1138 return ERR;
1141 static int
1142 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1144 int status = OK;
1146 config_lineno++;
1147 config_msg = "Internal error";
1149 /* Check for comment markers, since read_properties() will
1150 * only ensure opt and value are split at first " \t". */
1151 optlen = strcspn(opt, "#");
1152 if (optlen == 0)
1153 return OK;
1155 if (opt[optlen] != 0) {
1156 config_msg = "No option value";
1157 status = ERR;
1159 } else {
1160 /* Look for comment endings in the value. */
1161 size_t len = strcspn(value, "#");
1163 if (len < valuelen) {
1164 valuelen = len;
1165 value[valuelen] = 0;
1168 status = set_option(opt, value);
1171 if (status == ERR) {
1172 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1173 config_lineno, (int) optlen, opt, config_msg);
1174 config_errors = TRUE;
1177 /* Always keep going if errors are encountered. */
1178 return OK;
1181 static int
1182 load_options(void)
1184 char *home = getenv("HOME");
1185 char buf[SIZEOF_STR];
1186 FILE *file;
1188 config_lineno = 0;
1189 config_errors = FALSE;
1191 if (!home || !string_format(buf, "%s/.tigrc", home))
1192 return ERR;
1194 /* It's ok that the file doesn't exist. */
1195 file = fopen(buf, "r");
1196 if (!file)
1197 return OK;
1199 if (read_properties(file, " \t", read_option) == ERR ||
1200 config_errors == TRUE)
1201 fprintf(stderr, "Errors while loading %s.\n", buf);
1203 return OK;
1208 * The viewer
1211 struct view;
1212 struct view_ops;
1214 /* The display array of active views and the index of the current view. */
1215 static struct view *display[2];
1216 static unsigned int current_view;
1218 /* Reading from the prompt? */
1219 static bool input_mode = FALSE;
1221 #define foreach_displayed_view(view, i) \
1222 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1224 #define displayed_views() (display[1] != NULL ? 2 : 1)
1226 /* Current head and commit ID */
1227 static char ref_blob[SIZEOF_REF] = "";
1228 static char ref_commit[SIZEOF_REF] = "HEAD";
1229 static char ref_head[SIZEOF_REF] = "HEAD";
1231 struct view {
1232 const char *name; /* View name */
1233 const char *cmd_fmt; /* Default command line format */
1234 const char *cmd_env; /* Command line set via environment */
1235 const char *id; /* Points to either of ref_{head,commit,blob} */
1237 struct view_ops *ops; /* View operations */
1239 enum keymap keymap; /* What keymap does this view have */
1241 char cmd[SIZEOF_STR]; /* Command buffer */
1242 char ref[SIZEOF_REF]; /* Hovered commit reference */
1243 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1245 int height, width; /* The width and height of the main window */
1246 WINDOW *win; /* The main window */
1247 WINDOW *title; /* The title window living below the main window */
1249 /* Navigation */
1250 unsigned long offset; /* Offset of the window top */
1251 unsigned long lineno; /* Current line number */
1253 /* Searching */
1254 char grep[SIZEOF_STR]; /* Search string */
1255 regex_t *regex; /* Pre-compiled regex */
1257 /* If non-NULL, points to the view that opened this view. If this view
1258 * is closed tig will switch back to the parent view. */
1259 struct view *parent;
1261 /* Buffering */
1262 unsigned long lines; /* Total number of lines */
1263 struct line *line; /* Line index */
1264 unsigned long line_size;/* Total number of allocated lines */
1265 unsigned int digits; /* Number of digits in the lines member. */
1267 /* Loading */
1268 FILE *pipe;
1269 time_t start_time;
1272 struct view_ops {
1273 /* What type of content being displayed. Used in the title bar. */
1274 const char *type;
1275 /* Open and reads in all view content. */
1276 bool (*open)(struct view *view);
1277 /* Read one line; updates view->line. */
1278 bool (*read)(struct view *view, char *data);
1279 /* Draw one line; @lineno must be < view->height. */
1280 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1281 /* Depending on view handle a special requests. */
1282 enum request (*request)(struct view *view, enum request request, struct line *line);
1283 /* Search for regex in a line. */
1284 bool (*grep)(struct view *view, struct line *line);
1285 /* Select line */
1286 void (*select)(struct view *view, struct line *line);
1289 static struct view_ops pager_ops;
1290 static struct view_ops main_ops;
1291 static struct view_ops tree_ops;
1292 static struct view_ops blob_ops;
1293 static struct view_ops help_ops;
1294 static struct view_ops status_ops;
1295 static struct view_ops stage_ops;
1297 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1298 { name, cmd, #env, ref, ops, map}
1300 #define VIEW_(id, name, ops, ref) \
1301 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1304 static struct view views[] = {
1305 VIEW_(MAIN, "main", &main_ops, ref_head),
1306 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1307 VIEW_(LOG, "log", &pager_ops, ref_head),
1308 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1309 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1310 VIEW_(HELP, "help", &help_ops, ""),
1311 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1312 VIEW_(STATUS, "status", &status_ops, ""),
1313 VIEW_(STAGE, "stage", &stage_ops, ""),
1316 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1318 #define foreach_view(view, i) \
1319 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1321 #define view_is_displayed(view) \
1322 (view == display[0] || view == display[1])
1324 static bool
1325 draw_view_line(struct view *view, unsigned int lineno)
1327 struct line *line;
1328 bool selected = (view->offset + lineno == view->lineno);
1329 bool draw_ok;
1331 assert(view_is_displayed(view));
1333 if (view->offset + lineno >= view->lines)
1334 return FALSE;
1336 line = &view->line[view->offset + lineno];
1338 if (selected) {
1339 line->selected = TRUE;
1340 view->ops->select(view, line);
1341 } else if (line->selected) {
1342 line->selected = FALSE;
1343 wmove(view->win, lineno, 0);
1344 wclrtoeol(view->win);
1347 scrollok(view->win, FALSE);
1348 draw_ok = view->ops->draw(view, line, lineno, selected);
1349 scrollok(view->win, TRUE);
1351 return draw_ok;
1354 static void
1355 redraw_view_from(struct view *view, int lineno)
1357 assert(0 <= lineno && lineno < view->height);
1359 for (; lineno < view->height; lineno++) {
1360 if (!draw_view_line(view, lineno))
1361 break;
1364 redrawwin(view->win);
1365 if (input_mode)
1366 wnoutrefresh(view->win);
1367 else
1368 wrefresh(view->win);
1371 static void
1372 redraw_view(struct view *view)
1374 wclear(view->win);
1375 redraw_view_from(view, 0);
1379 static void
1380 update_view_title(struct view *view)
1382 char buf[SIZEOF_STR];
1383 char state[SIZEOF_STR];
1384 size_t bufpos = 0, statelen = 0;
1386 assert(view_is_displayed(view));
1388 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1389 unsigned int view_lines = view->offset + view->height;
1390 unsigned int lines = view->lines
1391 ? MIN(view_lines, view->lines) * 100 / view->lines
1392 : 0;
1394 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1395 view->ops->type,
1396 view->lineno + 1,
1397 view->lines,
1398 lines);
1400 if (view->pipe) {
1401 time_t secs = time(NULL) - view->start_time;
1403 /* Three git seconds are a long time ... */
1404 if (secs > 2)
1405 string_format_from(state, &statelen, " %lds", secs);
1409 string_format_from(buf, &bufpos, "[%s]", view->name);
1410 if (*view->ref && bufpos < view->width) {
1411 size_t refsize = strlen(view->ref);
1412 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1414 if (minsize < view->width)
1415 refsize = view->width - minsize + 7;
1416 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1419 if (statelen && bufpos < view->width) {
1420 string_format_from(buf, &bufpos, " %s", state);
1423 if (view == display[current_view])
1424 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1425 else
1426 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1428 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1429 wclrtoeol(view->title);
1430 wmove(view->title, 0, view->width - 1);
1432 if (input_mode)
1433 wnoutrefresh(view->title);
1434 else
1435 wrefresh(view->title);
1438 static void
1439 resize_display(void)
1441 int offset, i;
1442 struct view *base = display[0];
1443 struct view *view = display[1] ? display[1] : display[0];
1445 /* Setup window dimensions */
1447 getmaxyx(stdscr, base->height, base->width);
1449 /* Make room for the status window. */
1450 base->height -= 1;
1452 if (view != base) {
1453 /* Horizontal split. */
1454 view->width = base->width;
1455 view->height = SCALE_SPLIT_VIEW(base->height);
1456 base->height -= view->height;
1458 /* Make room for the title bar. */
1459 view->height -= 1;
1462 /* Make room for the title bar. */
1463 base->height -= 1;
1465 offset = 0;
1467 foreach_displayed_view (view, i) {
1468 if (!view->win) {
1469 view->win = newwin(view->height, 0, offset, 0);
1470 if (!view->win)
1471 die("Failed to create %s view", view->name);
1473 scrollok(view->win, TRUE);
1475 view->title = newwin(1, 0, offset + view->height, 0);
1476 if (!view->title)
1477 die("Failed to create title window");
1479 } else {
1480 wresize(view->win, view->height, view->width);
1481 mvwin(view->win, offset, 0);
1482 mvwin(view->title, offset + view->height, 0);
1485 offset += view->height + 1;
1489 static void
1490 redraw_display(void)
1492 struct view *view;
1493 int i;
1495 foreach_displayed_view (view, i) {
1496 redraw_view(view);
1497 update_view_title(view);
1501 static void
1502 update_display_cursor(struct view *view)
1504 /* Move the cursor to the right-most column of the cursor line.
1506 * XXX: This could turn out to be a bit expensive, but it ensures that
1507 * the cursor does not jump around. */
1508 if (view->lines) {
1509 wmove(view->win, view->lineno - view->offset, view->width - 1);
1510 wrefresh(view->win);
1515 * Navigation
1518 /* Scrolling backend */
1519 static void
1520 do_scroll_view(struct view *view, int lines)
1522 bool redraw_current_line = FALSE;
1524 /* The rendering expects the new offset. */
1525 view->offset += lines;
1527 assert(0 <= view->offset && view->offset < view->lines);
1528 assert(lines);
1530 /* Move current line into the view. */
1531 if (view->lineno < view->offset) {
1532 view->lineno = view->offset;
1533 redraw_current_line = TRUE;
1534 } else if (view->lineno >= view->offset + view->height) {
1535 view->lineno = view->offset + view->height - 1;
1536 redraw_current_line = TRUE;
1539 assert(view->offset <= view->lineno && view->lineno < view->lines);
1541 /* Redraw the whole screen if scrolling is pointless. */
1542 if (view->height < ABS(lines)) {
1543 redraw_view(view);
1545 } else {
1546 int line = lines > 0 ? view->height - lines : 0;
1547 int end = line + ABS(lines);
1549 wscrl(view->win, lines);
1551 for (; line < end; line++) {
1552 if (!draw_view_line(view, line))
1553 break;
1556 if (redraw_current_line)
1557 draw_view_line(view, view->lineno - view->offset);
1560 redrawwin(view->win);
1561 wrefresh(view->win);
1562 report("");
1565 /* Scroll frontend */
1566 static void
1567 scroll_view(struct view *view, enum request request)
1569 int lines = 1;
1571 assert(view_is_displayed(view));
1573 switch (request) {
1574 case REQ_SCROLL_PAGE_DOWN:
1575 lines = view->height;
1576 case REQ_SCROLL_LINE_DOWN:
1577 if (view->offset + lines > view->lines)
1578 lines = view->lines - view->offset;
1580 if (lines == 0 || view->offset + view->height >= view->lines) {
1581 report("Cannot scroll beyond the last line");
1582 return;
1584 break;
1586 case REQ_SCROLL_PAGE_UP:
1587 lines = view->height;
1588 case REQ_SCROLL_LINE_UP:
1589 if (lines > view->offset)
1590 lines = view->offset;
1592 if (lines == 0) {
1593 report("Cannot scroll beyond the first line");
1594 return;
1597 lines = -lines;
1598 break;
1600 default:
1601 die("request %d not handled in switch", request);
1604 do_scroll_view(view, lines);
1607 /* Cursor moving */
1608 static void
1609 move_view(struct view *view, enum request request)
1611 int scroll_steps = 0;
1612 int steps;
1614 switch (request) {
1615 case REQ_MOVE_FIRST_LINE:
1616 steps = -view->lineno;
1617 break;
1619 case REQ_MOVE_LAST_LINE:
1620 steps = view->lines - view->lineno - 1;
1621 break;
1623 case REQ_MOVE_PAGE_UP:
1624 steps = view->height > view->lineno
1625 ? -view->lineno : -view->height;
1626 break;
1628 case REQ_MOVE_PAGE_DOWN:
1629 steps = view->lineno + view->height >= view->lines
1630 ? view->lines - view->lineno - 1 : view->height;
1631 break;
1633 case REQ_MOVE_UP:
1634 steps = -1;
1635 break;
1637 case REQ_MOVE_DOWN:
1638 steps = 1;
1639 break;
1641 default:
1642 die("request %d not handled in switch", request);
1645 if (steps <= 0 && view->lineno == 0) {
1646 report("Cannot move beyond the first line");
1647 return;
1649 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1650 report("Cannot move beyond the last line");
1651 return;
1654 /* Move the current line */
1655 view->lineno += steps;
1656 assert(0 <= view->lineno && view->lineno < view->lines);
1658 /* Check whether the view needs to be scrolled */
1659 if (view->lineno < view->offset ||
1660 view->lineno >= view->offset + view->height) {
1661 scroll_steps = steps;
1662 if (steps < 0 && -steps > view->offset) {
1663 scroll_steps = -view->offset;
1665 } else if (steps > 0) {
1666 if (view->lineno == view->lines - 1 &&
1667 view->lines > view->height) {
1668 scroll_steps = view->lines - view->offset - 1;
1669 if (scroll_steps >= view->height)
1670 scroll_steps -= view->height - 1;
1675 if (!view_is_displayed(view)) {
1676 view->offset += scroll_steps;
1677 assert(0 <= view->offset && view->offset < view->lines);
1678 view->ops->select(view, &view->line[view->lineno]);
1679 return;
1682 /* Repaint the old "current" line if we be scrolling */
1683 if (ABS(steps) < view->height)
1684 draw_view_line(view, view->lineno - steps - view->offset);
1686 if (scroll_steps) {
1687 do_scroll_view(view, scroll_steps);
1688 return;
1691 /* Draw the current line */
1692 draw_view_line(view, view->lineno - view->offset);
1694 redrawwin(view->win);
1695 wrefresh(view->win);
1696 report("");
1701 * Searching
1704 static void search_view(struct view *view, enum request request);
1706 static bool
1707 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1709 assert(view_is_displayed(view));
1711 if (!view->ops->grep(view, line))
1712 return FALSE;
1714 if (lineno - view->offset >= view->height) {
1715 view->offset = lineno;
1716 view->lineno = lineno;
1717 redraw_view(view);
1719 } else {
1720 unsigned long old_lineno = view->lineno - view->offset;
1722 view->lineno = lineno;
1723 draw_view_line(view, old_lineno);
1725 draw_view_line(view, view->lineno - view->offset);
1726 redrawwin(view->win);
1727 wrefresh(view->win);
1730 report("Line %ld matches '%s'", lineno + 1, view->grep);
1731 return TRUE;
1734 static void
1735 find_next(struct view *view, enum request request)
1737 unsigned long lineno = view->lineno;
1738 int direction;
1740 if (!*view->grep) {
1741 if (!*opt_search)
1742 report("No previous search");
1743 else
1744 search_view(view, request);
1745 return;
1748 switch (request) {
1749 case REQ_SEARCH:
1750 case REQ_FIND_NEXT:
1751 direction = 1;
1752 break;
1754 case REQ_SEARCH_BACK:
1755 case REQ_FIND_PREV:
1756 direction = -1;
1757 break;
1759 default:
1760 return;
1763 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1764 lineno += direction;
1766 /* Note, lineno is unsigned long so will wrap around in which case it
1767 * will become bigger than view->lines. */
1768 for (; lineno < view->lines; lineno += direction) {
1769 struct line *line = &view->line[lineno];
1771 if (find_next_line(view, lineno, line))
1772 return;
1775 report("No match found for '%s'", view->grep);
1778 static void
1779 search_view(struct view *view, enum request request)
1781 int regex_err;
1783 if (view->regex) {
1784 regfree(view->regex);
1785 *view->grep = 0;
1786 } else {
1787 view->regex = calloc(1, sizeof(*view->regex));
1788 if (!view->regex)
1789 return;
1792 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1793 if (regex_err != 0) {
1794 char buf[SIZEOF_STR] = "unknown error";
1796 regerror(regex_err, view->regex, buf, sizeof(buf));
1797 report("Search failed: %s", buf);
1798 return;
1801 string_copy(view->grep, opt_search);
1803 find_next(view, request);
1807 * Incremental updating
1810 static void
1811 end_update(struct view *view)
1813 if (!view->pipe)
1814 return;
1815 set_nonblocking_input(FALSE);
1816 if (view->pipe == stdin)
1817 fclose(view->pipe);
1818 else
1819 pclose(view->pipe);
1820 view->pipe = NULL;
1823 static bool
1824 begin_update(struct view *view)
1826 if (view->pipe)
1827 end_update(view);
1829 if (opt_cmd[0]) {
1830 string_copy(view->cmd, opt_cmd);
1831 opt_cmd[0] = 0;
1832 /* When running random commands, initially show the
1833 * command in the title. However, it maybe later be
1834 * overwritten if a commit line is selected. */
1835 if (view == VIEW(REQ_VIEW_PAGER))
1836 string_copy(view->ref, view->cmd);
1837 else
1838 view->ref[0] = 0;
1840 } else if (view == VIEW(REQ_VIEW_TREE)) {
1841 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1842 char path[SIZEOF_STR];
1844 if (strcmp(view->vid, view->id))
1845 opt_path[0] = path[0] = 0;
1846 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1847 return FALSE;
1849 if (!string_format(view->cmd, format, view->id, path))
1850 return FALSE;
1852 } else {
1853 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1854 const char *id = view->id;
1856 if (!string_format(view->cmd, format, id, id, id, id, id))
1857 return FALSE;
1859 /* Put the current ref_* value to the view title ref
1860 * member. This is needed by the blob view. Most other
1861 * views sets it automatically after loading because the
1862 * first line is a commit line. */
1863 string_copy_rev(view->ref, view->id);
1866 /* Special case for the pager view. */
1867 if (opt_pipe) {
1868 view->pipe = opt_pipe;
1869 opt_pipe = NULL;
1870 } else {
1871 view->pipe = popen(view->cmd, "r");
1874 if (!view->pipe)
1875 return FALSE;
1877 set_nonblocking_input(TRUE);
1879 view->offset = 0;
1880 view->lines = 0;
1881 view->lineno = 0;
1882 string_copy_rev(view->vid, view->id);
1884 if (view->line) {
1885 int i;
1887 for (i = 0; i < view->lines; i++)
1888 if (view->line[i].data)
1889 free(view->line[i].data);
1891 free(view->line);
1892 view->line = NULL;
1895 view->start_time = time(NULL);
1897 return TRUE;
1900 static struct line *
1901 realloc_lines(struct view *view, size_t line_size)
1903 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1905 if (!tmp)
1906 return NULL;
1908 view->line = tmp;
1909 view->line_size = line_size;
1910 return view->line;
1913 static bool
1914 update_view(struct view *view)
1916 char in_buffer[BUFSIZ];
1917 char out_buffer[BUFSIZ * 2];
1918 char *line;
1919 /* The number of lines to read. If too low it will cause too much
1920 * redrawing (and possible flickering), if too high responsiveness
1921 * will suffer. */
1922 unsigned long lines = view->height;
1923 int redraw_from = -1;
1925 if (!view->pipe)
1926 return TRUE;
1928 /* Only redraw if lines are visible. */
1929 if (view->offset + view->height >= view->lines)
1930 redraw_from = view->lines - view->offset;
1932 /* FIXME: This is probably not perfect for backgrounded views. */
1933 if (!realloc_lines(view, view->lines + lines))
1934 goto alloc_error;
1936 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1937 size_t linelen = strlen(line);
1939 if (linelen)
1940 line[linelen - 1] = 0;
1942 if (opt_iconv != ICONV_NONE) {
1943 ICONV_CONST char *inbuf = line;
1944 size_t inlen = linelen;
1946 char *outbuf = out_buffer;
1947 size_t outlen = sizeof(out_buffer);
1949 size_t ret;
1951 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1952 if (ret != (size_t) -1) {
1953 line = out_buffer;
1954 linelen = strlen(out_buffer);
1958 if (!view->ops->read(view, line))
1959 goto alloc_error;
1961 if (lines-- == 1)
1962 break;
1966 int digits;
1968 lines = view->lines;
1969 for (digits = 0; lines; digits++)
1970 lines /= 10;
1972 /* Keep the displayed view in sync with line number scaling. */
1973 if (digits != view->digits) {
1974 view->digits = digits;
1975 redraw_from = 0;
1979 if (!view_is_displayed(view))
1980 goto check_pipe;
1982 if (view == VIEW(REQ_VIEW_TREE)) {
1983 /* Clear the view and redraw everything since the tree sorting
1984 * might have rearranged things. */
1985 redraw_view(view);
1987 } else if (redraw_from >= 0) {
1988 /* If this is an incremental update, redraw the previous line
1989 * since for commits some members could have changed when
1990 * loading the main view. */
1991 if (redraw_from > 0)
1992 redraw_from--;
1994 /* Since revision graph visualization requires knowledge
1995 * about the parent commit, it causes a further one-off
1996 * needed to be redrawn for incremental updates. */
1997 if (redraw_from > 0 && opt_rev_graph)
1998 redraw_from--;
2000 /* Incrementally draw avoids flickering. */
2001 redraw_view_from(view, redraw_from);
2004 /* Update the title _after_ the redraw so that if the redraw picks up a
2005 * commit reference in view->ref it'll be available here. */
2006 update_view_title(view);
2008 check_pipe:
2009 if (ferror(view->pipe)) {
2010 report("Failed to read: %s", strerror(errno));
2011 goto end;
2013 } else if (feof(view->pipe)) {
2014 report("");
2015 goto end;
2018 return TRUE;
2020 alloc_error:
2021 report("Allocation failure");
2023 end:
2024 view->ops->read(view, NULL);
2025 end_update(view);
2026 return FALSE;
2029 static struct line *
2030 add_line_data(struct view *view, void *data, enum line_type type)
2032 struct line *line = &view->line[view->lines++];
2034 memset(line, 0, sizeof(*line));
2035 line->type = type;
2036 line->data = data;
2038 return line;
2041 static struct line *
2042 add_line_text(struct view *view, char *data, enum line_type type)
2044 if (data)
2045 data = strdup(data);
2047 return data ? add_line_data(view, data, type) : NULL;
2052 * View opening
2055 enum open_flags {
2056 OPEN_DEFAULT = 0, /* Use default view switching. */
2057 OPEN_SPLIT = 1, /* Split current view. */
2058 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2059 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2062 static void
2063 open_view(struct view *prev, enum request request, enum open_flags flags)
2065 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2066 bool split = !!(flags & OPEN_SPLIT);
2067 bool reload = !!(flags & OPEN_RELOAD);
2068 struct view *view = VIEW(request);
2069 int nviews = displayed_views();
2070 struct view *base_view = display[0];
2072 if (view == prev && nviews == 1 && !reload) {
2073 report("Already in %s view", view->name);
2074 return;
2077 if (view->ops->open) {
2078 if (!view->ops->open(view)) {
2079 report("Failed to load %s view", view->name);
2080 return;
2083 } else if ((reload || strcmp(view->vid, view->id)) &&
2084 !begin_update(view)) {
2085 report("Failed to load %s view", view->name);
2086 return;
2089 if (split) {
2090 display[1] = view;
2091 if (!backgrounded)
2092 current_view = 1;
2093 } else {
2094 /* Maximize the current view. */
2095 memset(display, 0, sizeof(display));
2096 current_view = 0;
2097 display[current_view] = view;
2100 /* Resize the view when switching between split- and full-screen,
2101 * or when switching between two different full-screen views. */
2102 if (nviews != displayed_views() ||
2103 (nviews == 1 && base_view != display[0]))
2104 resize_display();
2106 if (split && prev->lineno - prev->offset >= prev->height) {
2107 /* Take the title line into account. */
2108 int lines = prev->lineno - prev->offset - prev->height + 1;
2110 /* Scroll the view that was split if the current line is
2111 * outside the new limited view. */
2112 do_scroll_view(prev, lines);
2115 if (prev && view != prev) {
2116 if (split && !backgrounded) {
2117 /* "Blur" the previous view. */
2118 update_view_title(prev);
2121 view->parent = prev;
2124 if (view->pipe && view->lines == 0) {
2125 /* Clear the old view and let the incremental updating refill
2126 * the screen. */
2127 wclear(view->win);
2128 report("");
2129 } else {
2130 redraw_view(view);
2131 report("");
2134 /* If the view is backgrounded the above calls to report()
2135 * won't redraw the view title. */
2136 if (backgrounded)
2137 update_view_title(view);
2140 static void
2141 open_editor(struct view *view, char *file)
2143 char cmd[SIZEOF_STR];
2144 char file_sq[SIZEOF_STR];
2145 char *editor;
2147 editor = getenv("GIT_EDITOR");
2148 if (!editor && *opt_editor)
2149 editor = opt_editor;
2150 if (!editor)
2151 editor = getenv("VISUAL");
2152 if (!editor)
2153 editor = getenv("EDITOR");
2154 if (!editor)
2155 editor = "vi";
2157 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2158 string_format(cmd, "%s %s", editor, file_sq)) {
2159 def_prog_mode(); /* save current tty modes */
2160 endwin(); /* restore original tty modes */
2161 system(cmd);
2162 reset_prog_mode();
2163 redraw_display();
2168 * User request switch noodle
2171 static int
2172 view_driver(struct view *view, enum request request)
2174 int i;
2176 if (view && view->lines) {
2177 request = view->ops->request(view, request, &view->line[view->lineno]);
2178 if (request == REQ_NONE)
2179 return TRUE;
2182 switch (request) {
2183 case REQ_MOVE_UP:
2184 case REQ_MOVE_DOWN:
2185 case REQ_MOVE_PAGE_UP:
2186 case REQ_MOVE_PAGE_DOWN:
2187 case REQ_MOVE_FIRST_LINE:
2188 case REQ_MOVE_LAST_LINE:
2189 move_view(view, request);
2190 break;
2192 case REQ_SCROLL_LINE_DOWN:
2193 case REQ_SCROLL_LINE_UP:
2194 case REQ_SCROLL_PAGE_DOWN:
2195 case REQ_SCROLL_PAGE_UP:
2196 scroll_view(view, request);
2197 break;
2199 case REQ_VIEW_BLOB:
2200 if (!ref_blob[0]) {
2201 report("No file chosen, press %s to open tree view",
2202 get_key(REQ_VIEW_TREE));
2203 break;
2205 open_view(view, request, OPEN_DEFAULT);
2206 break;
2208 case REQ_VIEW_PAGER:
2209 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2210 report("No pager content, press %s to run command from prompt",
2211 get_key(REQ_PROMPT));
2212 break;
2214 open_view(view, request, OPEN_DEFAULT);
2215 break;
2217 case REQ_VIEW_STAGE:
2218 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2219 report("No stage content, press %s to open the status view and choose file",
2220 get_key(REQ_VIEW_STATUS));
2221 break;
2223 open_view(view, request, OPEN_DEFAULT);
2224 break;
2226 case REQ_VIEW_MAIN:
2227 case REQ_VIEW_DIFF:
2228 case REQ_VIEW_LOG:
2229 case REQ_VIEW_TREE:
2230 case REQ_VIEW_HELP:
2231 case REQ_VIEW_STATUS:
2232 open_view(view, request, OPEN_DEFAULT);
2233 break;
2235 case REQ_NEXT:
2236 case REQ_PREVIOUS:
2237 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2239 if ((view == VIEW(REQ_VIEW_DIFF) &&
2240 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2241 (view == VIEW(REQ_VIEW_STAGE) &&
2242 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2243 (view == VIEW(REQ_VIEW_BLOB) &&
2244 view->parent == VIEW(REQ_VIEW_TREE))) {
2245 int line;
2247 view = view->parent;
2248 line = view->lineno;
2249 move_view(view, request);
2250 if (view_is_displayed(view))
2251 update_view_title(view);
2252 if (line != view->lineno)
2253 view->ops->request(view, REQ_ENTER,
2254 &view->line[view->lineno]);
2256 } else {
2257 move_view(view, request);
2259 break;
2261 case REQ_VIEW_NEXT:
2263 int nviews = displayed_views();
2264 int next_view = (current_view + 1) % nviews;
2266 if (next_view == current_view) {
2267 report("Only one view is displayed");
2268 break;
2271 current_view = next_view;
2272 /* Blur out the title of the previous view. */
2273 update_view_title(view);
2274 report("");
2275 break;
2277 case REQ_TOGGLE_LINENO:
2278 opt_line_number = !opt_line_number;
2279 redraw_display();
2280 break;
2282 case REQ_TOGGLE_REV_GRAPH:
2283 opt_rev_graph = !opt_rev_graph;
2284 redraw_display();
2285 break;
2287 case REQ_PROMPT:
2288 /* Always reload^Wrerun commands from the prompt. */
2289 open_view(view, opt_request, OPEN_RELOAD);
2290 break;
2292 case REQ_SEARCH:
2293 case REQ_SEARCH_BACK:
2294 search_view(view, request);
2295 break;
2297 case REQ_FIND_NEXT:
2298 case REQ_FIND_PREV:
2299 find_next(view, request);
2300 break;
2302 case REQ_STOP_LOADING:
2303 for (i = 0; i < ARRAY_SIZE(views); i++) {
2304 view = &views[i];
2305 if (view->pipe)
2306 report("Stopped loading the %s view", view->name),
2307 end_update(view);
2309 break;
2311 case REQ_SHOW_VERSION:
2312 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2313 return TRUE;
2315 case REQ_SCREEN_RESIZE:
2316 resize_display();
2317 /* Fall-through */
2318 case REQ_SCREEN_REDRAW:
2319 redraw_display();
2320 break;
2322 case REQ_EDIT:
2323 report("Nothing to edit");
2324 break;
2326 case REQ_ENTER:
2327 report("Nothing to enter");
2328 break;
2330 case REQ_NONE:
2331 doupdate();
2332 return TRUE;
2334 case REQ_VIEW_CLOSE:
2335 /* XXX: Mark closed views by letting view->parent point to the
2336 * view itself. Parents to closed view should never be
2337 * followed. */
2338 if (view->parent &&
2339 view->parent->parent != view->parent) {
2340 memset(display, 0, sizeof(display));
2341 current_view = 0;
2342 display[current_view] = view->parent;
2343 view->parent = view;
2344 resize_display();
2345 redraw_display();
2346 break;
2348 /* Fall-through */
2349 case REQ_QUIT:
2350 return FALSE;
2352 default:
2353 /* An unknown key will show most commonly used commands. */
2354 report("Unknown key, press 'h' for help");
2355 return TRUE;
2358 return TRUE;
2363 * Pager backend
2366 static bool
2367 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2369 char *text = line->data;
2370 enum line_type type = line->type;
2371 int textlen = strlen(text);
2372 int attr;
2374 wmove(view->win, lineno, 0);
2376 if (selected) {
2377 type = LINE_CURSOR;
2378 wchgat(view->win, -1, 0, type, NULL);
2381 attr = get_line_attr(type);
2382 wattrset(view->win, attr);
2384 if (opt_line_number || opt_tab_size < TABSIZE) {
2385 static char spaces[] = " ";
2386 int col_offset = 0, col = 0;
2388 if (opt_line_number) {
2389 unsigned long real_lineno = view->offset + lineno + 1;
2391 if (real_lineno == 1 ||
2392 (real_lineno % opt_num_interval) == 0) {
2393 wprintw(view->win, "%.*d", view->digits, real_lineno);
2395 } else {
2396 waddnstr(view->win, spaces,
2397 MIN(view->digits, STRING_SIZE(spaces)));
2399 waddstr(view->win, ": ");
2400 col_offset = view->digits + 2;
2403 while (text && col_offset + col < view->width) {
2404 int cols_max = view->width - col_offset - col;
2405 char *pos = text;
2406 int cols;
2408 if (*text == '\t') {
2409 text++;
2410 assert(sizeof(spaces) > TABSIZE);
2411 pos = spaces;
2412 cols = opt_tab_size - (col % opt_tab_size);
2414 } else {
2415 text = strchr(text, '\t');
2416 cols = line ? text - pos : strlen(pos);
2419 waddnstr(view->win, pos, MIN(cols, cols_max));
2420 col += cols;
2423 } else {
2424 int col = 0, pos = 0;
2426 for (; pos < textlen && col < view->width; pos++, col++)
2427 if (text[pos] == '\t')
2428 col += TABSIZE - (col % TABSIZE) - 1;
2430 waddnstr(view->win, text, pos);
2433 return TRUE;
2436 static bool
2437 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2439 char refbuf[SIZEOF_STR];
2440 char *ref = NULL;
2441 FILE *pipe;
2443 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2444 return TRUE;
2446 pipe = popen(refbuf, "r");
2447 if (!pipe)
2448 return TRUE;
2450 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2451 ref = chomp_string(ref);
2452 pclose(pipe);
2454 if (!ref || !*ref)
2455 return TRUE;
2457 /* This is the only fatal call, since it can "corrupt" the buffer. */
2458 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2459 return FALSE;
2461 return TRUE;
2464 static void
2465 add_pager_refs(struct view *view, struct line *line)
2467 char buf[SIZEOF_STR];
2468 char *commit_id = line->data + STRING_SIZE("commit ");
2469 struct ref **refs;
2470 size_t bufpos = 0, refpos = 0;
2471 const char *sep = "Refs: ";
2472 bool is_tag = FALSE;
2474 assert(line->type == LINE_COMMIT);
2476 refs = get_refs(commit_id);
2477 if (!refs) {
2478 if (view == VIEW(REQ_VIEW_DIFF))
2479 goto try_add_describe_ref;
2480 return;
2483 do {
2484 struct ref *ref = refs[refpos];
2485 char *fmt = ref->tag ? "%s[%s]" :
2486 ref->remote ? "%s<%s>" : "%s%s";
2488 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2489 return;
2490 sep = ", ";
2491 if (ref->tag)
2492 is_tag = TRUE;
2493 } while (refs[refpos++]->next);
2495 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2496 try_add_describe_ref:
2497 /* Add <tag>-g<commit_id> "fake" reference. */
2498 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2499 return;
2502 if (bufpos == 0)
2503 return;
2505 if (!realloc_lines(view, view->line_size + 1))
2506 return;
2508 add_line_text(view, buf, LINE_PP_REFS);
2511 static bool
2512 pager_read(struct view *view, char *data)
2514 struct line *line;
2516 if (!data)
2517 return TRUE;
2519 line = add_line_text(view, data, get_line_type(data));
2520 if (!line)
2521 return FALSE;
2523 if (line->type == LINE_COMMIT &&
2524 (view == VIEW(REQ_VIEW_DIFF) ||
2525 view == VIEW(REQ_VIEW_LOG)))
2526 add_pager_refs(view, line);
2528 return TRUE;
2531 static enum request
2532 pager_request(struct view *view, enum request request, struct line *line)
2534 int split = 0;
2536 if (request != REQ_ENTER)
2537 return request;
2539 if (line->type == LINE_COMMIT &&
2540 (view == VIEW(REQ_VIEW_LOG) ||
2541 view == VIEW(REQ_VIEW_PAGER))) {
2542 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2543 split = 1;
2546 /* Always scroll the view even if it was split. That way
2547 * you can use Enter to scroll through the log view and
2548 * split open each commit diff. */
2549 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2551 /* FIXME: A minor workaround. Scrolling the view will call report("")
2552 * but if we are scrolling a non-current view this won't properly
2553 * update the view title. */
2554 if (split)
2555 update_view_title(view);
2557 return REQ_NONE;
2560 static bool
2561 pager_grep(struct view *view, struct line *line)
2563 regmatch_t pmatch;
2564 char *text = line->data;
2566 if (!*text)
2567 return FALSE;
2569 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2570 return FALSE;
2572 return TRUE;
2575 static void
2576 pager_select(struct view *view, struct line *line)
2578 if (line->type == LINE_COMMIT) {
2579 char *text = line->data + STRING_SIZE("commit ");
2581 if (view != VIEW(REQ_VIEW_PAGER))
2582 string_copy_rev(view->ref, text);
2583 string_copy_rev(ref_commit, text);
2587 static struct view_ops pager_ops = {
2588 "line",
2589 NULL,
2590 pager_read,
2591 pager_draw,
2592 pager_request,
2593 pager_grep,
2594 pager_select,
2599 * Help backend
2602 static bool
2603 help_open(struct view *view)
2605 char buf[BUFSIZ];
2606 int lines = ARRAY_SIZE(req_info) + 2;
2607 int i;
2609 if (view->lines > 0)
2610 return TRUE;
2612 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2613 if (!req_info[i].request)
2614 lines++;
2616 view->line = calloc(lines, sizeof(*view->line));
2617 if (!view->line)
2618 return FALSE;
2620 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2622 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2623 char *key;
2625 if (!req_info[i].request) {
2626 add_line_text(view, "", LINE_DEFAULT);
2627 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2628 continue;
2631 key = get_key(req_info[i].request);
2632 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2633 continue;
2635 add_line_text(view, buf, LINE_DEFAULT);
2638 return TRUE;
2641 static struct view_ops help_ops = {
2642 "line",
2643 help_open,
2644 NULL,
2645 pager_draw,
2646 pager_request,
2647 pager_grep,
2648 pager_select,
2653 * Tree backend
2656 struct tree_stack_entry {
2657 struct tree_stack_entry *prev; /* Entry below this in the stack */
2658 unsigned long lineno; /* Line number to restore */
2659 char *name; /* Position of name in opt_path */
2662 /* The top of the path stack. */
2663 static struct tree_stack_entry *tree_stack = NULL;
2664 unsigned long tree_lineno = 0;
2666 static void
2667 pop_tree_stack_entry(void)
2669 struct tree_stack_entry *entry = tree_stack;
2671 tree_lineno = entry->lineno;
2672 entry->name[0] = 0;
2673 tree_stack = entry->prev;
2674 free(entry);
2677 static void
2678 push_tree_stack_entry(char *name, unsigned long lineno)
2680 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2681 size_t pathlen = strlen(opt_path);
2683 if (!entry)
2684 return;
2686 entry->prev = tree_stack;
2687 entry->name = opt_path + pathlen;
2688 tree_stack = entry;
2690 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2691 pop_tree_stack_entry();
2692 return;
2695 /* Move the current line to the first tree entry. */
2696 tree_lineno = 1;
2697 entry->lineno = lineno;
2700 /* Parse output from git-ls-tree(1):
2702 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2703 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2704 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2705 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2708 #define SIZEOF_TREE_ATTR \
2709 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2711 #define TREE_UP_FORMAT "040000 tree %s\t.."
2713 static int
2714 tree_compare_entry(enum line_type type1, char *name1,
2715 enum line_type type2, char *name2)
2717 if (type1 != type2) {
2718 if (type1 == LINE_TREE_DIR)
2719 return -1;
2720 return 1;
2723 return strcmp(name1, name2);
2726 static bool
2727 tree_read(struct view *view, char *text)
2729 size_t textlen = text ? strlen(text) : 0;
2730 char buf[SIZEOF_STR];
2731 unsigned long pos;
2732 enum line_type type;
2733 bool first_read = view->lines == 0;
2735 if (textlen <= SIZEOF_TREE_ATTR)
2736 return FALSE;
2738 type = text[STRING_SIZE("100644 ")] == 't'
2739 ? LINE_TREE_DIR : LINE_TREE_FILE;
2741 if (first_read) {
2742 /* Add path info line */
2743 if (!string_format(buf, "Directory path /%s", opt_path) ||
2744 !realloc_lines(view, view->line_size + 1) ||
2745 !add_line_text(view, buf, LINE_DEFAULT))
2746 return FALSE;
2748 /* Insert "link" to parent directory. */
2749 if (*opt_path) {
2750 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2751 !realloc_lines(view, view->line_size + 1) ||
2752 !add_line_text(view, buf, LINE_TREE_DIR))
2753 return FALSE;
2757 /* Strip the path part ... */
2758 if (*opt_path) {
2759 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2760 size_t striplen = strlen(opt_path);
2761 char *path = text + SIZEOF_TREE_ATTR;
2763 if (pathlen > striplen)
2764 memmove(path, path + striplen,
2765 pathlen - striplen + 1);
2768 /* Skip "Directory ..." and ".." line. */
2769 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2770 struct line *line = &view->line[pos];
2771 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2772 char *path2 = text + SIZEOF_TREE_ATTR;
2773 int cmp = tree_compare_entry(line->type, path1, type, path2);
2775 if (cmp <= 0)
2776 continue;
2778 text = strdup(text);
2779 if (!text)
2780 return FALSE;
2782 if (view->lines > pos)
2783 memmove(&view->line[pos + 1], &view->line[pos],
2784 (view->lines - pos) * sizeof(*line));
2786 line = &view->line[pos];
2787 line->data = text;
2788 line->type = type;
2789 view->lines++;
2790 return TRUE;
2793 if (!add_line_text(view, text, type))
2794 return FALSE;
2796 if (tree_lineno > view->lineno) {
2797 view->lineno = tree_lineno;
2798 tree_lineno = 0;
2801 return TRUE;
2804 static enum request
2805 tree_request(struct view *view, enum request request, struct line *line)
2807 enum open_flags flags;
2809 if (request != REQ_ENTER)
2810 return request;
2812 /* Cleanup the stack if the tree view is at a different tree. */
2813 while (!*opt_path && tree_stack)
2814 pop_tree_stack_entry();
2816 switch (line->type) {
2817 case LINE_TREE_DIR:
2818 /* Depending on whether it is a subdir or parent (updir?) link
2819 * mangle the path buffer. */
2820 if (line == &view->line[1] && *opt_path) {
2821 pop_tree_stack_entry();
2823 } else {
2824 char *data = line->data;
2825 char *basename = data + SIZEOF_TREE_ATTR;
2827 push_tree_stack_entry(basename, view->lineno);
2830 /* Trees and subtrees share the same ID, so they are not not
2831 * unique like blobs. */
2832 flags = OPEN_RELOAD;
2833 request = REQ_VIEW_TREE;
2834 break;
2836 case LINE_TREE_FILE:
2837 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2838 request = REQ_VIEW_BLOB;
2839 break;
2841 default:
2842 return TRUE;
2845 open_view(view, request, flags);
2846 if (request == REQ_VIEW_TREE) {
2847 view->lineno = tree_lineno;
2850 return REQ_NONE;
2853 static void
2854 tree_select(struct view *view, struct line *line)
2856 char *text = line->data + STRING_SIZE("100644 blob ");
2858 if (line->type == LINE_TREE_FILE) {
2859 string_copy_rev(ref_blob, text);
2861 } else if (line->type != LINE_TREE_DIR) {
2862 return;
2865 string_copy_rev(view->ref, text);
2868 static struct view_ops tree_ops = {
2869 "file",
2870 NULL,
2871 tree_read,
2872 pager_draw,
2873 tree_request,
2874 pager_grep,
2875 tree_select,
2878 static bool
2879 blob_read(struct view *view, char *line)
2881 return add_line_text(view, line, LINE_DEFAULT) != NULL;
2884 static struct view_ops blob_ops = {
2885 "line",
2886 NULL,
2887 blob_read,
2888 pager_draw,
2889 pager_request,
2890 pager_grep,
2891 pager_select,
2896 * Status backend
2899 struct status {
2900 char status;
2901 struct {
2902 mode_t mode;
2903 char rev[SIZEOF_REV];
2904 } old;
2905 struct {
2906 mode_t mode;
2907 char rev[SIZEOF_REV];
2908 } new;
2909 char name[SIZEOF_STR];
2912 static struct status stage_status;
2913 static enum line_type stage_line_type;
2915 /* Get fields from the diff line:
2916 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2918 static inline bool
2919 status_get_diff(struct status *file, char *buf, size_t bufsize)
2921 char *old_mode = buf + 1;
2922 char *new_mode = buf + 8;
2923 char *old_rev = buf + 15;
2924 char *new_rev = buf + 56;
2925 char *status = buf + 97;
2927 if (bufsize != 99 ||
2928 old_mode[-1] != ':' ||
2929 new_mode[-1] != ' ' ||
2930 old_rev[-1] != ' ' ||
2931 new_rev[-1] != ' ' ||
2932 status[-1] != ' ')
2933 return FALSE;
2935 file->status = *status;
2937 string_copy_rev(file->old.rev, old_rev);
2938 string_copy_rev(file->new.rev, new_rev);
2940 file->old.mode = strtoul(old_mode, NULL, 8);
2941 file->new.mode = strtoul(new_mode, NULL, 8);
2943 file->name[0] = 0;
2945 return TRUE;
2948 static bool
2949 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2951 struct status *file = NULL;
2952 char buf[SIZEOF_STR * 4];
2953 size_t bufsize = 0;
2954 FILE *pipe;
2956 pipe = popen(cmd, "r");
2957 if (!pipe)
2958 return FALSE;
2960 add_line_data(view, NULL, type);
2962 while (!feof(pipe) && !ferror(pipe)) {
2963 char *sep;
2964 size_t readsize;
2966 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
2967 if (!readsize)
2968 break;
2969 bufsize += readsize;
2971 /* Process while we have NUL chars. */
2972 while ((sep = memchr(buf, 0, bufsize))) {
2973 size_t sepsize = sep - buf + 1;
2975 if (!file) {
2976 if (!realloc_lines(view, view->line_size + 1))
2977 goto error_out;
2979 file = calloc(1, sizeof(*file));
2980 if (!file)
2981 goto error_out;
2983 add_line_data(view, file, type);
2986 /* Parse diff info part. */
2987 if (!diff) {
2988 file->status = '?';
2990 } else if (!file->status) {
2991 if (!status_get_diff(file, buf, sepsize))
2992 goto error_out;
2994 bufsize -= sepsize;
2995 memmove(buf, sep + 1, bufsize);
2997 sep = memchr(buf, 0, bufsize);
2998 if (!sep)
2999 break;
3000 sepsize = sep - buf + 1;
3003 /* git-ls-files just delivers a NUL separated
3004 * list of file names similar to the second half
3005 * of the git-diff-* output. */
3006 string_ncopy(file->name, buf, sepsize);
3007 bufsize -= sepsize;
3008 memmove(buf, sep + 1, bufsize);
3009 file = NULL;
3013 if (ferror(pipe)) {
3014 error_out:
3015 pclose(pipe);
3016 return FALSE;
3019 if (!view->line[view->lines - 1].data)
3020 add_line_data(view, NULL, LINE_STAT_NONE);
3022 pclose(pipe);
3023 return TRUE;
3026 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
3027 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3028 #define STATUS_LIST_OTHER_CMD \
3029 "git ls-files -z --others --exclude-per-directory=.gitignore"
3031 #define STATUS_DIFF_SHOW_CMD \
3032 "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3034 /* First parse staged info using git-diff-index(1), then parse unstaged
3035 * info using git-diff-files(1), and finally untracked files using
3036 * git-ls-files(1). */
3037 static bool
3038 status_open(struct view *view)
3040 struct stat statbuf;
3041 char exclude[SIZEOF_STR];
3042 char cmd[SIZEOF_STR];
3043 size_t i;
3045 for (i = 0; i < view->lines; i++)
3046 free(view->line[i].data);
3047 free(view->line);
3048 view->lines = view->line_size = 0;
3049 view->line = NULL;
3051 if (!realloc_lines(view, view->line_size + 6))
3052 return FALSE;
3054 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3055 return FALSE;
3057 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3059 if (stat(exclude, &statbuf) >= 0) {
3060 size_t cmdsize = strlen(cmd);
3062 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3063 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3064 return FALSE;
3067 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3068 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3069 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3070 return FALSE;
3072 return TRUE;
3075 static bool
3076 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3078 struct status *status = line->data;
3080 wmove(view->win, lineno, 0);
3082 if (selected) {
3083 wattrset(view->win, get_line_attr(LINE_CURSOR));
3084 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3086 } else if (!status && line->type != LINE_STAT_NONE) {
3087 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3088 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3090 } else {
3091 wattrset(view->win, get_line_attr(line->type));
3094 if (!status) {
3095 char *text;
3097 switch (line->type) {
3098 case LINE_STAT_STAGED:
3099 text = "Changes to be committed:";
3100 break;
3102 case LINE_STAT_UNSTAGED:
3103 text = "Changed but not updated:";
3104 break;
3106 case LINE_STAT_UNTRACKED:
3107 text = "Untracked files:";
3108 break;
3110 case LINE_STAT_NONE:
3111 text = " (no files)";
3112 break;
3114 default:
3115 return FALSE;
3118 waddstr(view->win, text);
3119 return TRUE;
3122 waddch(view->win, status->status);
3123 if (!selected)
3124 wattrset(view->win, A_NORMAL);
3125 wmove(view->win, lineno, 4);
3126 waddstr(view->win, status->name);
3128 return TRUE;
3131 static enum request
3132 status_enter(struct view *view, struct line *line)
3134 struct status *status = line->data;
3135 char path[SIZEOF_STR] = "";
3136 char *info;
3137 size_t cmdsize = 0;
3139 if (line->type == LINE_STAT_NONE ||
3140 (!status && line[1].type == LINE_STAT_NONE)) {
3141 report("No file to diff");
3142 return REQ_NONE;
3145 if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3146 return REQ_QUIT;
3148 if (opt_cdup[0] &&
3149 line->type != LINE_STAT_UNTRACKED &&
3150 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3151 return REQ_QUIT;
3153 switch (line->type) {
3154 case LINE_STAT_STAGED:
3155 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3156 "--cached", path))
3157 return REQ_QUIT;
3158 if (status)
3159 info = "Staged changes to %s";
3160 else
3161 info = "Staged changes";
3162 break;
3164 case LINE_STAT_UNSTAGED:
3165 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3166 "", path))
3167 return REQ_QUIT;
3168 if (status)
3169 info = "Unstaged changes to %s";
3170 else
3171 info = "Unstaged changes";
3172 break;
3174 case LINE_STAT_UNTRACKED:
3175 if (opt_pipe)
3176 return REQ_QUIT;
3179 if (!status) {
3180 report("No file to show");
3181 return REQ_NONE;
3184 opt_pipe = fopen(status->name, "r");
3185 info = "Untracked file %s";
3186 break;
3188 default:
3189 die("w00t");
3192 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3193 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3194 if (status) {
3195 stage_status = *status;
3196 } else {
3197 memset(&stage_status, 0, sizeof(stage_status));
3200 stage_line_type = line->type;
3201 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3204 return REQ_NONE;
3208 static bool
3209 status_update_file(struct view *view, struct status *status, enum line_type type)
3211 char cmd[SIZEOF_STR];
3212 char buf[SIZEOF_STR];
3213 size_t cmdsize = 0;
3214 size_t bufsize = 0;
3215 size_t written = 0;
3216 FILE *pipe;
3218 if (opt_cdup[0] &&
3219 type != LINE_STAT_UNTRACKED &&
3220 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3221 return FALSE;
3223 switch (type) {
3224 case LINE_STAT_STAGED:
3225 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3226 status->old.mode,
3227 status->old.rev,
3228 status->name, 0))
3229 return FALSE;
3231 string_add(cmd, cmdsize, "git update-index -z --index-info");
3232 break;
3234 case LINE_STAT_UNSTAGED:
3235 case LINE_STAT_UNTRACKED:
3236 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3237 return FALSE;
3239 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3240 break;
3242 default:
3243 die("w00t");
3246 pipe = popen(cmd, "w");
3247 if (!pipe)
3248 return FALSE;
3250 while (!ferror(pipe) && written < bufsize) {
3251 written += fwrite(buf + written, 1, bufsize - written, pipe);
3254 pclose(pipe);
3256 if (written != bufsize)
3257 return FALSE;
3259 return TRUE;
3262 static void
3263 status_update(struct view *view)
3265 struct line *line = &view->line[view->lineno];
3267 assert(view->lines);
3269 if (!line->data) {
3270 while (++line < view->line + view->lines && line->data) {
3271 if (!status_update_file(view, line->data, line->type))
3272 report("Failed to update file status");
3275 if (!line[-1].data) {
3276 report("Nothing to update");
3277 return;
3280 } else if (!status_update_file(view, line->data, line->type)) {
3281 report("Failed to update file status");
3284 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3287 static enum request
3288 status_request(struct view *view, enum request request, struct line *line)
3290 struct status *status = line->data;
3292 switch (request) {
3293 case REQ_STATUS_UPDATE:
3294 status_update(view);
3295 break;
3297 case REQ_EDIT:
3298 if (!status)
3299 return request;
3301 open_editor(view, status->name);
3302 break;
3304 case REQ_ENTER:
3305 status_enter(view, line);
3306 break;
3308 default:
3309 return request;
3312 return REQ_NONE;
3315 static void
3316 status_select(struct view *view, struct line *line)
3318 struct status *status = line->data;
3319 char file[SIZEOF_STR] = "all files";
3320 char *text;
3322 if (status && !string_format(file, "'%s'", status->name))
3323 return;
3325 if (!status && line[1].type == LINE_STAT_NONE)
3326 line++;
3328 switch (line->type) {
3329 case LINE_STAT_STAGED:
3330 text = "Press %s to unstage %s for commit";
3331 break;
3333 case LINE_STAT_UNSTAGED:
3334 text = "Press %s to stage %s for commit";
3335 break;
3337 case LINE_STAT_UNTRACKED:
3338 text = "Press %s to stage %s for addition";
3339 break;
3341 case LINE_STAT_NONE:
3342 text = "Nothing to update";
3343 break;
3345 default:
3346 die("w00t");
3349 string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
3352 static bool
3353 status_grep(struct view *view, struct line *line)
3355 struct status *status = line->data;
3356 enum { S_STATUS, S_NAME, S_END } state;
3357 char buf[2] = "?";
3358 regmatch_t pmatch;
3360 if (!status)
3361 return FALSE;
3363 for (state = S_STATUS; state < S_END; state++) {
3364 char *text;
3366 switch (state) {
3367 case S_NAME: text = status->name; break;
3368 case S_STATUS:
3369 buf[0] = status->status;
3370 text = buf;
3371 break;
3373 default:
3374 return FALSE;
3377 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3378 return TRUE;
3381 return FALSE;
3384 static struct view_ops status_ops = {
3385 "file",
3386 status_open,
3387 NULL,
3388 status_draw,
3389 status_request,
3390 status_grep,
3391 status_select,
3395 static bool
3396 stage_diff_line(FILE *pipe, struct line *line)
3398 char *buf = line->data;
3399 size_t bufsize = strlen(buf);
3400 size_t written = 0;
3402 while (!ferror(pipe) && written < bufsize) {
3403 written += fwrite(buf + written, 1, bufsize - written, pipe);
3406 fputc('\n', pipe);
3408 return written == bufsize;
3411 static struct line *
3412 stage_diff_hdr(struct view *view, struct line *line)
3414 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3415 struct line *diff_hdr;
3417 if (line->type == LINE_DIFF_CHUNK)
3418 diff_hdr = line - 1;
3419 else
3420 diff_hdr = view->line + 1;
3422 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3423 if (diff_hdr->type == LINE_DIFF_HEADER)
3424 return diff_hdr;
3426 diff_hdr += diff_hdr_dir;
3429 return NULL;
3432 static bool
3433 stage_update_chunk(struct view *view, struct line *line)
3435 char cmd[SIZEOF_STR];
3436 size_t cmdsize = 0;
3437 struct line *diff_hdr, *diff_chunk, *diff_end;
3438 FILE *pipe;
3440 diff_hdr = stage_diff_hdr(view, line);
3441 if (!diff_hdr)
3442 return FALSE;
3444 if (opt_cdup[0] &&
3445 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3446 return FALSE;
3448 if (!string_format_from(cmd, &cmdsize,
3449 "git apply --cached %s - && "
3450 "git update-index -q --unmerged --refresh 2>/dev/null",
3451 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3452 return FALSE;
3454 pipe = popen(cmd, "w");
3455 if (!pipe)
3456 return FALSE;
3458 diff_end = view->line + view->lines;
3459 if (line->type != LINE_DIFF_CHUNK) {
3460 diff_chunk = diff_hdr;
3462 } else {
3463 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3464 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3465 diff_chunk->type == LINE_DIFF_HEADER)
3466 diff_end = diff_chunk;
3468 diff_chunk = line;
3470 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3471 switch (diff_hdr->type) {
3472 case LINE_DIFF_HEADER:
3473 case LINE_DIFF_INDEX:
3474 case LINE_DIFF_ADD:
3475 case LINE_DIFF_DEL:
3476 break;
3478 default:
3479 diff_hdr++;
3480 continue;
3483 if (!stage_diff_line(pipe, diff_hdr++)) {
3484 pclose(pipe);
3485 return FALSE;
3490 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3491 diff_chunk++;
3493 pclose(pipe);
3495 if (diff_chunk != diff_end)
3496 return FALSE;
3498 return TRUE;
3501 static void
3502 stage_update(struct view *view, struct line *line)
3504 if (stage_line_type != LINE_STAT_UNTRACKED &&
3505 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3506 if (!stage_update_chunk(view, line)) {
3507 report("Failed to apply chunk");
3508 return;
3511 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3512 report("Failed to update file");
3513 return;
3516 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3518 view = VIEW(REQ_VIEW_STATUS);
3519 if (view_is_displayed(view))
3520 status_enter(view, &view->line[view->lineno]);
3523 static enum request
3524 stage_request(struct view *view, enum request request, struct line *line)
3526 switch (request) {
3527 case REQ_STATUS_UPDATE:
3528 stage_update(view, line);
3529 break;
3531 case REQ_EDIT:
3532 if (!stage_status.name[0])
3533 return request;
3535 open_editor(view, stage_status.name);
3536 break;
3538 case REQ_ENTER:
3539 pager_request(view, request, line);
3540 break;
3542 default:
3543 return request;
3546 return REQ_NONE;
3549 static struct view_ops stage_ops = {
3550 "line",
3551 NULL,
3552 pager_read,
3553 pager_draw,
3554 stage_request,
3555 pager_grep,
3556 pager_select,
3561 * Revision graph
3564 struct commit {
3565 char id[SIZEOF_REV]; /* SHA1 ID. */
3566 char title[128]; /* First line of the commit message. */
3567 char author[75]; /* Author of the commit. */
3568 struct tm time; /* Date from the author ident. */
3569 struct ref **refs; /* Repository references. */
3570 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3571 size_t graph_size; /* The width of the graph array. */
3574 /* Size of rev graph with no "padding" columns */
3575 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3577 struct rev_graph {
3578 struct rev_graph *prev, *next, *parents;
3579 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3580 size_t size;
3581 struct commit *commit;
3582 size_t pos;
3585 /* Parents of the commit being visualized. */
3586 static struct rev_graph graph_parents[4];
3588 /* The current stack of revisions on the graph. */
3589 static struct rev_graph graph_stacks[4] = {
3590 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3591 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3592 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3593 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3596 static inline bool
3597 graph_parent_is_merge(struct rev_graph *graph)
3599 return graph->parents->size > 1;
3602 static inline void
3603 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3605 struct commit *commit = graph->commit;
3607 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3608 commit->graph[commit->graph_size++] = symbol;
3611 static void
3612 done_rev_graph(struct rev_graph *graph)
3614 if (graph_parent_is_merge(graph) &&
3615 graph->pos < graph->size - 1 &&
3616 graph->next->size == graph->size + graph->parents->size - 1) {
3617 size_t i = graph->pos + graph->parents->size - 1;
3619 graph->commit->graph_size = i * 2;
3620 while (i < graph->next->size - 1) {
3621 append_to_rev_graph(graph, ' ');
3622 append_to_rev_graph(graph, '\\');
3623 i++;
3627 graph->size = graph->pos = 0;
3628 graph->commit = NULL;
3629 memset(graph->parents, 0, sizeof(*graph->parents));
3632 static void
3633 push_rev_graph(struct rev_graph *graph, char *parent)
3635 int i;
3637 /* "Collapse" duplicate parents lines.
3639 * FIXME: This needs to also update update the drawn graph but
3640 * for now it just serves as a method for pruning graph lines. */
3641 for (i = 0; i < graph->size; i++)
3642 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3643 return;
3645 if (graph->size < SIZEOF_REVITEMS) {
3646 string_copy_rev(graph->rev[graph->size++], parent);
3650 static chtype
3651 get_rev_graph_symbol(struct rev_graph *graph)
3653 chtype symbol;
3655 if (graph->parents->size == 0)
3656 symbol = REVGRAPH_INIT;
3657 else if (graph_parent_is_merge(graph))
3658 symbol = REVGRAPH_MERGE;
3659 else if (graph->pos >= graph->size)
3660 symbol = REVGRAPH_BRANCH;
3661 else
3662 symbol = REVGRAPH_COMMIT;
3664 return symbol;
3667 static void
3668 draw_rev_graph(struct rev_graph *graph)
3670 struct rev_filler {
3671 chtype separator, line;
3673 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3674 static struct rev_filler fillers[] = {
3675 { ' ', REVGRAPH_LINE },
3676 { '`', '.' },
3677 { '\'', ' ' },
3678 { '/', ' ' },
3680 chtype symbol = get_rev_graph_symbol(graph);
3681 struct rev_filler *filler;
3682 size_t i;
3684 filler = &fillers[DEFAULT];
3686 for (i = 0; i < graph->pos; i++) {
3687 append_to_rev_graph(graph, filler->line);
3688 if (graph_parent_is_merge(graph->prev) &&
3689 graph->prev->pos == i)
3690 filler = &fillers[RSHARP];
3692 append_to_rev_graph(graph, filler->separator);
3695 /* Place the symbol for this revision. */
3696 append_to_rev_graph(graph, symbol);
3698 if (graph->prev->size > graph->size)
3699 filler = &fillers[RDIAG];
3700 else
3701 filler = &fillers[DEFAULT];
3703 i++;
3705 for (; i < graph->size; i++) {
3706 append_to_rev_graph(graph, filler->separator);
3707 append_to_rev_graph(graph, filler->line);
3708 if (graph_parent_is_merge(graph->prev) &&
3709 i < graph->prev->pos + graph->parents->size)
3710 filler = &fillers[RSHARP];
3711 if (graph->prev->size > graph->size)
3712 filler = &fillers[LDIAG];
3715 if (graph->prev->size > graph->size) {
3716 append_to_rev_graph(graph, filler->separator);
3717 if (filler->line != ' ')
3718 append_to_rev_graph(graph, filler->line);
3722 /* Prepare the next rev graph */
3723 static void
3724 prepare_rev_graph(struct rev_graph *graph)
3726 size_t i;
3728 /* First, traverse all lines of revisions up to the active one. */
3729 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3730 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3731 break;
3733 push_rev_graph(graph->next, graph->rev[graph->pos]);
3736 /* Interleave the new revision parent(s). */
3737 for (i = 0; i < graph->parents->size; i++)
3738 push_rev_graph(graph->next, graph->parents->rev[i]);
3740 /* Lastly, put any remaining revisions. */
3741 for (i = graph->pos + 1; i < graph->size; i++)
3742 push_rev_graph(graph->next, graph->rev[i]);
3745 static void
3746 update_rev_graph(struct rev_graph *graph)
3748 /* If this is the finalizing update ... */
3749 if (graph->commit)
3750 prepare_rev_graph(graph);
3752 /* Graph visualization needs a one rev look-ahead,
3753 * so the first update doesn't visualize anything. */
3754 if (!graph->prev->commit)
3755 return;
3757 draw_rev_graph(graph->prev);
3758 done_rev_graph(graph->prev->prev);
3763 * Main view backend
3766 static bool
3767 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3769 char buf[DATE_COLS + 1];
3770 struct commit *commit = line->data;
3771 enum line_type type;
3772 int col = 0;
3773 size_t timelen;
3774 size_t authorlen;
3775 int trimmed = 1;
3777 if (!*commit->author)
3778 return FALSE;
3780 wmove(view->win, lineno, col);
3782 if (selected) {
3783 type = LINE_CURSOR;
3784 wattrset(view->win, get_line_attr(type));
3785 wchgat(view->win, -1, 0, type, NULL);
3787 } else {
3788 type = LINE_MAIN_COMMIT;
3789 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3792 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3793 waddnstr(view->win, buf, timelen);
3794 waddstr(view->win, " ");
3796 col += DATE_COLS;
3797 wmove(view->win, lineno, col);
3798 if (type != LINE_CURSOR)
3799 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3801 if (opt_utf8) {
3802 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3803 } else {
3804 authorlen = strlen(commit->author);
3805 if (authorlen > AUTHOR_COLS - 2) {
3806 authorlen = AUTHOR_COLS - 2;
3807 trimmed = 1;
3811 if (trimmed) {
3812 waddnstr(view->win, commit->author, authorlen);
3813 if (type != LINE_CURSOR)
3814 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3815 waddch(view->win, '~');
3816 } else {
3817 waddstr(view->win, commit->author);
3820 col += AUTHOR_COLS;
3821 if (type != LINE_CURSOR)
3822 wattrset(view->win, A_NORMAL);
3824 if (opt_rev_graph && commit->graph_size) {
3825 size_t i;
3827 wmove(view->win, lineno, col);
3828 /* Using waddch() instead of waddnstr() ensures that
3829 * they'll be rendered correctly for the cursor line. */
3830 for (i = 0; i < commit->graph_size; i++)
3831 waddch(view->win, commit->graph[i]);
3833 waddch(view->win, ' ');
3834 col += commit->graph_size + 1;
3837 wmove(view->win, lineno, col);
3839 if (commit->refs) {
3840 size_t i = 0;
3842 do {
3843 if (type == LINE_CURSOR)
3845 else if (commit->refs[i]->tag)
3846 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3847 else if (commit->refs[i]->remote)
3848 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3849 else
3850 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3851 waddstr(view->win, "[");
3852 waddstr(view->win, commit->refs[i]->name);
3853 waddstr(view->win, "]");
3854 if (type != LINE_CURSOR)
3855 wattrset(view->win, A_NORMAL);
3856 waddstr(view->win, " ");
3857 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3858 } while (commit->refs[i++]->next);
3861 if (type != LINE_CURSOR)
3862 wattrset(view->win, get_line_attr(type));
3865 int titlelen = strlen(commit->title);
3867 if (col + titlelen > view->width)
3868 titlelen = view->width - col;
3870 waddnstr(view->win, commit->title, titlelen);
3873 return TRUE;
3876 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3877 static bool
3878 main_read(struct view *view, char *line)
3880 static struct rev_graph *graph = graph_stacks;
3881 enum line_type type;
3882 struct commit *commit;
3884 if (!line) {
3885 update_rev_graph(graph);
3886 return TRUE;
3889 type = get_line_type(line);
3890 if (type == LINE_COMMIT) {
3891 commit = calloc(1, sizeof(struct commit));
3892 if (!commit)
3893 return FALSE;
3895 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3896 commit->refs = get_refs(commit->id);
3897 graph->commit = commit;
3898 add_line_data(view, commit, LINE_MAIN_COMMIT);
3899 return TRUE;
3902 if (!view->lines)
3903 return TRUE;
3904 commit = view->line[view->lines - 1].data;
3906 switch (type) {
3907 case LINE_PARENT:
3908 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3909 break;
3911 case LINE_AUTHOR:
3913 /* Parse author lines where the name may be empty:
3914 * author <email@address.tld> 1138474660 +0100
3916 char *ident = line + STRING_SIZE("author ");
3917 char *nameend = strchr(ident, '<');
3918 char *emailend = strchr(ident, '>');
3920 if (!nameend || !emailend)
3921 break;
3923 update_rev_graph(graph);
3924 graph = graph->next;
3926 *nameend = *emailend = 0;
3927 ident = chomp_string(ident);
3928 if (!*ident) {
3929 ident = chomp_string(nameend + 1);
3930 if (!*ident)
3931 ident = "Unknown";
3934 string_ncopy(commit->author, ident, strlen(ident));
3936 /* Parse epoch and timezone */
3937 if (emailend[1] == ' ') {
3938 char *secs = emailend + 2;
3939 char *zone = strchr(secs, ' ');
3940 time_t time = (time_t) atol(secs);
3942 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3943 long tz;
3945 zone++;
3946 tz = ('0' - zone[1]) * 60 * 60 * 10;
3947 tz += ('0' - zone[2]) * 60 * 60;
3948 tz += ('0' - zone[3]) * 60;
3949 tz += ('0' - zone[4]) * 60;
3951 if (zone[0] == '-')
3952 tz = -tz;
3954 time -= tz;
3957 gmtime_r(&time, &commit->time);
3959 break;
3961 default:
3962 /* Fill in the commit title if it has not already been set. */
3963 if (commit->title[0])
3964 break;
3966 /* Require titles to start with a non-space character at the
3967 * offset used by git log. */
3968 if (strncmp(line, " ", 4))
3969 break;
3970 line += 4;
3971 /* Well, if the title starts with a whitespace character,
3972 * try to be forgiving. Otherwise we end up with no title. */
3973 while (isspace(*line))
3974 line++;
3975 if (*line == '\0')
3976 break;
3977 /* FIXME: More graceful handling of titles; append "..." to
3978 * shortened titles, etc. */
3980 string_ncopy(commit->title, line, strlen(line));
3983 return TRUE;
3986 static enum request
3987 main_request(struct view *view, enum request request, struct line *line)
3989 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3991 if (request == REQ_ENTER)
3992 open_view(view, REQ_VIEW_DIFF, flags);
3993 else
3994 return request;
3996 return REQ_NONE;
3999 static bool
4000 main_grep(struct view *view, struct line *line)
4002 struct commit *commit = line->data;
4003 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4004 char buf[DATE_COLS + 1];
4005 regmatch_t pmatch;
4007 for (state = S_TITLE; state < S_END; state++) {
4008 char *text;
4010 switch (state) {
4011 case S_TITLE: text = commit->title; break;
4012 case S_AUTHOR: text = commit->author; break;
4013 case S_DATE:
4014 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4015 continue;
4016 text = buf;
4017 break;
4019 default:
4020 return FALSE;
4023 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4024 return TRUE;
4027 return FALSE;
4030 static void
4031 main_select(struct view *view, struct line *line)
4033 struct commit *commit = line->data;
4035 string_copy_rev(view->ref, commit->id);
4036 string_copy_rev(ref_commit, view->ref);
4039 static struct view_ops main_ops = {
4040 "commit",
4041 NULL,
4042 main_read,
4043 main_draw,
4044 main_request,
4045 main_grep,
4046 main_select,
4051 * Unicode / UTF-8 handling
4053 * NOTE: Much of the following code for dealing with unicode is derived from
4054 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4055 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4058 /* I've (over)annotated a lot of code snippets because I am not entirely
4059 * confident that the approach taken by this small UTF-8 interface is correct.
4060 * --jonas */
4062 static inline int
4063 unicode_width(unsigned long c)
4065 if (c >= 0x1100 &&
4066 (c <= 0x115f /* Hangul Jamo */
4067 || c == 0x2329
4068 || c == 0x232a
4069 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
4070 /* CJK ... Yi */
4071 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4072 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4073 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4074 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4075 || (c >= 0xffe0 && c <= 0xffe6)
4076 || (c >= 0x20000 && c <= 0x2fffd)
4077 || (c >= 0x30000 && c <= 0x3fffd)))
4078 return 2;
4080 return 1;
4083 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4084 * Illegal bytes are set one. */
4085 static const unsigned char utf8_bytes[256] = {
4086 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,
4087 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,
4088 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,
4089 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,
4090 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,
4091 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,
4092 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,
4093 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,
4096 /* Decode UTF-8 multi-byte representation into a unicode character. */
4097 static inline unsigned long
4098 utf8_to_unicode(const char *string, size_t length)
4100 unsigned long unicode;
4102 switch (length) {
4103 case 1:
4104 unicode = string[0];
4105 break;
4106 case 2:
4107 unicode = (string[0] & 0x1f) << 6;
4108 unicode += (string[1] & 0x3f);
4109 break;
4110 case 3:
4111 unicode = (string[0] & 0x0f) << 12;
4112 unicode += ((string[1] & 0x3f) << 6);
4113 unicode += (string[2] & 0x3f);
4114 break;
4115 case 4:
4116 unicode = (string[0] & 0x0f) << 18;
4117 unicode += ((string[1] & 0x3f) << 12);
4118 unicode += ((string[2] & 0x3f) << 6);
4119 unicode += (string[3] & 0x3f);
4120 break;
4121 case 5:
4122 unicode = (string[0] & 0x0f) << 24;
4123 unicode += ((string[1] & 0x3f) << 18);
4124 unicode += ((string[2] & 0x3f) << 12);
4125 unicode += ((string[3] & 0x3f) << 6);
4126 unicode += (string[4] & 0x3f);
4127 break;
4128 case 6:
4129 unicode = (string[0] & 0x01) << 30;
4130 unicode += ((string[1] & 0x3f) << 24);
4131 unicode += ((string[2] & 0x3f) << 18);
4132 unicode += ((string[3] & 0x3f) << 12);
4133 unicode += ((string[4] & 0x3f) << 6);
4134 unicode += (string[5] & 0x3f);
4135 break;
4136 default:
4137 die("Invalid unicode length");
4140 /* Invalid characters could return the special 0xfffd value but NUL
4141 * should be just as good. */
4142 return unicode > 0xffff ? 0 : unicode;
4145 /* Calculates how much of string can be shown within the given maximum width
4146 * and sets trimmed parameter to non-zero value if all of string could not be
4147 * shown.
4149 * Additionally, adds to coloffset how many many columns to move to align with
4150 * the expected position. Takes into account how multi-byte and double-width
4151 * characters will effect the cursor position.
4153 * Returns the number of bytes to output from string to satisfy max_width. */
4154 static size_t
4155 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4157 const char *start = string;
4158 const char *end = strchr(string, '\0');
4159 size_t mbwidth = 0;
4160 size_t width = 0;
4162 *trimmed = 0;
4164 while (string < end) {
4165 int c = *(unsigned char *) string;
4166 unsigned char bytes = utf8_bytes[c];
4167 size_t ucwidth;
4168 unsigned long unicode;
4170 if (string + bytes > end)
4171 break;
4173 /* Change representation to figure out whether
4174 * it is a single- or double-width character. */
4176 unicode = utf8_to_unicode(string, bytes);
4177 /* FIXME: Graceful handling of invalid unicode character. */
4178 if (!unicode)
4179 break;
4181 ucwidth = unicode_width(unicode);
4182 width += ucwidth;
4183 if (width > max_width) {
4184 *trimmed = 1;
4185 break;
4188 /* The column offset collects the differences between the
4189 * number of bytes encoding a character and the number of
4190 * columns will be used for rendering said character.
4192 * So if some character A is encoded in 2 bytes, but will be
4193 * represented on the screen using only 1 byte this will and up
4194 * adding 1 to the multi-byte column offset.
4196 * Assumes that no double-width character can be encoding in
4197 * less than two bytes. */
4198 if (bytes > ucwidth)
4199 mbwidth += bytes - ucwidth;
4201 string += bytes;
4204 *coloffset += mbwidth;
4206 return string - start;
4211 * Status management
4214 /* Whether or not the curses interface has been initialized. */
4215 static bool cursed = FALSE;
4217 /* The status window is used for polling keystrokes. */
4218 static WINDOW *status_win;
4220 static bool status_empty = TRUE;
4222 /* Update status and title window. */
4223 static void
4224 report(const char *msg, ...)
4226 struct view *view = display[current_view];
4228 if (input_mode)
4229 return;
4231 if (!status_empty || *msg) {
4232 va_list args;
4234 va_start(args, msg);
4236 wmove(status_win, 0, 0);
4237 if (*msg) {
4238 vwprintw(status_win, msg, args);
4239 status_empty = FALSE;
4240 } else {
4241 status_empty = TRUE;
4243 wclrtoeol(status_win);
4244 wrefresh(status_win);
4246 va_end(args);
4249 update_view_title(view);
4250 update_display_cursor(view);
4253 /* Controls when nodelay should be in effect when polling user input. */
4254 static void
4255 set_nonblocking_input(bool loading)
4257 static unsigned int loading_views;
4259 if ((loading == FALSE && loading_views-- == 1) ||
4260 (loading == TRUE && loading_views++ == 0))
4261 nodelay(status_win, loading);
4264 static void
4265 init_display(void)
4267 int x, y;
4269 /* Initialize the curses library */
4270 if (isatty(STDIN_FILENO)) {
4271 cursed = !!initscr();
4272 } else {
4273 /* Leave stdin and stdout alone when acting as a pager. */
4274 FILE *io = fopen("/dev/tty", "r+");
4276 if (!io)
4277 die("Failed to open /dev/tty");
4278 cursed = !!newterm(NULL, io, io);
4281 if (!cursed)
4282 die("Failed to initialize curses");
4284 nonl(); /* Tell curses not to do NL->CR/NL on output */
4285 cbreak(); /* Take input chars one at a time, no wait for \n */
4286 noecho(); /* Don't echo input */
4287 leaveok(stdscr, TRUE);
4289 if (has_colors())
4290 init_colors();
4292 getmaxyx(stdscr, y, x);
4293 status_win = newwin(1, 0, y - 1, 0);
4294 if (!status_win)
4295 die("Failed to create status window");
4297 /* Enable keyboard mapping */
4298 keypad(status_win, TRUE);
4299 wbkgdset(status_win, get_line_attr(LINE_STATUS));
4302 static char *
4303 read_prompt(const char *prompt)
4305 enum { READING, STOP, CANCEL } status = READING;
4306 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4307 int pos = 0;
4309 while (status == READING) {
4310 struct view *view;
4311 int i, key;
4313 input_mode = TRUE;
4315 foreach_view (view, i)
4316 update_view(view);
4318 input_mode = FALSE;
4320 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4321 wclrtoeol(status_win);
4323 /* Refresh, accept single keystroke of input */
4324 key = wgetch(status_win);
4325 switch (key) {
4326 case KEY_RETURN:
4327 case KEY_ENTER:
4328 case '\n':
4329 status = pos ? STOP : CANCEL;
4330 break;
4332 case KEY_BACKSPACE:
4333 if (pos > 0)
4334 pos--;
4335 else
4336 status = CANCEL;
4337 break;
4339 case KEY_ESC:
4340 status = CANCEL;
4341 break;
4343 case ERR:
4344 break;
4346 default:
4347 if (pos >= sizeof(buf)) {
4348 report("Input string too long");
4349 return NULL;
4352 if (isprint(key))
4353 buf[pos++] = (char) key;
4357 /* Clear the status window */
4358 status_empty = FALSE;
4359 report("");
4361 if (status == CANCEL)
4362 return NULL;
4364 buf[pos++] = 0;
4366 return buf;
4370 * Repository references
4373 static struct ref *refs;
4374 static size_t refs_size;
4376 /* Id <-> ref store */
4377 static struct ref ***id_refs;
4378 static size_t id_refs_size;
4380 static struct ref **
4381 get_refs(char *id)
4383 struct ref ***tmp_id_refs;
4384 struct ref **ref_list = NULL;
4385 size_t ref_list_size = 0;
4386 size_t i;
4388 for (i = 0; i < id_refs_size; i++)
4389 if (!strcmp(id, id_refs[i][0]->id))
4390 return id_refs[i];
4392 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4393 if (!tmp_id_refs)
4394 return NULL;
4396 id_refs = tmp_id_refs;
4398 for (i = 0; i < refs_size; i++) {
4399 struct ref **tmp;
4401 if (strcmp(id, refs[i].id))
4402 continue;
4404 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4405 if (!tmp) {
4406 if (ref_list)
4407 free(ref_list);
4408 return NULL;
4411 ref_list = tmp;
4412 if (ref_list_size > 0)
4413 ref_list[ref_list_size - 1]->next = 1;
4414 ref_list[ref_list_size] = &refs[i];
4416 /* XXX: The properties of the commit chains ensures that we can
4417 * safely modify the shared ref. The repo references will
4418 * always be similar for the same id. */
4419 ref_list[ref_list_size]->next = 0;
4420 ref_list_size++;
4423 if (ref_list)
4424 id_refs[id_refs_size++] = ref_list;
4426 return ref_list;
4429 static int
4430 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4432 struct ref *ref;
4433 bool tag = FALSE;
4434 bool remote = FALSE;
4436 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4437 /* Commits referenced by tags has "^{}" appended. */
4438 if (name[namelen - 1] != '}')
4439 return OK;
4441 while (namelen > 0 && name[namelen] != '^')
4442 namelen--;
4444 tag = TRUE;
4445 namelen -= STRING_SIZE("refs/tags/");
4446 name += STRING_SIZE("refs/tags/");
4448 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4449 remote = TRUE;
4450 namelen -= STRING_SIZE("refs/remotes/");
4451 name += STRING_SIZE("refs/remotes/");
4453 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4454 namelen -= STRING_SIZE("refs/heads/");
4455 name += STRING_SIZE("refs/heads/");
4457 } else if (!strcmp(name, "HEAD")) {
4458 return OK;
4461 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4462 if (!refs)
4463 return ERR;
4465 ref = &refs[refs_size++];
4466 ref->name = malloc(namelen + 1);
4467 if (!ref->name)
4468 return ERR;
4470 strncpy(ref->name, name, namelen);
4471 ref->name[namelen] = 0;
4472 ref->tag = tag;
4473 ref->remote = remote;
4474 string_copy_rev(ref->id, id);
4476 return OK;
4479 static int
4480 load_refs(void)
4482 const char *cmd_env = getenv("TIG_LS_REMOTE");
4483 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4485 return read_properties(popen(cmd, "r"), "\t", read_ref);
4488 static int
4489 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4491 if (!strcmp(name, "i18n.commitencoding"))
4492 string_ncopy(opt_encoding, value, valuelen);
4494 if (!strcmp(name, "core.editor"))
4495 string_ncopy(opt_editor, value, valuelen);
4497 return OK;
4500 static int
4501 load_repo_config(void)
4503 return read_properties(popen(GIT_CONFIG " --list", "r"),
4504 "=", read_repo_config_option);
4507 static int
4508 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4510 if (!opt_git_dir[0])
4511 string_ncopy(opt_git_dir, name, namelen);
4512 else
4513 string_ncopy(opt_cdup, name, namelen);
4514 return OK;
4517 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4518 * must be the last one! */
4519 static int
4520 load_repo_info(void)
4522 return read_properties(popen("git rev-parse --git-dir --show-cdup 2>/dev/null", "r"),
4523 "=", read_repo_info);
4526 static int
4527 read_properties(FILE *pipe, const char *separators,
4528 int (*read_property)(char *, size_t, char *, size_t))
4530 char buffer[BUFSIZ];
4531 char *name;
4532 int state = OK;
4534 if (!pipe)
4535 return ERR;
4537 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4538 char *value;
4539 size_t namelen;
4540 size_t valuelen;
4542 name = chomp_string(name);
4543 namelen = strcspn(name, separators);
4545 if (name[namelen]) {
4546 name[namelen] = 0;
4547 value = chomp_string(name + namelen + 1);
4548 valuelen = strlen(value);
4550 } else {
4551 value = "";
4552 valuelen = 0;
4555 state = read_property(name, namelen, value, valuelen);
4558 if (state != ERR && ferror(pipe))
4559 state = ERR;
4561 pclose(pipe);
4563 return state;
4568 * Main
4571 static void __NORETURN
4572 quit(int sig)
4574 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4575 if (cursed)
4576 endwin();
4577 exit(0);
4580 static void __NORETURN
4581 die(const char *err, ...)
4583 va_list args;
4585 endwin();
4587 va_start(args, err);
4588 fputs("tig: ", stderr);
4589 vfprintf(stderr, err, args);
4590 fputs("\n", stderr);
4591 va_end(args);
4593 exit(1);
4597 main(int argc, char *argv[])
4599 struct view *view;
4600 enum request request;
4601 size_t i;
4603 signal(SIGINT, quit);
4605 if (setlocale(LC_ALL, "")) {
4606 char *codeset = nl_langinfo(CODESET);
4608 string_ncopy(opt_codeset, codeset, strlen(codeset));
4611 if (load_repo_info() == ERR)
4612 die("Failed to load repo info.");
4614 /* Require a git repository unless when running in pager mode. */
4615 if (!opt_git_dir[0])
4616 die("Not a git repository");
4618 if (load_options() == ERR)
4619 die("Failed to load user config.");
4621 /* Load the repo config file so options can be overwritten from
4622 * the command line. */
4623 if (load_repo_config() == ERR)
4624 die("Failed to load repo config.");
4626 if (!parse_options(argc, argv))
4627 return 0;
4629 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4630 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4631 if (opt_iconv == ICONV_NONE)
4632 die("Failed to initialize character set conversion");
4635 if (load_refs() == ERR)
4636 die("Failed to load refs.");
4638 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4639 view->cmd_env = getenv(view->cmd_env);
4641 request = opt_request;
4643 init_display();
4645 while (view_driver(display[current_view], request)) {
4646 int key;
4647 int i;
4649 foreach_view (view, i)
4650 update_view(view);
4652 /* Refresh, accept single keystroke of input */
4653 key = wgetch(status_win);
4655 /* wgetch() with nodelay() enabled returns ERR when there's no
4656 * input. */
4657 if (key == ERR) {
4658 request = REQ_NONE;
4659 continue;
4662 request = get_keybinding(display[current_view]->keymap, key);
4664 /* Some low-level request handling. This keeps access to
4665 * status_win restricted. */
4666 switch (request) {
4667 case REQ_PROMPT:
4669 char *cmd = read_prompt(":");
4671 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4672 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4673 opt_request = REQ_VIEW_DIFF;
4674 } else {
4675 opt_request = REQ_VIEW_PAGER;
4677 break;
4680 request = REQ_NONE;
4681 break;
4683 case REQ_SEARCH:
4684 case REQ_SEARCH_BACK:
4686 const char *prompt = request == REQ_SEARCH
4687 ? "/" : "?";
4688 char *search = read_prompt(prompt);
4690 if (search)
4691 string_ncopy(opt_search, search, strlen(search));
4692 else
4693 request = REQ_NONE;
4694 break;
4696 case REQ_SCREEN_RESIZE:
4698 int height, width;
4700 getmaxyx(stdscr, height, width);
4702 /* Resize the status view and let the view driver take
4703 * care of resizing the displayed views. */
4704 wresize(status_win, 1, width);
4705 mvwin(status_win, height - 1, 0);
4706 wrefresh(status_win);
4707 break;
4709 default:
4710 break;
4714 quit(0);
4716 return 0;