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