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