cf65447cb5320508af6147b38e27c3f3bc6df127
1 /* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifndef VERSION
15 #define VERSION "tig-0.3"
16 #endif
18 #ifndef DEBUG
19 #define NDEBUG
20 #endif
22 #include <assert.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <time.h>
33 #include <curses.h>
35 #if __GNUC__ >= 3
36 #define __NORETURN __attribute__((__noreturn__))
37 #else
38 #define __NORETURN
39 #endif
41 static void __NORETURN die(const char *err, ...);
42 static void report(const char *msg, ...);
43 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
44 static void set_nonblocking_input(bool loading);
45 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
47 #define ABS(x) ((x) >= 0 ? (x) : -(x))
48 #define MIN(x, y) ((x) < (y) ? (x) : (y))
50 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
51 #define STRING_SIZE(x) (sizeof(x) - 1)
53 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
54 #define SIZEOF_CMD 1024 /* Size of command buffer. */
55 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
57 /* This color name can be used to refer to the default term colors. */
58 #define COLOR_DEFAULT (-1)
60 /* The format and size of the date column in the main view. */
61 #define DATE_FORMAT "%Y-%m-%d %H:%M"
62 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
64 #define AUTHOR_COLS 20
66 /* The default interval between line numbers. */
67 #define NUMBER_INTERVAL 1
69 #define TABSIZE 8
71 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
73 #define TIG_LS_REMOTE \
74 "git ls-remote . 2>/dev/null"
76 #define TIG_DIFF_CMD \
77 "git show --patch-with-stat --find-copies-harder -B -C %s"
79 #define TIG_LOG_CMD \
80 "git log --cc --stat -n100 %s"
82 #define TIG_MAIN_CMD \
83 "git log --topo-order --stat --pretty=raw %s"
85 /* XXX: Needs to be defined to the empty string. */
86 #define TIG_HELP_CMD ""
87 #define TIG_PAGER_CMD ""
89 /* Some ascii-shorthands fitted into the ncurses namespace. */
90 #define KEY_TAB '\t'
91 #define KEY_RETURN '\r'
92 #define KEY_ESC 27
95 struct ref {
96 char *name; /* Ref name; tag or head names are shortened. */
97 char id[41]; /* Commit SHA1 ID */
98 unsigned int tag:1; /* Is it a tag? */
99 unsigned int next:1; /* For ref lists: are there more refs? */
100 };
102 static struct ref **get_refs(char *id);
104 struct int_map {
105 const char *name;
106 int namelen;
107 int value;
108 };
110 static int
111 set_from_int_map(struct int_map *map, size_t map_size,
112 int *value, const char *name, int namelen)
113 {
115 int i;
117 for (i = 0; i < map_size; i++)
118 if (namelen == map[i].namelen &&
119 !strncasecmp(name, map[i].name, namelen)) {
120 *value = map[i].value;
121 return OK;
122 }
124 return ERR;
125 }
128 /*
129 * String helpers
130 */
132 static inline void
133 string_ncopy(char *dst, const char *src, int dstlen)
134 {
135 strncpy(dst, src, dstlen - 1);
136 dst[dstlen - 1] = 0;
138 }
140 /* Shorthand for safely copying into a fixed buffer. */
141 #define string_copy(dst, src) \
142 string_ncopy(dst, src, sizeof(dst))
144 static char *
145 chomp_string(char *name)
146 {
147 int namelen;
149 while (isspace(*name))
150 name++;
152 namelen = strlen(name) - 1;
153 while (namelen > 0 && isspace(name[namelen]))
154 name[namelen--] = 0;
156 return name;
157 }
159 static bool
160 string_nformat(char *buf, size_t bufsize, int *bufpos, const char *fmt, ...)
161 {
162 va_list args;
163 int pos = bufpos ? *bufpos : 0;
165 va_start(args, fmt);
166 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
167 va_end(args);
169 if (bufpos)
170 *bufpos = pos;
172 return pos >= bufsize ? FALSE : TRUE;
173 }
175 #define string_format(buf, fmt, args...) \
176 string_nformat(buf, sizeof(buf), NULL, fmt, args)
178 #define string_format_from(buf, from, fmt, args...) \
179 string_nformat(buf, sizeof(buf), from, fmt, args)
181 static int
182 string_enum_compare(const char *str1, const char *str2, int len)
183 {
184 size_t i;
186 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
188 /* Diff-Header == DIFF_HEADER */
189 for (i = 0; i < len; i++) {
190 if (toupper(str1[i]) == toupper(str2[i]))
191 continue;
193 if (string_enum_sep(str1[i]) &&
194 string_enum_sep(str2[i]))
195 continue;
197 return str1[i] - str2[i];
198 }
200 return 0;
201 }
203 /* Shell quoting
204 *
205 * NOTE: The following is a slightly modified copy of the git project's shell
206 * quoting routines found in the quote.c file.
207 *
208 * Help to copy the thing properly quoted for the shell safety. any single
209 * quote is replaced with '\'', any exclamation point is replaced with '\!',
210 * and the whole thing is enclosed in a
211 *
212 * E.g.
213 * original sq_quote result
214 * name ==> name ==> 'name'
215 * a b ==> a b ==> 'a b'
216 * a'b ==> a'\''b ==> 'a'\''b'
217 * a!b ==> a'\!'b ==> 'a'\!'b'
218 */
220 static size_t
221 sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
222 {
223 char c;
225 #define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
227 BUFPUT('\'');
228 while ((c = *src++)) {
229 if (c == '\'' || c == '!') {
230 BUFPUT('\'');
231 BUFPUT('\\');
232 BUFPUT(c);
233 BUFPUT('\'');
234 } else {
235 BUFPUT(c);
236 }
237 }
238 BUFPUT('\'');
240 return bufsize;
241 }
244 /*
245 * User requests
246 */
248 #define REQ_INFO \
249 /* XXX: Keep the view request first and in sync with views[]. */ \
250 REQ_GROUP("View switching") \
251 REQ_(VIEW_MAIN, "Show main view"), \
252 REQ_(VIEW_DIFF, "Show diff view"), \
253 REQ_(VIEW_LOG, "Show log view"), \
254 REQ_(VIEW_HELP, "Show help page"), \
255 REQ_(VIEW_PAGER, "Show pager view"), \
256 \
257 REQ_GROUP("View manipulation") \
258 REQ_(ENTER, "Enter current line and scroll"), \
259 REQ_(NEXT, "Move to next"), \
260 REQ_(PREVIOUS, "Move to previous"), \
261 REQ_(VIEW_NEXT, "Move focus to next view"), \
262 REQ_(VIEW_CLOSE, "Close the current view"), \
263 REQ_(QUIT, "Close all views and quit"), \
264 \
265 REQ_GROUP("Cursor navigation") \
266 REQ_(MOVE_UP, "Move cursor one line up"), \
267 REQ_(MOVE_DOWN, "Move cursor one line down"), \
268 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
269 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
270 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
271 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
272 \
273 REQ_GROUP("Scrolling") \
274 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
275 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
276 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
277 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
278 \
279 REQ_GROUP("Misc") \
280 REQ_(PROMPT, "Bring up the prompt"), \
281 REQ_(SCREEN_UPDATE, "Update the screen"), \
282 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
283 REQ_(SCREEN_RESIZE, "Resize the screen"), \
284 REQ_(SHOW_VERSION, "Show version information"), \
285 REQ_(STOP_LOADING, "Stop all loading views"), \
286 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
287 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization")
290 /* User action requests. */
291 enum request {
292 #define REQ_GROUP(help)
293 #define REQ_(req, help) REQ_##req
295 /* Offset all requests to avoid conflicts with ncurses getch values. */
296 REQ_OFFSET = KEY_MAX + 1,
297 REQ_INFO,
298 REQ_UNKNOWN,
300 #undef REQ_GROUP
301 #undef REQ_
302 };
304 struct request_info {
305 enum request request;
306 char *name;
307 int namelen;
308 char *help;
309 };
311 static struct request_info req_info[] = {
312 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
313 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
314 REQ_INFO
315 #undef REQ_GROUP
316 #undef REQ_
317 };
319 static enum request
320 get_request(const char *name)
321 {
322 int namelen = strlen(name);
323 int i;
325 for (i = 0; i < ARRAY_SIZE(req_info); i++)
326 if (req_info[i].namelen == namelen &&
327 !string_enum_compare(req_info[i].name, name, namelen))
328 return req_info[i].request;
330 return REQ_UNKNOWN;
331 }
334 /*
335 * Options
336 */
338 static const char usage[] =
339 VERSION " (" __DATE__ ")\n"
340 "\n"
341 "Usage: tig [options]\n"
342 " or: tig [options] [--] [git log options]\n"
343 " or: tig [options] log [git log options]\n"
344 " or: tig [options] diff [git diff options]\n"
345 " or: tig [options] show [git show options]\n"
346 " or: tig [options] < [git command output]\n"
347 "\n"
348 "Options:\n"
349 " -l Start up in log view\n"
350 " -d Start up in diff view\n"
351 " -n[I], --line-number[=I] Show line numbers with given interval\n"
352 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
353 " -- Mark end of tig options\n"
354 " -v, --version Show version and exit\n"
355 " -h, --help Show help message and exit\n";
357 /* Option and state variables. */
358 static bool opt_line_number = FALSE;
359 static bool opt_rev_graph = TRUE;
360 static int opt_num_interval = NUMBER_INTERVAL;
361 static int opt_tab_size = TABSIZE;
362 static enum request opt_request = REQ_VIEW_MAIN;
363 static char opt_cmd[SIZEOF_CMD] = "";
364 static char opt_encoding[20] = "";
365 static bool opt_utf8 = TRUE;
366 static FILE *opt_pipe = NULL;
368 enum option_type {
369 OPT_NONE,
370 OPT_INT,
371 };
373 static bool
374 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
375 {
376 va_list args;
377 char *value = "";
378 int *number;
380 if (opt[0] != '-')
381 return FALSE;
383 if (opt[1] == '-') {
384 int namelen = strlen(name);
386 opt += 2;
388 if (strncmp(opt, name, namelen))
389 return FALSE;
391 if (opt[namelen] == '=')
392 value = opt + namelen + 1;
394 } else {
395 if (!short_name || opt[1] != short_name)
396 return FALSE;
397 value = opt + 2;
398 }
400 va_start(args, type);
401 if (type == OPT_INT) {
402 number = va_arg(args, int *);
403 if (isdigit(*value))
404 *number = atoi(value);
405 }
406 va_end(args);
408 return TRUE;
409 }
411 /* Returns the index of log or diff command or -1 to exit. */
412 static bool
413 parse_options(int argc, char *argv[])
414 {
415 int i;
417 for (i = 1; i < argc; i++) {
418 char *opt = argv[i];
420 if (!strcmp(opt, "-l")) {
421 opt_request = REQ_VIEW_LOG;
422 continue;
423 }
425 if (!strcmp(opt, "-d")) {
426 opt_request = REQ_VIEW_DIFF;
427 continue;
428 }
430 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
431 opt_line_number = TRUE;
432 continue;
433 }
435 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
436 opt_tab_size = MIN(opt_tab_size, TABSIZE);
437 continue;
438 }
440 if (check_option(opt, 'v', "version", OPT_NONE)) {
441 printf("tig version %s\n", VERSION);
442 return FALSE;
443 }
445 if (check_option(opt, 'h', "help", OPT_NONE)) {
446 printf(usage);
447 return FALSE;
448 }
450 if (!strcmp(opt, "--")) {
451 i++;
452 break;
453 }
455 if (!strcmp(opt, "log") ||
456 !strcmp(opt, "diff") ||
457 !strcmp(opt, "show")) {
458 opt_request = opt[0] == 'l'
459 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
460 break;
461 }
463 if (opt[0] && opt[0] != '-')
464 break;
466 die("unknown option '%s'\n\n%s", opt, usage);
467 }
469 if (!isatty(STDIN_FILENO)) {
470 opt_request = REQ_VIEW_PAGER;
471 opt_pipe = stdin;
473 } else if (i < argc) {
474 size_t buf_size;
476 if (opt_request == REQ_VIEW_MAIN)
477 /* XXX: This is vulnerable to the user overriding
478 * options required for the main view parser. */
479 string_copy(opt_cmd, "git log --stat --pretty=raw");
480 else
481 string_copy(opt_cmd, "git");
482 buf_size = strlen(opt_cmd);
484 while (buf_size < sizeof(opt_cmd) && i < argc) {
485 opt_cmd[buf_size++] = ' ';
486 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
487 }
489 if (buf_size >= sizeof(opt_cmd))
490 die("command too long");
492 opt_cmd[buf_size] = 0;
494 }
496 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
497 opt_utf8 = FALSE;
499 return TRUE;
500 }
503 /*
504 * Line-oriented content detection.
505 */
507 #define LINE_INFO \
508 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
509 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
510 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
511 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
512 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
513 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
514 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
515 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
516 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
517 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
518 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
519 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
520 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
521 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
522 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
523 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
524 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
525 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
526 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
527 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
528 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
529 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
530 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
531 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
532 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
533 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
534 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
535 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
536 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
537 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
538 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
539 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
540 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
541 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
542 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
543 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
544 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
545 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
547 enum line_type {
548 #define LINE(type, line, fg, bg, attr) \
549 LINE_##type
550 LINE_INFO
551 #undef LINE
552 };
554 struct line_info {
555 const char *name; /* Option name. */
556 int namelen; /* Size of option name. */
557 const char *line; /* The start of line to match. */
558 int linelen; /* Size of string to match. */
559 int fg, bg, attr; /* Color and text attributes for the lines. */
560 };
562 static struct line_info line_info[] = {
563 #define LINE(type, line, fg, bg, attr) \
564 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
565 LINE_INFO
566 #undef LINE
567 };
569 static enum line_type
570 get_line_type(char *line)
571 {
572 int linelen = strlen(line);
573 enum line_type type;
575 for (type = 0; type < ARRAY_SIZE(line_info); type++)
576 /* Case insensitive search matches Signed-off-by lines better. */
577 if (linelen >= line_info[type].linelen &&
578 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
579 return type;
581 return LINE_DEFAULT;
582 }
584 static inline int
585 get_line_attr(enum line_type type)
586 {
587 assert(type < ARRAY_SIZE(line_info));
588 return COLOR_PAIR(type) | line_info[type].attr;
589 }
591 static struct line_info *
592 get_line_info(char *name, int namelen)
593 {
594 enum line_type type;
596 for (type = 0; type < ARRAY_SIZE(line_info); type++)
597 if (namelen == line_info[type].namelen &&
598 !string_enum_compare(line_info[type].name, name, namelen))
599 return &line_info[type];
601 return NULL;
602 }
604 static void
605 init_colors(void)
606 {
607 int default_bg = COLOR_BLACK;
608 int default_fg = COLOR_WHITE;
609 enum line_type type;
611 start_color();
613 if (use_default_colors() != ERR) {
614 default_bg = -1;
615 default_fg = -1;
616 }
618 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
619 struct line_info *info = &line_info[type];
620 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
621 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
623 init_pair(type, fg, bg);
624 }
625 }
627 struct line {
628 enum line_type type;
629 void *data; /* User data */
630 };
633 /*
634 * Keys
635 */
637 struct keybinding {
638 int alias;
639 enum request request;
640 struct keybinding *next;
641 };
643 static struct keybinding default_keybindings[] = {
644 /* View switching */
645 { 'm', REQ_VIEW_MAIN },
646 { 'd', REQ_VIEW_DIFF },
647 { 'l', REQ_VIEW_LOG },
648 { 'p', REQ_VIEW_PAGER },
649 { 'h', REQ_VIEW_HELP },
650 { '?', REQ_VIEW_HELP },
652 /* View manipulation */
653 { 'q', REQ_VIEW_CLOSE },
654 { KEY_TAB, REQ_VIEW_NEXT },
655 { KEY_RETURN, REQ_ENTER },
656 { KEY_UP, REQ_PREVIOUS },
657 { KEY_DOWN, REQ_NEXT },
659 /* Cursor navigation */
660 { 'k', REQ_MOVE_UP },
661 { 'j', REQ_MOVE_DOWN },
662 { KEY_HOME, REQ_MOVE_FIRST_LINE },
663 { KEY_END, REQ_MOVE_LAST_LINE },
664 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
665 { ' ', REQ_MOVE_PAGE_DOWN },
666 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
667 { 'b', REQ_MOVE_PAGE_UP },
668 { '-', REQ_MOVE_PAGE_UP },
670 /* Scrolling */
671 { KEY_IC, REQ_SCROLL_LINE_UP },
672 { KEY_DC, REQ_SCROLL_LINE_DOWN },
673 { 'w', REQ_SCROLL_PAGE_UP },
674 { 's', REQ_SCROLL_PAGE_DOWN },
676 /* Misc */
677 { 'Q', REQ_QUIT },
678 { 'z', REQ_STOP_LOADING },
679 { 'v', REQ_SHOW_VERSION },
680 { 'r', REQ_SCREEN_REDRAW },
681 { 'n', REQ_TOGGLE_LINENO },
682 { 'g', REQ_TOGGLE_REV_GRAPH},
683 { ':', REQ_PROMPT },
685 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
686 { ERR, REQ_SCREEN_UPDATE },
688 /* Use the ncurses SIGWINCH handler. */
689 { KEY_RESIZE, REQ_SCREEN_RESIZE },
690 };
692 #define KEYMAP_INFO \
693 KEYMAP_(GENERIC), \
694 KEYMAP_(MAIN), \
695 KEYMAP_(DIFF), \
696 KEYMAP_(LOG), \
697 KEYMAP_(PAGER), \
698 KEYMAP_(HELP) \
700 enum keymap {
701 #define KEYMAP_(name) KEYMAP_##name
702 KEYMAP_INFO
703 #undef KEYMAP_
704 };
706 static struct int_map keymap_table[] = {
707 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
708 KEYMAP_INFO
709 #undef KEYMAP_
710 };
712 #define set_keymap(map, name) \
713 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
715 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
717 static void
718 add_keybinding(enum keymap keymap, enum request request, int key)
719 {
720 struct keybinding *keybinding;
722 keybinding = calloc(1, sizeof(*keybinding));
723 if (!keybinding)
724 die("Failed to allocate keybinding");
726 keybinding->alias = key;
727 keybinding->request = request;
728 keybinding->next = keybindings[keymap];
729 keybindings[keymap] = keybinding;
730 }
732 /* Looks for a key binding first in the given map, then in the generic map, and
733 * lastly in the default keybindings. */
734 static enum request
735 get_keybinding(enum keymap keymap, int key)
736 {
737 struct keybinding *kbd;
738 int i;
740 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
741 if (kbd->alias == key)
742 return kbd->request;
744 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
745 if (kbd->alias == key)
746 return kbd->request;
748 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
749 if (default_keybindings[i].alias == key)
750 return default_keybindings[i].request;
752 return (enum request) key;
753 }
756 struct key {
757 char *name;
758 int value;
759 };
761 static struct key key_table[] = {
762 { "Enter", KEY_RETURN },
763 { "Space", ' ' },
764 { "Backspace", KEY_BACKSPACE },
765 { "Tab", KEY_TAB },
766 { "Escape", KEY_ESC },
767 { "Left", KEY_LEFT },
768 { "Right", KEY_RIGHT },
769 { "Up", KEY_UP },
770 { "Down", KEY_DOWN },
771 { "Insert", KEY_IC },
772 { "Delete", KEY_DC },
773 { "Home", KEY_HOME },
774 { "End", KEY_END },
775 { "PageUp", KEY_PPAGE },
776 { "PageDown", KEY_NPAGE },
777 { "F1", KEY_F(1) },
778 { "F2", KEY_F(2) },
779 { "F3", KEY_F(3) },
780 { "F4", KEY_F(4) },
781 { "F5", KEY_F(5) },
782 { "F6", KEY_F(6) },
783 { "F7", KEY_F(7) },
784 { "F8", KEY_F(8) },
785 { "F9", KEY_F(9) },
786 { "F10", KEY_F(10) },
787 { "F11", KEY_F(11) },
788 { "F12", KEY_F(12) },
789 };
791 static int
792 get_key_value(const char *name)
793 {
794 int i;
796 for (i = 0; i < ARRAY_SIZE(key_table); i++)
797 if (!strcasecmp(key_table[i].name, name))
798 return key_table[i].value;
800 if (strlen(name) == 1 && isprint(*name))
801 return (int) *name;
803 return ERR;
804 }
806 static char *
807 get_key(enum request request)
808 {
809 static char buf[BUFSIZ];
810 static char key_char[] = "'X'";
811 int pos = 0;
812 char *sep = " ";
813 int i;
815 buf[pos] = 0;
817 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
818 struct keybinding *keybinding = &default_keybindings[i];
819 char *seq = NULL;
820 int key;
822 if (keybinding->request != request)
823 continue;
825 for (key = 0; key < ARRAY_SIZE(key_table); key++)
826 if (key_table[key].value == keybinding->alias)
827 seq = key_table[key].name;
829 if (seq == NULL &&
830 keybinding->alias < 127 &&
831 isprint(keybinding->alias)) {
832 key_char[1] = (char) keybinding->alias;
833 seq = key_char;
834 }
836 if (!seq)
837 seq = "'?'";
839 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
840 return "Too many keybindings!";
841 sep = ", ";
842 }
844 return buf;
845 }
848 /*
849 * User config file handling.
850 */
852 static struct int_map color_map[] = {
853 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
854 COLOR_MAP(DEFAULT),
855 COLOR_MAP(BLACK),
856 COLOR_MAP(BLUE),
857 COLOR_MAP(CYAN),
858 COLOR_MAP(GREEN),
859 COLOR_MAP(MAGENTA),
860 COLOR_MAP(RED),
861 COLOR_MAP(WHITE),
862 COLOR_MAP(YELLOW),
863 };
865 #define set_color(color, name) \
866 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
868 static struct int_map attr_map[] = {
869 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
870 ATTR_MAP(NORMAL),
871 ATTR_MAP(BLINK),
872 ATTR_MAP(BOLD),
873 ATTR_MAP(DIM),
874 ATTR_MAP(REVERSE),
875 ATTR_MAP(STANDOUT),
876 ATTR_MAP(UNDERLINE),
877 };
879 #define set_attribute(attr, name) \
880 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
882 static int config_lineno;
883 static bool config_errors;
884 static char *config_msg;
886 /* Wants: object fgcolor bgcolor [attr] */
887 static int
888 option_color_command(int argc, char *argv[])
889 {
890 struct line_info *info;
892 if (argc != 3 && argc != 4) {
893 config_msg = "Wrong number of arguments given to color command";
894 return ERR;
895 }
897 info = get_line_info(argv[0], strlen(argv[0]));
898 if (!info) {
899 config_msg = "Unknown color name";
900 return ERR;
901 }
903 if (set_color(&info->fg, argv[1]) == ERR ||
904 set_color(&info->bg, argv[2]) == ERR) {
905 config_msg = "Unknown color";
906 return ERR;
907 }
909 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
910 config_msg = "Unknown attribute";
911 return ERR;
912 }
914 return OK;
915 }
917 /* Wants: name = value */
918 static int
919 option_set_command(int argc, char *argv[])
920 {
921 if (argc != 3) {
922 config_msg = "Wrong number of arguments given to set command";
923 return ERR;
924 }
926 if (strcmp(argv[1], "=")) {
927 config_msg = "No value assigned";
928 return ERR;
929 }
931 if (!strcmp(argv[0], "show-rev-graph")) {
932 opt_rev_graph = (!strcmp(argv[2], "1") ||
933 !strcmp(argv[2], "true") ||
934 !strcmp(argv[2], "yes"));
935 return OK;
936 }
938 if (!strcmp(argv[0], "line-number-interval")) {
939 opt_num_interval = atoi(argv[2]);
940 return OK;
941 }
943 if (!strcmp(argv[0], "tab-size")) {
944 opt_tab_size = atoi(argv[2]);
945 return OK;
946 }
948 if (!strcmp(argv[0], "commit-encoding")) {
949 string_copy(opt_encoding, argv[2]);
950 return OK;
951 }
953 config_msg = "Unknown variable name";
954 return ERR;
955 }
957 /* Wants: mode request key */
958 static int
959 option_bind_command(int argc, char *argv[])
960 {
961 enum request request;
962 int keymap;
963 int key;
965 if (argc != 3) {
966 config_msg = "Wrong number of arguments given to bind command";
967 return ERR;
968 }
970 if (set_keymap(&keymap, argv[0]) == ERR) {
971 config_msg = "Unknown key map";
972 return ERR;
973 }
975 key = get_key_value(argv[1]);
976 if (key == ERR) {
977 config_msg = "Unknown key";
978 return ERR;
979 }
981 request = get_request(argv[2]);
982 if (request == REQ_UNKNOWN) {
983 config_msg = "Unknown request name";
984 return ERR;
985 }
987 add_keybinding(keymap, request, key);
989 return OK;
990 }
992 static int
993 set_option(char *opt, char *value)
994 {
995 char *argv[16];
996 int valuelen;
997 int argc = 0;
999 /* Tokenize */
1000 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1001 argv[argc++] = value;
1003 value += valuelen;
1004 if (!*value)
1005 break;
1007 *value++ = 0;
1008 while (isspace(*value))
1009 value++;
1010 }
1012 if (!strcmp(opt, "color"))
1013 return option_color_command(argc, argv);
1015 if (!strcmp(opt, "set"))
1016 return option_set_command(argc, argv);
1018 if (!strcmp(opt, "bind"))
1019 return option_bind_command(argc, argv);
1021 config_msg = "Unknown option command";
1022 return ERR;
1023 }
1025 static int
1026 read_option(char *opt, int optlen, char *value, int valuelen)
1027 {
1028 int status = OK;
1030 config_lineno++;
1031 config_msg = "Internal error";
1033 /* Check for comment markers, since read_properties() will
1034 * only ensure opt and value are split at first " \t". */
1035 optlen = strcspn(opt, "#;");
1036 if (optlen == 0)
1037 return OK;
1039 if (opt[optlen] != 0) {
1040 config_msg = "No option value";
1041 status = ERR;
1043 } else {
1044 /* Look for comment endings in the value. */
1045 int len = strcspn(value, "#;");
1047 if (len < valuelen) {
1048 valuelen = len;
1049 value[valuelen] = 0;
1050 }
1052 status = set_option(opt, value);
1053 }
1055 if (status == ERR) {
1056 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1057 config_lineno, optlen, opt, config_msg);
1058 config_errors = TRUE;
1059 }
1061 /* Always keep going if errors are encountered. */
1062 return OK;
1063 }
1065 static int
1066 load_options(void)
1067 {
1068 char *home = getenv("HOME");
1069 char buf[1024];
1070 FILE *file;
1072 config_lineno = 0;
1073 config_errors = FALSE;
1075 if (!home || !string_format(buf, "%s/.tigrc", home))
1076 return ERR;
1078 /* It's ok that the file doesn't exist. */
1079 file = fopen(buf, "r");
1080 if (!file)
1081 return OK;
1083 if (read_properties(file, " \t", read_option) == ERR ||
1084 config_errors == TRUE)
1085 fprintf(stderr, "Errors while loading %s.\n", buf);
1087 return OK;
1088 }
1091 /*
1092 * The viewer
1093 */
1095 struct view;
1096 struct view_ops;
1098 /* The display array of active views and the index of the current view. */
1099 static struct view *display[2];
1100 static unsigned int current_view;
1102 #define foreach_view(view, i) \
1103 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1105 #define displayed_views() (display[1] != NULL ? 2 : 1)
1107 /* Current head and commit ID */
1108 static char ref_commit[SIZEOF_REF] = "HEAD";
1109 static char ref_head[SIZEOF_REF] = "HEAD";
1111 struct view {
1112 const char *name; /* View name */
1113 const char *cmd_fmt; /* Default command line format */
1114 const char *cmd_env; /* Command line set via environment */
1115 const char *id; /* Points to either of ref_{head,commit} */
1117 struct view_ops *ops; /* View operations */
1119 enum keymap keymap; /* What keymap does this view have */
1121 char cmd[SIZEOF_CMD]; /* Command buffer */
1122 char ref[SIZEOF_REF]; /* Hovered commit reference */
1123 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1125 int height, width; /* The width and height of the main window */
1126 WINDOW *win; /* The main window */
1127 WINDOW *title; /* The title window living below the main window */
1129 /* Navigation */
1130 unsigned long offset; /* Offset of the window top */
1131 unsigned long lineno; /* Current line number */
1133 /* If non-NULL, points to the view that opened this view. If this view
1134 * is closed tig will switch back to the parent view. */
1135 struct view *parent;
1137 /* Buffering */
1138 unsigned long lines; /* Total number of lines */
1139 struct line *line; /* Line index */
1140 unsigned long line_size;/* Total number of allocated lines */
1141 unsigned int digits; /* Number of digits in the lines member. */
1143 /* Loading */
1144 FILE *pipe;
1145 time_t start_time;
1146 };
1148 struct view_ops {
1149 /* What type of content being displayed. Used in the title bar. */
1150 const char *type;
1151 /* Draw one line; @lineno must be < view->height. */
1152 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1153 /* Read one line; updates view->line. */
1154 bool (*read)(struct view *view, char *data);
1155 /* Depending on view, change display based on current line. */
1156 bool (*enter)(struct view *view, struct line *line);
1157 };
1159 static struct view_ops pager_ops;
1160 static struct view_ops main_ops;
1162 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1163 { name, cmd, #env, ref, ops, map}
1165 #define VIEW_(id, name, ops, ref) \
1166 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1169 static struct view views[] = {
1170 VIEW_(MAIN, "main", &main_ops, ref_head),
1171 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1172 VIEW_(LOG, "log", &pager_ops, ref_head),
1173 VIEW_(HELP, "help", &pager_ops, "static"),
1174 VIEW_(PAGER, "pager", &pager_ops, "static"),
1175 };
1177 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1180 static bool
1181 draw_view_line(struct view *view, unsigned int lineno)
1182 {
1183 if (view->offset + lineno >= view->lines)
1184 return FALSE;
1186 return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
1187 }
1189 static void
1190 redraw_view_from(struct view *view, int lineno)
1191 {
1192 assert(0 <= lineno && lineno < view->height);
1194 for (; lineno < view->height; lineno++) {
1195 if (!draw_view_line(view, lineno))
1196 break;
1197 }
1199 redrawwin(view->win);
1200 wrefresh(view->win);
1201 }
1203 static void
1204 redraw_view(struct view *view)
1205 {
1206 wclear(view->win);
1207 redraw_view_from(view, 0);
1208 }
1211 static void
1212 update_view_title(struct view *view)
1213 {
1214 if (view == display[current_view])
1215 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1216 else
1217 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1219 werase(view->title);
1220 wmove(view->title, 0, 0);
1222 if (*view->ref)
1223 wprintw(view->title, "[%s] %s", view->name, view->ref);
1224 else
1225 wprintw(view->title, "[%s]", view->name);
1227 if (view->lines || view->pipe) {
1228 unsigned int view_lines = view->offset + view->height;
1229 unsigned int lines = view->lines
1230 ? MIN(view_lines, view->lines) * 100 / view->lines
1231 : 0;
1233 wprintw(view->title, " - %s %d of %d (%d%%)",
1234 view->ops->type,
1235 view->lineno + 1,
1236 view->lines,
1237 lines);
1238 }
1240 if (view->pipe) {
1241 time_t secs = time(NULL) - view->start_time;
1243 /* Three git seconds are a long time ... */
1244 if (secs > 2)
1245 wprintw(view->title, " %lds", secs);
1246 }
1248 wmove(view->title, 0, view->width - 1);
1249 wrefresh(view->title);
1250 }
1252 static void
1253 resize_display(void)
1254 {
1255 int offset, i;
1256 struct view *base = display[0];
1257 struct view *view = display[1] ? display[1] : display[0];
1259 /* Setup window dimensions */
1261 getmaxyx(stdscr, base->height, base->width);
1263 /* Make room for the status window. */
1264 base->height -= 1;
1266 if (view != base) {
1267 /* Horizontal split. */
1268 view->width = base->width;
1269 view->height = SCALE_SPLIT_VIEW(base->height);
1270 base->height -= view->height;
1272 /* Make room for the title bar. */
1273 view->height -= 1;
1274 }
1276 /* Make room for the title bar. */
1277 base->height -= 1;
1279 offset = 0;
1281 foreach_view (view, i) {
1282 if (!view->win) {
1283 view->win = newwin(view->height, 0, offset, 0);
1284 if (!view->win)
1285 die("Failed to create %s view", view->name);
1287 scrollok(view->win, TRUE);
1289 view->title = newwin(1, 0, offset + view->height, 0);
1290 if (!view->title)
1291 die("Failed to create title window");
1293 } else {
1294 wresize(view->win, view->height, view->width);
1295 mvwin(view->win, offset, 0);
1296 mvwin(view->title, offset + view->height, 0);
1297 }
1299 offset += view->height + 1;
1300 }
1301 }
1303 static void
1304 redraw_display(void)
1305 {
1306 struct view *view;
1307 int i;
1309 foreach_view (view, i) {
1310 redraw_view(view);
1311 update_view_title(view);
1312 }
1313 }
1315 static void
1316 update_display_cursor(void)
1317 {
1318 struct view *view = display[current_view];
1320 /* Move the cursor to the right-most column of the cursor line.
1321 *
1322 * XXX: This could turn out to be a bit expensive, but it ensures that
1323 * the cursor does not jump around. */
1324 if (view->lines) {
1325 wmove(view->win, view->lineno - view->offset, view->width - 1);
1326 wrefresh(view->win);
1327 }
1328 }
1330 /*
1331 * Navigation
1332 */
1334 /* Scrolling backend */
1335 static void
1336 do_scroll_view(struct view *view, int lines, bool redraw)
1337 {
1338 /* The rendering expects the new offset. */
1339 view->offset += lines;
1341 assert(0 <= view->offset && view->offset < view->lines);
1342 assert(lines);
1344 /* Redraw the whole screen if scrolling is pointless. */
1345 if (view->height < ABS(lines)) {
1346 redraw_view(view);
1348 } else {
1349 int line = lines > 0 ? view->height - lines : 0;
1350 int end = line + ABS(lines);
1352 wscrl(view->win, lines);
1354 for (; line < end; line++) {
1355 if (!draw_view_line(view, line))
1356 break;
1357 }
1358 }
1360 /* Move current line into the view. */
1361 if (view->lineno < view->offset) {
1362 view->lineno = view->offset;
1363 draw_view_line(view, 0);
1365 } else if (view->lineno >= view->offset + view->height) {
1366 if (view->lineno == view->offset + view->height) {
1367 /* Clear the hidden line so it doesn't show if the view
1368 * is scrolled up. */
1369 wmove(view->win, view->height, 0);
1370 wclrtoeol(view->win);
1371 }
1372 view->lineno = view->offset + view->height - 1;
1373 draw_view_line(view, view->lineno - view->offset);
1374 }
1376 assert(view->offset <= view->lineno && view->lineno < view->lines);
1378 if (!redraw)
1379 return;
1381 redrawwin(view->win);
1382 wrefresh(view->win);
1383 report("");
1384 }
1386 /* Scroll frontend */
1387 static void
1388 scroll_view(struct view *view, enum request request)
1389 {
1390 int lines = 1;
1392 switch (request) {
1393 case REQ_SCROLL_PAGE_DOWN:
1394 lines = view->height;
1395 case REQ_SCROLL_LINE_DOWN:
1396 if (view->offset + lines > view->lines)
1397 lines = view->lines - view->offset;
1399 if (lines == 0 || view->offset + view->height >= view->lines) {
1400 report("Cannot scroll beyond the last line");
1401 return;
1402 }
1403 break;
1405 case REQ_SCROLL_PAGE_UP:
1406 lines = view->height;
1407 case REQ_SCROLL_LINE_UP:
1408 if (lines > view->offset)
1409 lines = view->offset;
1411 if (lines == 0) {
1412 report("Cannot scroll beyond the first line");
1413 return;
1414 }
1416 lines = -lines;
1417 break;
1419 default:
1420 die("request %d not handled in switch", request);
1421 }
1423 do_scroll_view(view, lines, TRUE);
1424 }
1426 /* Cursor moving */
1427 static void
1428 move_view(struct view *view, enum request request, bool redraw)
1429 {
1430 int steps;
1432 switch (request) {
1433 case REQ_MOVE_FIRST_LINE:
1434 steps = -view->lineno;
1435 break;
1437 case REQ_MOVE_LAST_LINE:
1438 steps = view->lines - view->lineno - 1;
1439 break;
1441 case REQ_MOVE_PAGE_UP:
1442 steps = view->height > view->lineno
1443 ? -view->lineno : -view->height;
1444 break;
1446 case REQ_MOVE_PAGE_DOWN:
1447 steps = view->lineno + view->height >= view->lines
1448 ? view->lines - view->lineno - 1 : view->height;
1449 break;
1451 case REQ_MOVE_UP:
1452 steps = -1;
1453 break;
1455 case REQ_MOVE_DOWN:
1456 steps = 1;
1457 break;
1459 default:
1460 die("request %d not handled in switch", request);
1461 }
1463 if (steps <= 0 && view->lineno == 0) {
1464 report("Cannot move beyond the first line");
1465 return;
1467 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1468 report("Cannot move beyond the last line");
1469 return;
1470 }
1472 /* Move the current line */
1473 view->lineno += steps;
1474 assert(0 <= view->lineno && view->lineno < view->lines);
1476 /* Repaint the old "current" line if we be scrolling */
1477 if (ABS(steps) < view->height) {
1478 int prev_lineno = view->lineno - steps - view->offset;
1480 wmove(view->win, prev_lineno, 0);
1481 wclrtoeol(view->win);
1482 draw_view_line(view, prev_lineno);
1483 }
1485 /* Check whether the view needs to be scrolled */
1486 if (view->lineno < view->offset ||
1487 view->lineno >= view->offset + view->height) {
1488 if (steps < 0 && -steps > view->offset) {
1489 steps = -view->offset;
1491 } else if (steps > 0) {
1492 if (view->lineno == view->lines - 1 &&
1493 view->lines > view->height) {
1494 steps = view->lines - view->offset - 1;
1495 if (steps >= view->height)
1496 steps -= view->height - 1;
1497 }
1498 }
1500 do_scroll_view(view, steps, redraw);
1501 return;
1502 }
1504 /* Draw the current line */
1505 draw_view_line(view, view->lineno - view->offset);
1507 if (!redraw)
1508 return;
1510 redrawwin(view->win);
1511 wrefresh(view->win);
1512 report("");
1513 }
1516 /*
1517 * Incremental updating
1518 */
1520 static void
1521 end_update(struct view *view)
1522 {
1523 if (!view->pipe)
1524 return;
1525 set_nonblocking_input(FALSE);
1526 if (view->pipe == stdin)
1527 fclose(view->pipe);
1528 else
1529 pclose(view->pipe);
1530 view->pipe = NULL;
1531 }
1533 static bool
1534 begin_update(struct view *view)
1535 {
1536 const char *id = view->id;
1538 if (view->pipe)
1539 end_update(view);
1541 if (opt_cmd[0]) {
1542 string_copy(view->cmd, opt_cmd);
1543 opt_cmd[0] = 0;
1544 /* When running random commands, the view ref could have become
1545 * invalid so clear it. */
1546 view->ref[0] = 0;
1547 } else {
1548 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1550 if (!string_format(view->cmd, format, id, id, id, id, id))
1551 return FALSE;
1552 }
1554 /* Special case for the pager view. */
1555 if (opt_pipe) {
1556 view->pipe = opt_pipe;
1557 opt_pipe = NULL;
1558 } else {
1559 view->pipe = popen(view->cmd, "r");
1560 }
1562 if (!view->pipe)
1563 return FALSE;
1565 set_nonblocking_input(TRUE);
1567 view->offset = 0;
1568 view->lines = 0;
1569 view->lineno = 0;
1570 string_copy(view->vid, id);
1572 if (view->line) {
1573 int i;
1575 for (i = 0; i < view->lines; i++)
1576 if (view->line[i].data)
1577 free(view->line[i].data);
1579 free(view->line);
1580 view->line = NULL;
1581 }
1583 view->start_time = time(NULL);
1585 return TRUE;
1586 }
1588 static struct line *
1589 realloc_lines(struct view *view, size_t line_size)
1590 {
1591 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1593 if (!tmp)
1594 return NULL;
1596 view->line = tmp;
1597 view->line_size = line_size;
1598 return view->line;
1599 }
1601 static bool
1602 update_view(struct view *view)
1603 {
1604 char buffer[BUFSIZ];
1605 char *line;
1606 /* The number of lines to read. If too low it will cause too much
1607 * redrawing (and possible flickering), if too high responsiveness
1608 * will suffer. */
1609 unsigned long lines = view->height;
1610 int redraw_from = -1;
1612 if (!view->pipe)
1613 return TRUE;
1615 /* Only redraw if lines are visible. */
1616 if (view->offset + view->height >= view->lines)
1617 redraw_from = view->lines - view->offset;
1619 if (!realloc_lines(view, view->lines + lines))
1620 goto alloc_error;
1622 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1623 int linelen = strlen(line);
1625 if (linelen)
1626 line[linelen - 1] = 0;
1628 if (!view->ops->read(view, line))
1629 goto alloc_error;
1631 if (lines-- == 1)
1632 break;
1633 }
1635 {
1636 int digits;
1638 lines = view->lines;
1639 for (digits = 0; lines; digits++)
1640 lines /= 10;
1642 /* Keep the displayed view in sync with line number scaling. */
1643 if (digits != view->digits) {
1644 view->digits = digits;
1645 redraw_from = 0;
1646 }
1647 }
1649 if (redraw_from >= 0) {
1650 /* If this is an incremental update, redraw the previous line
1651 * since for commits some members could have changed when
1652 * loading the main view. */
1653 if (redraw_from > 0)
1654 redraw_from--;
1656 /* Incrementally draw avoids flickering. */
1657 redraw_view_from(view, redraw_from);
1658 }
1660 /* Update the title _after_ the redraw so that if the redraw picks up a
1661 * commit reference in view->ref it'll be available here. */
1662 update_view_title(view);
1664 if (ferror(view->pipe)) {
1665 report("Failed to read: %s", strerror(errno));
1666 goto end;
1668 } else if (feof(view->pipe)) {
1669 report("");
1670 goto end;
1671 }
1673 return TRUE;
1675 alloc_error:
1676 report("Allocation failure");
1678 end:
1679 end_update(view);
1680 return FALSE;
1681 }
1684 /*
1685 * View opening
1686 */
1688 static void open_help_view(struct view *view)
1689 {
1690 char buf[BUFSIZ];
1691 int lines = ARRAY_SIZE(req_info) + 2;
1692 int i;
1694 if (view->lines > 0)
1695 return;
1697 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1698 if (!req_info[i].request)
1699 lines++;
1701 view->line = calloc(lines, sizeof(*view->line));
1702 if (!view->line) {
1703 report("Allocation failure");
1704 return;
1705 }
1707 view->ops->read(view, "Quick reference for tig keybindings:");
1709 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1710 char *key;
1712 if (!req_info[i].request) {
1713 view->ops->read(view, "");
1714 view->ops->read(view, req_info[i].help);
1715 continue;
1716 }
1718 key = get_key(req_info[i].request);
1719 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1720 continue;
1722 view->ops->read(view, buf);
1723 }
1724 }
1726 enum open_flags {
1727 OPEN_DEFAULT = 0, /* Use default view switching. */
1728 OPEN_SPLIT = 1, /* Split current view. */
1729 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
1730 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
1731 };
1733 static void
1734 open_view(struct view *prev, enum request request, enum open_flags flags)
1735 {
1736 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1737 bool split = !!(flags & OPEN_SPLIT);
1738 bool reload = !!(flags & OPEN_RELOAD);
1739 struct view *view = VIEW(request);
1740 int nviews = displayed_views();
1741 struct view *base_view = display[0];
1743 if (view == prev && nviews == 1 && !reload) {
1744 report("Already in %s view", view->name);
1745 return;
1746 }
1748 if (view == VIEW(REQ_VIEW_HELP)) {
1749 open_help_view(view);
1751 } else if ((reload || strcmp(view->vid, view->id)) &&
1752 !begin_update(view)) {
1753 report("Failed to load %s view", view->name);
1754 return;
1755 }
1757 if (split) {
1758 display[1] = view;
1759 if (!backgrounded)
1760 current_view = 1;
1761 } else {
1762 /* Maximize the current view. */
1763 memset(display, 0, sizeof(display));
1764 current_view = 0;
1765 display[current_view] = view;
1766 }
1768 /* Resize the view when switching between split- and full-screen,
1769 * or when switching between two different full-screen views. */
1770 if (nviews != displayed_views() ||
1771 (nviews == 1 && base_view != display[0]))
1772 resize_display();
1774 if (split && prev->lineno - prev->offset >= prev->height) {
1775 /* Take the title line into account. */
1776 int lines = prev->lineno - prev->offset - prev->height + 1;
1778 /* Scroll the view that was split if the current line is
1779 * outside the new limited view. */
1780 do_scroll_view(prev, lines, TRUE);
1781 }
1783 if (prev && view != prev) {
1784 if (split && !backgrounded) {
1785 /* "Blur" the previous view. */
1786 update_view_title(prev);
1787 }
1789 view->parent = prev;
1790 }
1792 if (view->pipe && view->lines == 0) {
1793 /* Clear the old view and let the incremental updating refill
1794 * the screen. */
1795 wclear(view->win);
1796 report("");
1797 } else {
1798 redraw_view(view);
1799 report("");
1800 }
1802 /* If the view is backgrounded the above calls to report()
1803 * won't redraw the view title. */
1804 if (backgrounded)
1805 update_view_title(view);
1806 }
1809 /*
1810 * User request switch noodle
1811 */
1813 static int
1814 view_driver(struct view *view, enum request request)
1815 {
1816 int i;
1818 switch (request) {
1819 case REQ_MOVE_UP:
1820 case REQ_MOVE_DOWN:
1821 case REQ_MOVE_PAGE_UP:
1822 case REQ_MOVE_PAGE_DOWN:
1823 case REQ_MOVE_FIRST_LINE:
1824 case REQ_MOVE_LAST_LINE:
1825 move_view(view, request, TRUE);
1826 break;
1828 case REQ_SCROLL_LINE_DOWN:
1829 case REQ_SCROLL_LINE_UP:
1830 case REQ_SCROLL_PAGE_DOWN:
1831 case REQ_SCROLL_PAGE_UP:
1832 scroll_view(view, request);
1833 break;
1835 case REQ_VIEW_MAIN:
1836 case REQ_VIEW_DIFF:
1837 case REQ_VIEW_LOG:
1838 case REQ_VIEW_HELP:
1839 case REQ_VIEW_PAGER:
1840 open_view(view, request, OPEN_DEFAULT);
1841 break;
1843 case REQ_NEXT:
1844 case REQ_PREVIOUS:
1845 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1847 if (view == VIEW(REQ_VIEW_DIFF) &&
1848 view->parent == VIEW(REQ_VIEW_MAIN)) {
1849 bool redraw = display[1] == view;
1851 view = view->parent;
1852 move_view(view, request, redraw);
1853 if (redraw)
1854 update_view_title(view);
1855 } else {
1856 move_view(view, request, TRUE);
1857 break;
1858 }
1859 /* Fall-through */
1861 case REQ_ENTER:
1862 if (!view->lines) {
1863 report("Nothing to enter");
1864 break;
1865 }
1866 return view->ops->enter(view, &view->line[view->lineno]);
1868 case REQ_VIEW_NEXT:
1869 {
1870 int nviews = displayed_views();
1871 int next_view = (current_view + 1) % nviews;
1873 if (next_view == current_view) {
1874 report("Only one view is displayed");
1875 break;
1876 }
1878 current_view = next_view;
1879 /* Blur out the title of the previous view. */
1880 update_view_title(view);
1881 report("");
1882 break;
1883 }
1884 case REQ_TOGGLE_LINENO:
1885 opt_line_number = !opt_line_number;
1886 redraw_display();
1887 break;
1889 case REQ_TOGGLE_REV_GRAPH:
1890 opt_rev_graph = !opt_rev_graph;
1891 redraw_display();
1892 break;
1894 case REQ_PROMPT:
1895 /* Always reload^Wrerun commands from the prompt. */
1896 open_view(view, opt_request, OPEN_RELOAD);
1897 break;
1899 case REQ_STOP_LOADING:
1900 for (i = 0; i < ARRAY_SIZE(views); i++) {
1901 view = &views[i];
1902 if (view->pipe)
1903 report("Stopped loading the %s view", view->name),
1904 end_update(view);
1905 }
1906 break;
1908 case REQ_SHOW_VERSION:
1909 report("%s (built %s)", VERSION, __DATE__);
1910 return TRUE;
1912 case REQ_SCREEN_RESIZE:
1913 resize_display();
1914 /* Fall-through */
1915 case REQ_SCREEN_REDRAW:
1916 redraw_display();
1917 break;
1919 case REQ_SCREEN_UPDATE:
1920 doupdate();
1921 return TRUE;
1923 case REQ_VIEW_CLOSE:
1924 /* XXX: Mark closed views by letting view->parent point to the
1925 * view itself. Parents to closed view should never be
1926 * followed. */
1927 if (view->parent &&
1928 view->parent->parent != view->parent) {
1929 memset(display, 0, sizeof(display));
1930 current_view = 0;
1931 display[current_view] = view->parent;
1932 view->parent = view;
1933 resize_display();
1934 redraw_display();
1935 break;
1936 }
1937 /* Fall-through */
1938 case REQ_QUIT:
1939 return FALSE;
1941 default:
1942 /* An unknown key will show most commonly used commands. */
1943 report("Unknown key, press 'h' for help");
1944 return TRUE;
1945 }
1947 return TRUE;
1948 }
1951 /*
1952 * Pager backend
1953 */
1955 static bool
1956 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1957 {
1958 char *text = line->data;
1959 enum line_type type = line->type;
1960 int textlen = strlen(text);
1961 int attr;
1963 wmove(view->win, lineno, 0);
1965 if (view->offset + lineno == view->lineno) {
1966 if (type == LINE_COMMIT) {
1967 string_copy(view->ref, text + 7);
1968 string_copy(ref_commit, view->ref);
1969 }
1971 type = LINE_CURSOR;
1972 wchgat(view->win, -1, 0, type, NULL);
1973 }
1975 attr = get_line_attr(type);
1976 wattrset(view->win, attr);
1978 if (opt_line_number || opt_tab_size < TABSIZE) {
1979 static char spaces[] = " ";
1980 int col_offset = 0, col = 0;
1982 if (opt_line_number) {
1983 unsigned long real_lineno = view->offset + lineno + 1;
1985 if (real_lineno == 1 ||
1986 (real_lineno % opt_num_interval) == 0) {
1987 wprintw(view->win, "%.*d", view->digits, real_lineno);
1989 } else {
1990 waddnstr(view->win, spaces,
1991 MIN(view->digits, STRING_SIZE(spaces)));
1992 }
1993 waddstr(view->win, ": ");
1994 col_offset = view->digits + 2;
1995 }
1997 while (text && col_offset + col < view->width) {
1998 int cols_max = view->width - col_offset - col;
1999 char *pos = text;
2000 int cols;
2002 if (*text == '\t') {
2003 text++;
2004 assert(sizeof(spaces) > TABSIZE);
2005 pos = spaces;
2006 cols = opt_tab_size - (col % opt_tab_size);
2008 } else {
2009 text = strchr(text, '\t');
2010 cols = line ? text - pos : strlen(pos);
2011 }
2013 waddnstr(view->win, pos, MIN(cols, cols_max));
2014 col += cols;
2015 }
2017 } else {
2018 int col = 0, pos = 0;
2020 for (; pos < textlen && col < view->width; pos++, col++)
2021 if (text[pos] == '\t')
2022 col += TABSIZE - (col % TABSIZE) - 1;
2024 waddnstr(view->win, text, pos);
2025 }
2027 return TRUE;
2028 }
2030 static void
2031 add_pager_refs(struct view *view, struct line *line)
2032 {
2033 char buf[1024];
2034 char *data = line->data;
2035 struct ref **refs;
2036 int bufpos = 0, refpos = 0;
2037 const char *sep = "Refs: ";
2039 assert(line->type == LINE_COMMIT);
2041 refs = get_refs(data + STRING_SIZE("commit "));
2042 if (!refs)
2043 return;
2045 do {
2046 struct ref *ref = refs[refpos];
2047 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
2049 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2050 return;
2051 sep = ", ";
2052 } while (refs[refpos++]->next);
2054 if (!realloc_lines(view, view->line_size + 1))
2055 return;
2057 line = &view->line[view->lines];
2058 line->data = strdup(buf);
2059 if (!line->data)
2060 return;
2062 line->type = LINE_PP_REFS;
2063 view->lines++;
2064 }
2066 static bool
2067 pager_read(struct view *view, char *data)
2068 {
2069 struct line *line = &view->line[view->lines];
2071 line->data = strdup(data);
2072 if (!line->data)
2073 return FALSE;
2075 line->type = get_line_type(line->data);
2076 view->lines++;
2078 if (line->type == LINE_COMMIT &&
2079 (view == VIEW(REQ_VIEW_DIFF) ||
2080 view == VIEW(REQ_VIEW_LOG)))
2081 add_pager_refs(view, line);
2083 return TRUE;
2084 }
2086 static bool
2087 pager_enter(struct view *view, struct line *line)
2088 {
2089 int split = 0;
2091 if (line->type == LINE_COMMIT &&
2092 (view == VIEW(REQ_VIEW_LOG) ||
2093 view == VIEW(REQ_VIEW_PAGER))) {
2094 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2095 split = 1;
2096 }
2098 /* Always scroll the view even if it was split. That way
2099 * you can use Enter to scroll through the log view and
2100 * split open each commit diff. */
2101 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2103 /* FIXME: A minor workaround. Scrolling the view will call report("")
2104 * but if we are scrolling a non-current view this won't properly
2105 * update the view title. */
2106 if (split)
2107 update_view_title(view);
2109 return TRUE;
2110 }
2112 static struct view_ops pager_ops = {
2113 "line",
2114 pager_draw,
2115 pager_read,
2116 pager_enter,
2117 };
2120 /*
2121 * Main view backend
2122 */
2124 struct commit {
2125 char id[41]; /* SHA1 ID. */
2126 char title[75]; /* First line of the commit message. */
2127 char author[75]; /* Author of the commit. */
2128 struct tm time; /* Date from the author ident. */
2129 struct ref **refs; /* Repository references. */
2130 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
2131 size_t graph_size; /* The width of the graph array. */
2132 };
2134 static bool
2135 main_draw(struct view *view, struct line *line, unsigned int lineno)
2136 {
2137 char buf[DATE_COLS + 1];
2138 struct commit *commit = line->data;
2139 enum line_type type;
2140 int col = 0;
2141 size_t timelen;
2142 size_t authorlen;
2143 int trimmed = 1;
2145 if (!*commit->author)
2146 return FALSE;
2148 wmove(view->win, lineno, col);
2150 if (view->offset + lineno == view->lineno) {
2151 string_copy(view->ref, commit->id);
2152 string_copy(ref_commit, view->ref);
2153 type = LINE_CURSOR;
2154 wattrset(view->win, get_line_attr(type));
2155 wchgat(view->win, -1, 0, type, NULL);
2157 } else {
2158 type = LINE_MAIN_COMMIT;
2159 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2160 }
2162 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2163 waddnstr(view->win, buf, timelen);
2164 waddstr(view->win, " ");
2166 col += DATE_COLS;
2167 wmove(view->win, lineno, col);
2168 if (type != LINE_CURSOR)
2169 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2171 if (opt_utf8) {
2172 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2173 } else {
2174 authorlen = strlen(commit->author);
2175 if (authorlen > AUTHOR_COLS - 2) {
2176 authorlen = AUTHOR_COLS - 2;
2177 trimmed = 1;
2178 }
2179 }
2181 if (trimmed) {
2182 waddnstr(view->win, commit->author, authorlen);
2183 if (type != LINE_CURSOR)
2184 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2185 waddch(view->win, '~');
2186 } else {
2187 waddstr(view->win, commit->author);
2188 }
2190 col += AUTHOR_COLS;
2191 if (type != LINE_CURSOR)
2192 wattrset(view->win, A_NORMAL);
2194 if (opt_rev_graph && commit->graph_size) {
2195 size_t i;
2197 wmove(view->win, lineno, col);
2198 /* Using waddch() instead of waddnstr() ensures that
2199 * they'll be rendered correctly for the cursor line. */
2200 for (i = 0; i < commit->graph_size; i++)
2201 waddch(view->win, commit->graph[i]);
2203 col += commit->graph_size + 1;
2204 }
2206 wmove(view->win, lineno, col);
2208 if (commit->refs) {
2209 size_t i = 0;
2211 do {
2212 if (type == LINE_CURSOR)
2213 ;
2214 else if (commit->refs[i]->tag)
2215 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2216 else
2217 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2218 waddstr(view->win, "[");
2219 waddstr(view->win, commit->refs[i]->name);
2220 waddstr(view->win, "]");
2221 if (type != LINE_CURSOR)
2222 wattrset(view->win, A_NORMAL);
2223 waddstr(view->win, " ");
2224 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2225 } while (commit->refs[i++]->next);
2226 }
2228 if (type != LINE_CURSOR)
2229 wattrset(view->win, get_line_attr(type));
2231 {
2232 int titlelen = strlen(commit->title);
2234 if (col + titlelen > view->width)
2235 titlelen = view->width - col;
2237 waddnstr(view->win, commit->title, titlelen);
2238 }
2240 return TRUE;
2241 }
2243 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2244 static bool
2245 main_read(struct view *view, char *line)
2246 {
2247 enum line_type type = get_line_type(line);
2248 struct commit *commit = view->lines
2249 ? view->line[view->lines - 1].data : NULL;
2251 switch (type) {
2252 case LINE_COMMIT:
2253 commit = calloc(1, sizeof(struct commit));
2254 if (!commit)
2255 return FALSE;
2257 line += STRING_SIZE("commit ");
2259 view->line[view->lines++].data = commit;
2260 string_copy(commit->id, line);
2261 commit->refs = get_refs(commit->id);
2262 commit->graph[commit->graph_size++] = ACS_LTEE;
2263 break;
2265 case LINE_AUTHOR:
2266 {
2267 char *ident = line + STRING_SIZE("author ");
2268 char *end = strchr(ident, '<');
2270 if (!commit)
2271 break;
2273 if (end) {
2274 for (; end > ident && isspace(end[-1]); end--) ;
2275 *end = 0;
2276 }
2278 string_copy(commit->author, ident);
2280 /* Parse epoch and timezone */
2281 if (end) {
2282 char *secs = strchr(end + 1, '>');
2283 char *zone;
2284 time_t time;
2286 if (!secs || secs[1] != ' ')
2287 break;
2289 secs += 2;
2290 time = (time_t) atol(secs);
2291 zone = strchr(secs, ' ');
2292 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
2293 long tz;
2295 zone++;
2296 tz = ('0' - zone[1]) * 60 * 60 * 10;
2297 tz += ('0' - zone[2]) * 60 * 60;
2298 tz += ('0' - zone[3]) * 60;
2299 tz += ('0' - zone[4]) * 60;
2301 if (zone[0] == '-')
2302 tz = -tz;
2304 time -= tz;
2305 }
2306 gmtime_r(&time, &commit->time);
2307 }
2308 break;
2309 }
2310 default:
2311 if (!commit)
2312 break;
2314 /* Fill in the commit title if it has not already been set. */
2315 if (commit->title[0])
2316 break;
2318 /* Require titles to start with a non-space character at the
2319 * offset used by git log. */
2320 /* FIXME: More gracefull handling of titles; append "..." to
2321 * shortened titles, etc. */
2322 if (strncmp(line, " ", 4) ||
2323 isspace(line[4]))
2324 break;
2326 string_copy(commit->title, line + 4);
2327 }
2329 return TRUE;
2330 }
2332 static bool
2333 main_enter(struct view *view, struct line *line)
2334 {
2335 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2337 open_view(view, REQ_VIEW_DIFF, flags);
2338 return TRUE;
2339 }
2341 static struct view_ops main_ops = {
2342 "commit",
2343 main_draw,
2344 main_read,
2345 main_enter,
2346 };
2349 /*
2350 * Unicode / UTF-8 handling
2351 *
2352 * NOTE: Much of the following code for dealing with unicode is derived from
2353 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2354 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2355 */
2357 /* I've (over)annotated a lot of code snippets because I am not entirely
2358 * confident that the approach taken by this small UTF-8 interface is correct.
2359 * --jonas */
2361 static inline int
2362 unicode_width(unsigned long c)
2363 {
2364 if (c >= 0x1100 &&
2365 (c <= 0x115f /* Hangul Jamo */
2366 || c == 0x2329
2367 || c == 0x232a
2368 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
2369 /* CJK ... Yi */
2370 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
2371 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
2372 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
2373 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
2374 || (c >= 0xffe0 && c <= 0xffe6)
2375 || (c >= 0x20000 && c <= 0x2fffd)
2376 || (c >= 0x30000 && c <= 0x3fffd)))
2377 return 2;
2379 return 1;
2380 }
2382 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2383 * Illegal bytes are set one. */
2384 static const unsigned char utf8_bytes[256] = {
2385 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,
2386 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,
2387 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,
2388 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,
2389 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,
2390 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,
2391 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,
2392 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,
2393 };
2395 /* Decode UTF-8 multi-byte representation into a unicode character. */
2396 static inline unsigned long
2397 utf8_to_unicode(const char *string, size_t length)
2398 {
2399 unsigned long unicode;
2401 switch (length) {
2402 case 1:
2403 unicode = string[0];
2404 break;
2405 case 2:
2406 unicode = (string[0] & 0x1f) << 6;
2407 unicode += (string[1] & 0x3f);
2408 break;
2409 case 3:
2410 unicode = (string[0] & 0x0f) << 12;
2411 unicode += ((string[1] & 0x3f) << 6);
2412 unicode += (string[2] & 0x3f);
2413 break;
2414 case 4:
2415 unicode = (string[0] & 0x0f) << 18;
2416 unicode += ((string[1] & 0x3f) << 12);
2417 unicode += ((string[2] & 0x3f) << 6);
2418 unicode += (string[3] & 0x3f);
2419 break;
2420 case 5:
2421 unicode = (string[0] & 0x0f) << 24;
2422 unicode += ((string[1] & 0x3f) << 18);
2423 unicode += ((string[2] & 0x3f) << 12);
2424 unicode += ((string[3] & 0x3f) << 6);
2425 unicode += (string[4] & 0x3f);
2426 break;
2427 case 6:
2428 unicode = (string[0] & 0x01) << 30;
2429 unicode += ((string[1] & 0x3f) << 24);
2430 unicode += ((string[2] & 0x3f) << 18);
2431 unicode += ((string[3] & 0x3f) << 12);
2432 unicode += ((string[4] & 0x3f) << 6);
2433 unicode += (string[5] & 0x3f);
2434 break;
2435 default:
2436 die("Invalid unicode length");
2437 }
2439 /* Invalid characters could return the special 0xfffd value but NUL
2440 * should be just as good. */
2441 return unicode > 0xffff ? 0 : unicode;
2442 }
2444 /* Calculates how much of string can be shown within the given maximum width
2445 * and sets trimmed parameter to non-zero value if all of string could not be
2446 * shown.
2447 *
2448 * Additionally, adds to coloffset how many many columns to move to align with
2449 * the expected position. Takes into account how multi-byte and double-width
2450 * characters will effect the cursor position.
2451 *
2452 * Returns the number of bytes to output from string to satisfy max_width. */
2453 static size_t
2454 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2455 {
2456 const char *start = string;
2457 const char *end = strchr(string, '\0');
2458 size_t mbwidth = 0;
2459 size_t width = 0;
2461 *trimmed = 0;
2463 while (string < end) {
2464 int c = *(unsigned char *) string;
2465 unsigned char bytes = utf8_bytes[c];
2466 size_t ucwidth;
2467 unsigned long unicode;
2469 if (string + bytes > end)
2470 break;
2472 /* Change representation to figure out whether
2473 * it is a single- or double-width character. */
2475 unicode = utf8_to_unicode(string, bytes);
2476 /* FIXME: Graceful handling of invalid unicode character. */
2477 if (!unicode)
2478 break;
2480 ucwidth = unicode_width(unicode);
2481 width += ucwidth;
2482 if (width > max_width) {
2483 *trimmed = 1;
2484 break;
2485 }
2487 /* The column offset collects the differences between the
2488 * number of bytes encoding a character and the number of
2489 * columns will be used for rendering said character.
2490 *
2491 * So if some character A is encoded in 2 bytes, but will be
2492 * represented on the screen using only 1 byte this will and up
2493 * adding 1 to the multi-byte column offset.
2494 *
2495 * Assumes that no double-width character can be encoding in
2496 * less than two bytes. */
2497 if (bytes > ucwidth)
2498 mbwidth += bytes - ucwidth;
2500 string += bytes;
2501 }
2503 *coloffset += mbwidth;
2505 return string - start;
2506 }
2509 /*
2510 * Status management
2511 */
2513 /* Whether or not the curses interface has been initialized. */
2514 static bool cursed = FALSE;
2516 /* The status window is used for polling keystrokes. */
2517 static WINDOW *status_win;
2519 /* Update status and title window. */
2520 static void
2521 report(const char *msg, ...)
2522 {
2523 static bool empty = TRUE;
2524 struct view *view = display[current_view];
2526 if (!empty || *msg) {
2527 va_list args;
2529 va_start(args, msg);
2531 werase(status_win);
2532 wmove(status_win, 0, 0);
2533 if (*msg) {
2534 vwprintw(status_win, msg, args);
2535 empty = FALSE;
2536 } else {
2537 empty = TRUE;
2538 }
2539 wrefresh(status_win);
2541 va_end(args);
2542 }
2544 update_view_title(view);
2545 update_display_cursor();
2546 }
2548 /* Controls when nodelay should be in effect when polling user input. */
2549 static void
2550 set_nonblocking_input(bool loading)
2551 {
2552 static unsigned int loading_views;
2554 if ((loading == FALSE && loading_views-- == 1) ||
2555 (loading == TRUE && loading_views++ == 0))
2556 nodelay(status_win, loading);
2557 }
2559 static void
2560 init_display(void)
2561 {
2562 int x, y;
2564 /* Initialize the curses library */
2565 if (isatty(STDIN_FILENO)) {
2566 cursed = !!initscr();
2567 } else {
2568 /* Leave stdin and stdout alone when acting as a pager. */
2569 FILE *io = fopen("/dev/tty", "r+");
2571 cursed = !!newterm(NULL, io, io);
2572 }
2574 if (!cursed)
2575 die("Failed to initialize curses");
2577 nonl(); /* Tell curses not to do NL->CR/NL on output */
2578 cbreak(); /* Take input chars one at a time, no wait for \n */
2579 noecho(); /* Don't echo input */
2580 leaveok(stdscr, TRUE);
2582 if (has_colors())
2583 init_colors();
2585 getmaxyx(stdscr, y, x);
2586 status_win = newwin(1, 0, y - 1, 0);
2587 if (!status_win)
2588 die("Failed to create status window");
2590 /* Enable keyboard mapping */
2591 keypad(status_win, TRUE);
2592 wbkgdset(status_win, get_line_attr(LINE_STATUS));
2593 }
2596 /*
2597 * Repository references
2598 */
2600 static struct ref *refs;
2601 static size_t refs_size;
2603 /* Id <-> ref store */
2604 static struct ref ***id_refs;
2605 static size_t id_refs_size;
2607 static struct ref **
2608 get_refs(char *id)
2609 {
2610 struct ref ***tmp_id_refs;
2611 struct ref **ref_list = NULL;
2612 size_t ref_list_size = 0;
2613 size_t i;
2615 for (i = 0; i < id_refs_size; i++)
2616 if (!strcmp(id, id_refs[i][0]->id))
2617 return id_refs[i];
2619 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2620 if (!tmp_id_refs)
2621 return NULL;
2623 id_refs = tmp_id_refs;
2625 for (i = 0; i < refs_size; i++) {
2626 struct ref **tmp;
2628 if (strcmp(id, refs[i].id))
2629 continue;
2631 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2632 if (!tmp) {
2633 if (ref_list)
2634 free(ref_list);
2635 return NULL;
2636 }
2638 ref_list = tmp;
2639 if (ref_list_size > 0)
2640 ref_list[ref_list_size - 1]->next = 1;
2641 ref_list[ref_list_size] = &refs[i];
2643 /* XXX: The properties of the commit chains ensures that we can
2644 * safely modify the shared ref. The repo references will
2645 * always be similar for the same id. */
2646 ref_list[ref_list_size]->next = 0;
2647 ref_list_size++;
2648 }
2650 if (ref_list)
2651 id_refs[id_refs_size++] = ref_list;
2653 return ref_list;
2654 }
2656 static int
2657 read_ref(char *id, int idlen, char *name, int namelen)
2658 {
2659 struct ref *ref;
2660 bool tag = FALSE;
2662 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2663 /* Commits referenced by tags has "^{}" appended. */
2664 if (name[namelen - 1] != '}')
2665 return OK;
2667 while (namelen > 0 && name[namelen] != '^')
2668 namelen--;
2670 tag = TRUE;
2671 namelen -= STRING_SIZE("refs/tags/");
2672 name += STRING_SIZE("refs/tags/");
2674 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2675 namelen -= STRING_SIZE("refs/heads/");
2676 name += STRING_SIZE("refs/heads/");
2678 } else if (!strcmp(name, "HEAD")) {
2679 return OK;
2680 }
2682 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2683 if (!refs)
2684 return ERR;
2686 ref = &refs[refs_size++];
2687 ref->name = malloc(namelen + 1);
2688 if (!ref->name)
2689 return ERR;
2691 strncpy(ref->name, name, namelen);
2692 ref->name[namelen] = 0;
2693 ref->tag = tag;
2694 string_copy(ref->id, id);
2696 return OK;
2697 }
2699 static int
2700 load_refs(void)
2701 {
2702 const char *cmd_env = getenv("TIG_LS_REMOTE");
2703 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2705 return read_properties(popen(cmd, "r"), "\t", read_ref);
2706 }
2708 static int
2709 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2710 {
2711 if (!strcmp(name, "i18n.commitencoding"))
2712 string_copy(opt_encoding, value);
2714 return OK;
2715 }
2717 static int
2718 load_repo_config(void)
2719 {
2720 return read_properties(popen("git repo-config --list", "r"),
2721 "=", read_repo_config_option);
2722 }
2724 static int
2725 read_properties(FILE *pipe, const char *separators,
2726 int (*read_property)(char *, int, char *, int))
2727 {
2728 char buffer[BUFSIZ];
2729 char *name;
2730 int state = OK;
2732 if (!pipe)
2733 return ERR;
2735 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2736 char *value;
2737 size_t namelen;
2738 size_t valuelen;
2740 name = chomp_string(name);
2741 namelen = strcspn(name, separators);
2743 if (name[namelen]) {
2744 name[namelen] = 0;
2745 value = chomp_string(name + namelen + 1);
2746 valuelen = strlen(value);
2748 } else {
2749 value = "";
2750 valuelen = 0;
2751 }
2753 state = read_property(name, namelen, value, valuelen);
2754 }
2756 if (state != ERR && ferror(pipe))
2757 state = ERR;
2759 pclose(pipe);
2761 return state;
2762 }
2765 /*
2766 * Main
2767 */
2769 static void __NORETURN
2770 quit(int sig)
2771 {
2772 /* XXX: Restore tty modes and let the OS cleanup the rest! */
2773 if (cursed)
2774 endwin();
2775 exit(0);
2776 }
2778 static void __NORETURN
2779 die(const char *err, ...)
2780 {
2781 va_list args;
2783 endwin();
2785 va_start(args, err);
2786 fputs("tig: ", stderr);
2787 vfprintf(stderr, err, args);
2788 fputs("\n", stderr);
2789 va_end(args);
2791 exit(1);
2792 }
2794 int
2795 main(int argc, char *argv[])
2796 {
2797 struct view *view;
2798 enum request request;
2799 size_t i;
2801 signal(SIGINT, quit);
2803 if (load_options() == ERR)
2804 die("Failed to load user config.");
2806 /* Load the repo config file so options can be overwritten from
2807 * the command line. */
2808 if (load_repo_config() == ERR)
2809 die("Failed to load repo config.");
2811 if (!parse_options(argc, argv))
2812 return 0;
2814 if (load_refs() == ERR)
2815 die("Failed to load refs.");
2817 /* Require a git repository unless when running in pager mode. */
2818 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2819 die("Not a git repository");
2821 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2822 view->cmd_env = getenv(view->cmd_env);
2824 request = opt_request;
2826 init_display();
2828 while (view_driver(display[current_view], request)) {
2829 int key;
2830 int i;
2832 foreach_view (view, i)
2833 update_view(view);
2835 /* Refresh, accept single keystroke of input */
2836 key = wgetch(status_win);
2838 request = get_keybinding(display[current_view]->keymap, key);
2840 /* Some low-level request handling. This keeps access to
2841 * status_win restricted. */
2842 switch (request) {
2843 case REQ_PROMPT:
2844 report(":");
2845 /* Temporarily switch to line-oriented and echoed
2846 * input. */
2847 nocbreak();
2848 echo();
2850 if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2851 memcpy(opt_cmd, "git ", 4);
2852 opt_request = REQ_VIEW_PAGER;
2853 } else {
2854 report("Prompt interrupted by loading view, "
2855 "press 'z' to stop loading views");
2856 request = REQ_SCREEN_UPDATE;
2857 }
2859 noecho();
2860 cbreak();
2861 break;
2863 case REQ_SCREEN_RESIZE:
2864 {
2865 int height, width;
2867 getmaxyx(stdscr, height, width);
2869 /* Resize the status view and let the view driver take
2870 * care of resizing the displayed views. */
2871 wresize(status_win, 1, width);
2872 mvwin(status_win, height - 1, 0);
2873 wrefresh(status_win);
2874 break;
2875 }
2876 default:
2877 break;
2878 }
2879 }
2881 quit(0);
2883 return 0;
2884 }