Support set command in ~/.tigrc; allows a few options to be configured
[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 * User config file handling.
605 */
606
607 static struct int_map color_map[] = {
608 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
609 COLOR_MAP(DEFAULT),
610 COLOR_MAP(BLACK),
611 COLOR_MAP(BLUE),
612 COLOR_MAP(CYAN),
613 COLOR_MAP(GREEN),
614 COLOR_MAP(MAGENTA),
615 COLOR_MAP(RED),
616 COLOR_MAP(WHITE),
617 COLOR_MAP(YELLOW),
618 };
619
620 #define set_color(color, name) \
621 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
622
623 static struct int_map attr_map[] = {
624 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
625 ATTR_MAP(NORMAL),
626 ATTR_MAP(BLINK),
627 ATTR_MAP(BOLD),
628 ATTR_MAP(DIM),
629 ATTR_MAP(REVERSE),
630 ATTR_MAP(STANDOUT),
631 ATTR_MAP(UNDERLINE),
632 };
633
634 #define set_attribute(attr, name) \
635 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
636
637 static int config_lineno;
638 static bool config_errors;
639 static char *config_msg;
640
641 /* Wants: object fgcolor bgcolor [attr] */
642 static int
643 option_color_command(int argc, char *argv[])
644 {
645 struct line_info *info;
646
647 if (argc != 3 && argc != 4) {
648 config_msg = "Wrong number of arguments given to color command";
649 return ERR;
650 }
651
652 info = get_line_info(argv[0], strlen(argv[0]));
653 if (!info) {
654 config_msg = "Unknown color name";
655 return ERR;
656 }
657
658 if (set_color(&info->fg, argv[1]) == ERR) {
659 config_msg = "Unknown color";
660 return ERR;
661 }
662
663 if (set_color(&info->bg, argv[2]) == ERR) {
664 config_msg = "Unknown color";
665 return ERR;
666 }
667
668 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
669 config_msg = "Unknown attribute";
670 return ERR;
671 }
672
673 return OK;
674 }
675
676 /* Wants: name = value */
677 static int
678 option_set_command(int argc, char *argv[])
679 {
680 if (argc != 3) {
681 config_msg = "Wrong number of arguments given to set command";
682 return ERR;
683 }
684
685 if (strcmp(argv[1], "=")) {
686 config_msg = "No value assigned";
687 return ERR;
688 }
689
690 if (!strcmp(argv[0], "show-rev-graph")) {
691 opt_rev_graph = (!strcmp(argv[2], "1") ||
692 !strcmp(argv[2], "true") ||
693 !strcmp(argv[2], "yes"));
694 return OK;
695 }
696
697 if (!strcmp(argv[0], "line-number-interval")) {
698 opt_num_interval = atoi(argv[2]);
699 return OK;
700 }
701
702 if (!strcmp(argv[0], "tab-size")) {
703 opt_tab_size = atoi(argv[2]);
704 return OK;
705 }
706
707 if (!strcmp(argv[0], "encoding")) {
708 string_copy(opt_encoding, argv[2]);
709 return OK;
710 }
711
712 return ERR;
713 }
714
715 static int
716 set_option(char *opt, char *value)
717 {
718 char *argv[16];
719 int valuelen;
720 int argc = 0;
721
722 /* Tokenize */
723 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
724 argv[argc++] = value;
725
726 value += valuelen;
727 if (!*value)
728 break;
729
730 *value++ = 0;
731 while (isspace(*value))
732 value++;
733 }
734
735 if (!strcmp(opt, "color"))
736 return option_color_command(argc, argv);
737
738 if (!strcmp(opt, "set"))
739 return option_set_command(argc, argv);
740
741 return ERR;
742 }
743
744 static int
745 read_option(char *opt, int optlen, char *value, int valuelen)
746 {
747 config_lineno++;
748 config_msg = "Internal error";
749
750 optlen = strcspn(opt, "#;");
751 if (optlen == 0) {
752 /* The whole line is a commend or empty. */
753 return OK;
754
755 } else if (opt[optlen] != 0) {
756 /* Part of the option name is a comment, so the value part
757 * should be ignored. */
758 valuelen = 0;
759 opt[optlen] = value[valuelen] = 0;
760 } else {
761 /* Else look for comment endings in the value. */
762 valuelen = strcspn(value, "#;");
763 value[valuelen] = 0;
764 }
765
766 if (set_option(opt, value) == ERR) {
767 fprintf(stderr, "Error on line %d, near '%.*s' option: %s\n",
768 config_lineno, optlen, opt, config_msg);
769 config_errors = TRUE;
770 }
771
772 /* Always keep going if errors are encountered. */
773 return OK;
774 }
775
776 static int
777 load_options(void)
778 {
779 char *home = getenv("HOME");
780 char buf[1024];
781 FILE *file;
782
783 config_lineno = 0;
784 config_errors = FALSE;
785
786 if (!home || !string_format(buf, "%s/.tigrc", home))
787 return ERR;
788
789 /* It's ok that the file doesn't exist. */
790 file = fopen(buf, "r");
791 if (!file)
792 return OK;
793
794 if (read_properties(file, " \t", read_option) == ERR ||
795 config_errors == TRUE)
796 fprintf(stderr, "Errors while loading %s.\n", buf);
797
798 return OK;
799 }
800
801
802 /*
803 * The viewer
804 */
805
806 struct view;
807 struct view_ops;
808
809 /* The display array of active views and the index of the current view. */
810 static struct view *display[2];
811 static unsigned int current_view;
812
813 #define foreach_view(view, i) \
814 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
815
816 #define displayed_views() (display[1] != NULL ? 2 : 1)
817
818 /* Current head and commit ID */
819 static char ref_commit[SIZEOF_REF] = "HEAD";
820 static char ref_head[SIZEOF_REF] = "HEAD";
821
822 struct view {
823 const char *name; /* View name */
824 const char *cmd_fmt; /* Default command line format */
825 const char *cmd_env; /* Command line set via environment */
826 const char *id; /* Points to either of ref_{head,commit} */
827
828 struct view_ops *ops; /* View operations */
829
830 char cmd[SIZEOF_CMD]; /* Command buffer */
831 char ref[SIZEOF_REF]; /* Hovered commit reference */
832 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
833
834 int height, width; /* The width and height of the main window */
835 WINDOW *win; /* The main window */
836 WINDOW *title; /* The title window living below the main window */
837
838 /* Navigation */
839 unsigned long offset; /* Offset of the window top */
840 unsigned long lineno; /* Current line number */
841
842 /* If non-NULL, points to the view that opened this view. If this view
843 * is closed tig will switch back to the parent view. */
844 struct view *parent;
845
846 /* Buffering */
847 unsigned long lines; /* Total number of lines */
848 struct line *line; /* Line index */
849 unsigned long line_size;/* Total number of allocated lines */
850 unsigned int digits; /* Number of digits in the lines member. */
851
852 /* Loading */
853 FILE *pipe;
854 time_t start_time;
855 };
856
857 struct view_ops {
858 /* What type of content being displayed. Used in the title bar. */
859 const char *type;
860 /* Draw one line; @lineno must be < view->height. */
861 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
862 /* Read one line; updates view->line. */
863 bool (*read)(struct view *view, char *data);
864 /* Depending on view, change display based on current line. */
865 bool (*enter)(struct view *view, struct line *line);
866 };
867
868 static struct view_ops pager_ops;
869 static struct view_ops main_ops;
870
871 #define VIEW_STR(name, cmd, env, ref, ops) \
872 { name, cmd, #env, ref, ops }
873
874 #define VIEW_(id, name, ops, ref) \
875 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops)
876
877
878 static struct view views[] = {
879 VIEW_(MAIN, "main", &main_ops, ref_head),
880 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
881 VIEW_(LOG, "log", &pager_ops, ref_head),
882 VIEW_(HELP, "help", &pager_ops, "static"),
883 VIEW_(PAGER, "pager", &pager_ops, "static"),
884 };
885
886 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
887
888
889 static bool
890 draw_view_line(struct view *view, unsigned int lineno)
891 {
892 if (view->offset + lineno >= view->lines)
893 return FALSE;
894
895 return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
896 }
897
898 static void
899 redraw_view_from(struct view *view, int lineno)
900 {
901 assert(0 <= lineno && lineno < view->height);
902
903 for (; lineno < view->height; lineno++) {
904 if (!draw_view_line(view, lineno))
905 break;
906 }
907
908 redrawwin(view->win);
909 wrefresh(view->win);
910 }
911
912 static void
913 redraw_view(struct view *view)
914 {
915 wclear(view->win);
916 redraw_view_from(view, 0);
917 }
918
919
920 static void
921 update_view_title(struct view *view)
922 {
923 if (view == display[current_view])
924 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
925 else
926 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
927
928 werase(view->title);
929 wmove(view->title, 0, 0);
930
931 if (*view->ref)
932 wprintw(view->title, "[%s] %s", view->name, view->ref);
933 else
934 wprintw(view->title, "[%s]", view->name);
935
936 if (view->lines || view->pipe) {
937 unsigned int view_lines = view->offset + view->height;
938 unsigned int lines = view->lines
939 ? MIN(view_lines, view->lines) * 100 / view->lines
940 : 0;
941
942 wprintw(view->title, " - %s %d of %d (%d%%)",
943 view->ops->type,
944 view->lineno + 1,
945 view->lines,
946 lines);
947 }
948
949 if (view->pipe) {
950 time_t secs = time(NULL) - view->start_time;
951
952 /* Three git seconds are a long time ... */
953 if (secs > 2)
954 wprintw(view->title, " %lds", secs);
955 }
956
957 wmove(view->title, 0, view->width - 1);
958 wrefresh(view->title);
959 }
960
961 static void
962 resize_display(void)
963 {
964 int offset, i;
965 struct view *base = display[0];
966 struct view *view = display[1] ? display[1] : display[0];
967
968 /* Setup window dimensions */
969
970 getmaxyx(stdscr, base->height, base->width);
971
972 /* Make room for the status window. */
973 base->height -= 1;
974
975 if (view != base) {
976 /* Horizontal split. */
977 view->width = base->width;
978 view->height = SCALE_SPLIT_VIEW(base->height);
979 base->height -= view->height;
980
981 /* Make room for the title bar. */
982 view->height -= 1;
983 }
984
985 /* Make room for the title bar. */
986 base->height -= 1;
987
988 offset = 0;
989
990 foreach_view (view, i) {
991 if (!view->win) {
992 view->win = newwin(view->height, 0, offset, 0);
993 if (!view->win)
994 die("Failed to create %s view", view->name);
995
996 scrollok(view->win, TRUE);
997
998 view->title = newwin(1, 0, offset + view->height, 0);
999 if (!view->title)
1000 die("Failed to create title window");
1001
1002 } else {
1003 wresize(view->win, view->height, view->width);
1004 mvwin(view->win, offset, 0);
1005 mvwin(view->title, offset + view->height, 0);
1006 }
1007
1008 offset += view->height + 1;
1009 }
1010 }
1011
1012 static void
1013 redraw_display(void)
1014 {
1015 struct view *view;
1016 int i;
1017
1018 foreach_view (view, i) {
1019 redraw_view(view);
1020 update_view_title(view);
1021 }
1022 }
1023
1024 static void
1025 update_display_cursor(void)
1026 {
1027 struct view *view = display[current_view];
1028
1029 /* Move the cursor to the right-most column of the cursor line.
1030 *
1031 * XXX: This could turn out to be a bit expensive, but it ensures that
1032 * the cursor does not jump around. */
1033 if (view->lines) {
1034 wmove(view->win, view->lineno - view->offset, view->width - 1);
1035 wrefresh(view->win);
1036 }
1037 }
1038
1039 /*
1040 * Navigation
1041 */
1042
1043 /* Scrolling backend */
1044 static void
1045 do_scroll_view(struct view *view, int lines, bool redraw)
1046 {
1047 /* The rendering expects the new offset. */
1048 view->offset += lines;
1049
1050 assert(0 <= view->offset && view->offset < view->lines);
1051 assert(lines);
1052
1053 /* Redraw the whole screen if scrolling is pointless. */
1054 if (view->height < ABS(lines)) {
1055 redraw_view(view);
1056
1057 } else {
1058 int line = lines > 0 ? view->height - lines : 0;
1059 int end = line + ABS(lines);
1060
1061 wscrl(view->win, lines);
1062
1063 for (; line < end; line++) {
1064 if (!draw_view_line(view, line))
1065 break;
1066 }
1067 }
1068
1069 /* Move current line into the view. */
1070 if (view->lineno < view->offset) {
1071 view->lineno = view->offset;
1072 draw_view_line(view, 0);
1073
1074 } else if (view->lineno >= view->offset + view->height) {
1075 if (view->lineno == view->offset + view->height) {
1076 /* Clear the hidden line so it doesn't show if the view
1077 * is scrolled up. */
1078 wmove(view->win, view->height, 0);
1079 wclrtoeol(view->win);
1080 }
1081 view->lineno = view->offset + view->height - 1;
1082 draw_view_line(view, view->lineno - view->offset);
1083 }
1084
1085 assert(view->offset <= view->lineno && view->lineno < view->lines);
1086
1087 if (!redraw)
1088 return;
1089
1090 redrawwin(view->win);
1091 wrefresh(view->win);
1092 report("");
1093 }
1094
1095 /* Scroll frontend */
1096 static void
1097 scroll_view(struct view *view, enum request request)
1098 {
1099 int lines = 1;
1100
1101 switch (request) {
1102 case REQ_SCROLL_PAGE_DOWN:
1103 lines = view->height;
1104 case REQ_SCROLL_LINE_DOWN:
1105 if (view->offset + lines > view->lines)
1106 lines = view->lines - view->offset;
1107
1108 if (lines == 0 || view->offset + view->height >= view->lines) {
1109 report("Cannot scroll beyond the last line");
1110 return;
1111 }
1112 break;
1113
1114 case REQ_SCROLL_PAGE_UP:
1115 lines = view->height;
1116 case REQ_SCROLL_LINE_UP:
1117 if (lines > view->offset)
1118 lines = view->offset;
1119
1120 if (lines == 0) {
1121 report("Cannot scroll beyond the first line");
1122 return;
1123 }
1124
1125 lines = -lines;
1126 break;
1127
1128 default:
1129 die("request %d not handled in switch", request);
1130 }
1131
1132 do_scroll_view(view, lines, TRUE);
1133 }
1134
1135 /* Cursor moving */
1136 static void
1137 move_view(struct view *view, enum request request, bool redraw)
1138 {
1139 int steps;
1140
1141 switch (request) {
1142 case REQ_MOVE_FIRST_LINE:
1143 steps = -view->lineno;
1144 break;
1145
1146 case REQ_MOVE_LAST_LINE:
1147 steps = view->lines - view->lineno - 1;
1148 break;
1149
1150 case REQ_MOVE_PAGE_UP:
1151 steps = view->height > view->lineno
1152 ? -view->lineno : -view->height;
1153 break;
1154
1155 case REQ_MOVE_PAGE_DOWN:
1156 steps = view->lineno + view->height >= view->lines
1157 ? view->lines - view->lineno - 1 : view->height;
1158 break;
1159
1160 case REQ_MOVE_UP:
1161 steps = -1;
1162 break;
1163
1164 case REQ_MOVE_DOWN:
1165 steps = 1;
1166 break;
1167
1168 default:
1169 die("request %d not handled in switch", request);
1170 }
1171
1172 if (steps <= 0 && view->lineno == 0) {
1173 report("Cannot move beyond the first line");
1174 return;
1175
1176 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1177 report("Cannot move beyond the last line");
1178 return;
1179 }
1180
1181 /* Move the current line */
1182 view->lineno += steps;
1183 assert(0 <= view->lineno && view->lineno < view->lines);
1184
1185 /* Repaint the old "current" line if we be scrolling */
1186 if (ABS(steps) < view->height) {
1187 int prev_lineno = view->lineno - steps - view->offset;
1188
1189 wmove(view->win, prev_lineno, 0);
1190 wclrtoeol(view->win);
1191 draw_view_line(view, prev_lineno);
1192 }
1193
1194 /* Check whether the view needs to be scrolled */
1195 if (view->lineno < view->offset ||
1196 view->lineno >= view->offset + view->height) {
1197 if (steps < 0 && -steps > view->offset) {
1198 steps = -view->offset;
1199
1200 } else if (steps > 0) {
1201 if (view->lineno == view->lines - 1 &&
1202 view->lines > view->height) {
1203 steps = view->lines - view->offset - 1;
1204 if (steps >= view->height)
1205 steps -= view->height - 1;
1206 }
1207 }
1208
1209 do_scroll_view(view, steps, redraw);
1210 return;
1211 }
1212
1213 /* Draw the current line */
1214 draw_view_line(view, view->lineno - view->offset);
1215
1216 if (!redraw)
1217 return;
1218
1219 redrawwin(view->win);
1220 wrefresh(view->win);
1221 report("");
1222 }
1223
1224
1225 /*
1226 * Incremental updating
1227 */
1228
1229 static void
1230 end_update(struct view *view)
1231 {
1232 if (!view->pipe)
1233 return;
1234 set_nonblocking_input(FALSE);
1235 if (view->pipe == stdin)
1236 fclose(view->pipe);
1237 else
1238 pclose(view->pipe);
1239 view->pipe = NULL;
1240 }
1241
1242 static bool
1243 begin_update(struct view *view)
1244 {
1245 const char *id = view->id;
1246
1247 if (view->pipe)
1248 end_update(view);
1249
1250 if (opt_cmd[0]) {
1251 string_copy(view->cmd, opt_cmd);
1252 opt_cmd[0] = 0;
1253 /* When running random commands, the view ref could have become
1254 * invalid so clear it. */
1255 view->ref[0] = 0;
1256 } else {
1257 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1258
1259 if (!string_format(view->cmd, format, id, id, id, id, id))
1260 return FALSE;
1261 }
1262
1263 /* Special case for the pager view. */
1264 if (opt_pipe) {
1265 view->pipe = opt_pipe;
1266 opt_pipe = NULL;
1267 } else {
1268 view->pipe = popen(view->cmd, "r");
1269 }
1270
1271 if (!view->pipe)
1272 return FALSE;
1273
1274 set_nonblocking_input(TRUE);
1275
1276 view->offset = 0;
1277 view->lines = 0;
1278 view->lineno = 0;
1279 string_copy(view->vid, id);
1280
1281 if (view->line) {
1282 int i;
1283
1284 for (i = 0; i < view->lines; i++)
1285 if (view->line[i].data)
1286 free(view->line[i].data);
1287
1288 free(view->line);
1289 view->line = NULL;
1290 }
1291
1292 view->start_time = time(NULL);
1293
1294 return TRUE;
1295 }
1296
1297 static struct line *
1298 realloc_lines(struct view *view, size_t line_size)
1299 {
1300 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1301
1302 if (!tmp)
1303 return NULL;
1304
1305 view->line = tmp;
1306 view->line_size = line_size;
1307 return view->line;
1308 }
1309
1310 static bool
1311 update_view(struct view *view)
1312 {
1313 char buffer[BUFSIZ];
1314 char *line;
1315 /* The number of lines to read. If too low it will cause too much
1316 * redrawing (and possible flickering), if too high responsiveness
1317 * will suffer. */
1318 unsigned long lines = view->height;
1319 int redraw_from = -1;
1320
1321 if (!view->pipe)
1322 return TRUE;
1323
1324 /* Only redraw if lines are visible. */
1325 if (view->offset + view->height >= view->lines)
1326 redraw_from = view->lines - view->offset;
1327
1328 if (!realloc_lines(view, view->lines + lines))
1329 goto alloc_error;
1330
1331 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1332 int linelen = strlen(line);
1333
1334 if (linelen)
1335 line[linelen - 1] = 0;
1336
1337 if (!view->ops->read(view, line))
1338 goto alloc_error;
1339
1340 if (lines-- == 1)
1341 break;
1342 }
1343
1344 {
1345 int digits;
1346
1347 lines = view->lines;
1348 for (digits = 0; lines; digits++)
1349 lines /= 10;
1350
1351 /* Keep the displayed view in sync with line number scaling. */
1352 if (digits != view->digits) {
1353 view->digits = digits;
1354 redraw_from = 0;
1355 }
1356 }
1357
1358 if (redraw_from >= 0) {
1359 /* If this is an incremental update, redraw the previous line
1360 * since for commits some members could have changed when
1361 * loading the main view. */
1362 if (redraw_from > 0)
1363 redraw_from--;
1364
1365 /* Incrementally draw avoids flickering. */
1366 redraw_view_from(view, redraw_from);
1367 }
1368
1369 /* Update the title _after_ the redraw so that if the redraw picks up a
1370 * commit reference in view->ref it'll be available here. */
1371 update_view_title(view);
1372
1373 if (ferror(view->pipe)) {
1374 report("Failed to read: %s", strerror(errno));
1375 goto end;
1376
1377 } else if (feof(view->pipe)) {
1378 report("");
1379 goto end;
1380 }
1381
1382 return TRUE;
1383
1384 alloc_error:
1385 report("Allocation failure");
1386
1387 end:
1388 end_update(view);
1389 return FALSE;
1390 }
1391
1392 enum open_flags {
1393 OPEN_DEFAULT = 0, /* Use default view switching. */
1394 OPEN_SPLIT = 1, /* Split current view. */
1395 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
1396 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
1397 };
1398
1399 static void
1400 open_view(struct view *prev, enum request request, enum open_flags flags)
1401 {
1402 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1403 bool split = !!(flags & OPEN_SPLIT);
1404 bool reload = !!(flags & OPEN_RELOAD);
1405 struct view *view = VIEW(request);
1406 int nviews = displayed_views();
1407 struct view *base_view = display[0];
1408
1409 if (view == prev && nviews == 1 && !reload) {
1410 report("Already in %s view", view->name);
1411 return;
1412 }
1413
1414 if (view == VIEW(REQ_VIEW_HELP)) {
1415 load_help_page();
1416
1417 } else if ((reload || strcmp(view->vid, view->id)) &&
1418 !begin_update(view)) {
1419 report("Failed to load %s view", view->name);
1420 return;
1421 }
1422
1423 if (split) {
1424 display[1] = view;
1425 if (!backgrounded)
1426 current_view = 1;
1427 } else {
1428 /* Maximize the current view. */
1429 memset(display, 0, sizeof(display));
1430 current_view = 0;
1431 display[current_view] = view;
1432 }
1433
1434 /* Resize the view when switching between split- and full-screen,
1435 * or when switching between two different full-screen views. */
1436 if (nviews != displayed_views() ||
1437 (nviews == 1 && base_view != display[0]))
1438 resize_display();
1439
1440 if (split && prev->lineno - prev->offset >= prev->height) {
1441 /* Take the title line into account. */
1442 int lines = prev->lineno - prev->offset - prev->height + 1;
1443
1444 /* Scroll the view that was split if the current line is
1445 * outside the new limited view. */
1446 do_scroll_view(prev, lines, TRUE);
1447 }
1448
1449 if (prev && view != prev) {
1450 if (split && !backgrounded) {
1451 /* "Blur" the previous view. */
1452 update_view_title(prev);
1453 }
1454
1455 view->parent = prev;
1456 }
1457
1458 if (view->pipe && view->lines == 0) {
1459 /* Clear the old view and let the incremental updating refill
1460 * the screen. */
1461 wclear(view->win);
1462 report("");
1463 } else {
1464 redraw_view(view);
1465 report("");
1466 }
1467
1468 /* If the view is backgrounded the above calls to report()
1469 * won't redraw the view title. */
1470 if (backgrounded)
1471 update_view_title(view);
1472 }
1473
1474
1475 /*
1476 * User request switch noodle
1477 */
1478
1479 static int
1480 view_driver(struct view *view, enum request request)
1481 {
1482 int i;
1483
1484 switch (request) {
1485 case REQ_MOVE_UP:
1486 case REQ_MOVE_DOWN:
1487 case REQ_MOVE_PAGE_UP:
1488 case REQ_MOVE_PAGE_DOWN:
1489 case REQ_MOVE_FIRST_LINE:
1490 case REQ_MOVE_LAST_LINE:
1491 move_view(view, request, TRUE);
1492 break;
1493
1494 case REQ_SCROLL_LINE_DOWN:
1495 case REQ_SCROLL_LINE_UP:
1496 case REQ_SCROLL_PAGE_DOWN:
1497 case REQ_SCROLL_PAGE_UP:
1498 scroll_view(view, request);
1499 break;
1500
1501 case REQ_VIEW_MAIN:
1502 case REQ_VIEW_DIFF:
1503 case REQ_VIEW_LOG:
1504 case REQ_VIEW_HELP:
1505 case REQ_VIEW_PAGER:
1506 open_view(view, request, OPEN_DEFAULT);
1507 break;
1508
1509 case REQ_NEXT:
1510 case REQ_PREVIOUS:
1511 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1512
1513 if (view == VIEW(REQ_VIEW_DIFF) &&
1514 view->parent == VIEW(REQ_VIEW_MAIN)) {
1515 bool redraw = display[1] == view;
1516
1517 view = view->parent;
1518 move_view(view, request, redraw);
1519 if (redraw)
1520 update_view_title(view);
1521 } else {
1522 move_view(view, request, TRUE);
1523 break;
1524 }
1525 /* Fall-through */
1526
1527 case REQ_ENTER:
1528 if (!view->lines) {
1529 report("Nothing to enter");
1530 break;
1531 }
1532 return view->ops->enter(view, &view->line[view->lineno]);
1533
1534 case REQ_VIEW_NEXT:
1535 {
1536 int nviews = displayed_views();
1537 int next_view = (current_view + 1) % nviews;
1538
1539 if (next_view == current_view) {
1540 report("Only one view is displayed");
1541 break;
1542 }
1543
1544 current_view = next_view;
1545 /* Blur out the title of the previous view. */
1546 update_view_title(view);
1547 report("");
1548 break;
1549 }
1550 case REQ_TOGGLE_LINENO:
1551 opt_line_number = !opt_line_number;
1552 redraw_display();
1553 break;
1554
1555 case REQ_TOGGLE_REV_GRAPH:
1556 opt_rev_graph = !opt_rev_graph;
1557 redraw_display();
1558 break;
1559
1560 case REQ_PROMPT:
1561 /* Always reload^Wrerun commands from the prompt. */
1562 open_view(view, opt_request, OPEN_RELOAD);
1563 break;
1564
1565 case REQ_STOP_LOADING:
1566 for (i = 0; i < ARRAY_SIZE(views); i++) {
1567 view = &views[i];
1568 if (view->pipe)
1569 report("Stopped loading the %s view", view->name),
1570 end_update(view);
1571 }
1572 break;
1573
1574 case REQ_SHOW_VERSION:
1575 report("%s (built %s)", VERSION, __DATE__);
1576 return TRUE;
1577
1578 case REQ_SCREEN_RESIZE:
1579 resize_display();
1580 /* Fall-through */
1581 case REQ_SCREEN_REDRAW:
1582 redraw_display();
1583 break;
1584
1585 case REQ_SCREEN_UPDATE:
1586 doupdate();
1587 return TRUE;
1588
1589 case REQ_VIEW_CLOSE:
1590 /* XXX: Mark closed views by letting view->parent point to the
1591 * view itself. Parents to closed view should never be
1592 * followed. */
1593 if (view->parent &&
1594 view->parent->parent != view->parent) {
1595 memset(display, 0, sizeof(display));
1596 current_view = 0;
1597 display[current_view] = view->parent;
1598 view->parent = view;
1599 resize_display();
1600 redraw_display();
1601 break;
1602 }
1603 /* Fall-through */
1604 case REQ_QUIT:
1605 return FALSE;
1606
1607 default:
1608 /* An unknown key will show most commonly used commands. */
1609 report("Unknown key, press 'h' for help");
1610 return TRUE;
1611 }
1612
1613 return TRUE;
1614 }
1615
1616
1617 /*
1618 * Pager backend
1619 */
1620
1621 static bool
1622 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1623 {
1624 char *text = line->data;
1625 enum line_type type = line->type;
1626 int textlen = strlen(text);
1627 int attr;
1628
1629 wmove(view->win, lineno, 0);
1630
1631 if (view->offset + lineno == view->lineno) {
1632 if (type == LINE_COMMIT) {
1633 string_copy(view->ref, text + 7);
1634 string_copy(ref_commit, view->ref);
1635 }
1636
1637 type = LINE_CURSOR;
1638 wchgat(view->win, -1, 0, type, NULL);
1639 }
1640
1641 attr = get_line_attr(type);
1642 wattrset(view->win, attr);
1643
1644 if (opt_line_number || opt_tab_size < TABSIZE) {
1645 static char spaces[] = " ";
1646 int col_offset = 0, col = 0;
1647
1648 if (opt_line_number) {
1649 unsigned long real_lineno = view->offset + lineno + 1;
1650
1651 if (real_lineno == 1 ||
1652 (real_lineno % opt_num_interval) == 0) {
1653 wprintw(view->win, "%.*d", view->digits, real_lineno);
1654
1655 } else {
1656 waddnstr(view->win, spaces,
1657 MIN(view->digits, STRING_SIZE(spaces)));
1658 }
1659 waddstr(view->win, ": ");
1660 col_offset = view->digits + 2;
1661 }
1662
1663 while (text && col_offset + col < view->width) {
1664 int cols_max = view->width - col_offset - col;
1665 char *pos = text;
1666 int cols;
1667
1668 if (*text == '\t') {
1669 text++;
1670 assert(sizeof(spaces) > TABSIZE);
1671 pos = spaces;
1672 cols = opt_tab_size - (col % opt_tab_size);
1673
1674 } else {
1675 text = strchr(text, '\t');
1676 cols = line ? text - pos : strlen(pos);
1677 }
1678
1679 waddnstr(view->win, pos, MIN(cols, cols_max));
1680 col += cols;
1681 }
1682
1683 } else {
1684 int col = 0, pos = 0;
1685
1686 for (; pos < textlen && col < view->width; pos++, col++)
1687 if (text[pos] == '\t')
1688 col += TABSIZE - (col % TABSIZE) - 1;
1689
1690 waddnstr(view->win, text, pos);
1691 }
1692
1693 return TRUE;
1694 }
1695
1696 static void
1697 add_pager_refs(struct view *view, struct line *line)
1698 {
1699 char buf[1024];
1700 char *data = line->data;
1701 struct ref **refs;
1702 int bufpos = 0, refpos = 0;
1703 const char *sep = "Refs: ";
1704
1705 assert(line->type == LINE_COMMIT);
1706
1707 refs = get_refs(data + STRING_SIZE("commit "));
1708 if (!refs)
1709 return;
1710
1711 do {
1712 struct ref *ref = refs[refpos];
1713 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
1714
1715 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
1716 return;
1717 sep = ", ";
1718 } while (refs[refpos++]->next);
1719
1720 if (!realloc_lines(view, view->line_size + 1))
1721 return;
1722
1723 line = &view->line[view->lines];
1724 line->data = strdup(buf);
1725 if (!line->data)
1726 return;
1727
1728 line->type = LINE_PP_REFS;
1729 view->lines++;
1730 }
1731
1732 static bool
1733 pager_read(struct view *view, char *data)
1734 {
1735 struct line *line = &view->line[view->lines];
1736
1737 line->data = strdup(data);
1738 if (!line->data)
1739 return FALSE;
1740
1741 line->type = get_line_type(line->data);
1742 view->lines++;
1743
1744 if (line->type == LINE_COMMIT &&
1745 (view == VIEW(REQ_VIEW_DIFF) ||
1746 view == VIEW(REQ_VIEW_LOG)))
1747 add_pager_refs(view, line);
1748
1749 return TRUE;
1750 }
1751
1752 static bool
1753 pager_enter(struct view *view, struct line *line)
1754 {
1755 int split = 0;
1756
1757 if (line->type == LINE_COMMIT &&
1758 (view == VIEW(REQ_VIEW_LOG) ||
1759 view == VIEW(REQ_VIEW_PAGER))) {
1760 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1761 split = 1;
1762 }
1763
1764 /* Always scroll the view even if it was split. That way
1765 * you can use Enter to scroll through the log view and
1766 * split open each commit diff. */
1767 scroll_view(view, REQ_SCROLL_LINE_DOWN);
1768
1769 /* FIXME: A minor workaround. Scrolling the view will call report("")
1770 * but if we are scrolling a non-current view this won't properly
1771 * update the view title. */
1772 if (split)
1773 update_view_title(view);
1774
1775 return TRUE;
1776 }
1777
1778 static struct view_ops pager_ops = {
1779 "line",
1780 pager_draw,
1781 pager_read,
1782 pager_enter,
1783 };
1784
1785
1786 /*
1787 * Main view backend
1788 */
1789
1790 struct commit {
1791 char id[41]; /* SHA1 ID. */
1792 char title[75]; /* First line of the commit message. */
1793 char author[75]; /* Author of the commit. */
1794 struct tm time; /* Date from the author ident. */
1795 struct ref **refs; /* Repository references. */
1796 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
1797 size_t graph_size; /* The width of the graph array. */
1798 };
1799
1800 static bool
1801 main_draw(struct view *view, struct line *line, unsigned int lineno)
1802 {
1803 char buf[DATE_COLS + 1];
1804 struct commit *commit = line->data;
1805 enum line_type type;
1806 int col = 0;
1807 size_t timelen;
1808 size_t authorlen;
1809 int trimmed = 1;
1810
1811 if (!*commit->author)
1812 return FALSE;
1813
1814 wmove(view->win, lineno, col);
1815
1816 if (view->offset + lineno == view->lineno) {
1817 string_copy(view->ref, commit->id);
1818 string_copy(ref_commit, view->ref);
1819 type = LINE_CURSOR;
1820 wattrset(view->win, get_line_attr(type));
1821 wchgat(view->win, -1, 0, type, NULL);
1822
1823 } else {
1824 type = LINE_MAIN_COMMIT;
1825 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
1826 }
1827
1828 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
1829 waddnstr(view->win, buf, timelen);
1830 waddstr(view->win, " ");
1831
1832 col += DATE_COLS;
1833 wmove(view->win, lineno, col);
1834 if (type != LINE_CURSOR)
1835 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
1836
1837 if (opt_utf8) {
1838 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1839 } else {
1840 authorlen = strlen(commit->author);
1841 if (authorlen > AUTHOR_COLS - 2) {
1842 authorlen = AUTHOR_COLS - 2;
1843 trimmed = 1;
1844 }
1845 }
1846
1847 if (trimmed) {
1848 waddnstr(view->win, commit->author, authorlen);
1849 if (type != LINE_CURSOR)
1850 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
1851 waddch(view->win, '~');
1852 } else {
1853 waddstr(view->win, commit->author);
1854 }
1855
1856 col += AUTHOR_COLS;
1857 if (type != LINE_CURSOR)
1858 wattrset(view->win, A_NORMAL);
1859
1860 if (opt_rev_graph && commit->graph_size) {
1861 size_t i;
1862
1863 wmove(view->win, lineno, col);
1864 /* Using waddch() instead of waddnstr() ensures that
1865 * they'll be rendered correctly for the cursor line. */
1866 for (i = 0; i < commit->graph_size; i++)
1867 waddch(view->win, commit->graph[i]);
1868
1869 col += commit->graph_size + 1;
1870 }
1871
1872 wmove(view->win, lineno, col);
1873
1874 if (commit->refs) {
1875 size_t i = 0;
1876
1877 do {
1878 if (type == LINE_CURSOR)
1879 ;
1880 else if (commit->refs[i]->tag)
1881 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1882 else
1883 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1884 waddstr(view->win, "[");
1885 waddstr(view->win, commit->refs[i]->name);
1886 waddstr(view->win, "]");
1887 if (type != LINE_CURSOR)
1888 wattrset(view->win, A_NORMAL);
1889 waddstr(view->win, " ");
1890 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
1891 } while (commit->refs[i++]->next);
1892 }
1893
1894 if (type != LINE_CURSOR)
1895 wattrset(view->win, get_line_attr(type));
1896
1897 {
1898 int titlelen = strlen(commit->title);
1899
1900 if (col + titlelen > view->width)
1901 titlelen = view->width - col;
1902
1903 waddnstr(view->win, commit->title, titlelen);
1904 }
1905
1906 return TRUE;
1907 }
1908
1909 /* Reads git log --pretty=raw output and parses it into the commit struct. */
1910 static bool
1911 main_read(struct view *view, char *line)
1912 {
1913 enum line_type type = get_line_type(line);
1914 struct commit *commit = view->lines
1915 ? view->line[view->lines - 1].data : NULL;
1916
1917 switch (type) {
1918 case LINE_COMMIT:
1919 commit = calloc(1, sizeof(struct commit));
1920 if (!commit)
1921 return FALSE;
1922
1923 line += STRING_SIZE("commit ");
1924
1925 view->line[view->lines++].data = commit;
1926 string_copy(commit->id, line);
1927 commit->refs = get_refs(commit->id);
1928 commit->graph[commit->graph_size++] = ACS_LTEE;
1929 break;
1930
1931 case LINE_AUTHOR:
1932 {
1933 char *ident = line + STRING_SIZE("author ");
1934 char *end = strchr(ident, '<');
1935
1936 if (!commit)
1937 break;
1938
1939 if (end) {
1940 for (; end > ident && isspace(end[-1]); end--) ;
1941 *end = 0;
1942 }
1943
1944 string_copy(commit->author, ident);
1945
1946 /* Parse epoch and timezone */
1947 if (end) {
1948 char *secs = strchr(end + 1, '>');
1949 char *zone;
1950 time_t time;
1951
1952 if (!secs || secs[1] != ' ')
1953 break;
1954
1955 secs += 2;
1956 time = (time_t) atol(secs);
1957 zone = strchr(secs, ' ');
1958 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
1959 long tz;
1960
1961 zone++;
1962 tz = ('0' - zone[1]) * 60 * 60 * 10;
1963 tz += ('0' - zone[2]) * 60 * 60;
1964 tz += ('0' - zone[3]) * 60;
1965 tz += ('0' - zone[4]) * 60;
1966
1967 if (zone[0] == '-')
1968 tz = -tz;
1969
1970 time -= tz;
1971 }
1972 gmtime_r(&time, &commit->time);
1973 }
1974 break;
1975 }
1976 default:
1977 if (!commit)
1978 break;
1979
1980 /* Fill in the commit title if it has not already been set. */
1981 if (commit->title[0])
1982 break;
1983
1984 /* Require titles to start with a non-space character at the
1985 * offset used by git log. */
1986 /* FIXME: More gracefull handling of titles; append "..." to
1987 * shortened titles, etc. */
1988 if (strncmp(line, " ", 4) ||
1989 isspace(line[4]))
1990 break;
1991
1992 string_copy(commit->title, line + 4);
1993 }
1994
1995 return TRUE;
1996 }
1997
1998 static bool
1999 main_enter(struct view *view, struct line *line)
2000 {
2001 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2002
2003 open_view(view, REQ_VIEW_DIFF, flags);
2004 return TRUE;
2005 }
2006
2007 static struct view_ops main_ops = {
2008 "commit",
2009 main_draw,
2010 main_read,
2011 main_enter,
2012 };
2013
2014
2015 /*
2016 * Keys
2017 */
2018
2019 struct keymap {
2020 int alias;
2021 int request;
2022 };
2023
2024 static struct keymap keymap[] = {
2025 /* View switching */
2026 { 'm', REQ_VIEW_MAIN },
2027 { 'd', REQ_VIEW_DIFF },
2028 { 'l', REQ_VIEW_LOG },
2029 { 'p', REQ_VIEW_PAGER },
2030 { 'h', REQ_VIEW_HELP },
2031 { '?', REQ_VIEW_HELP },
2032
2033 /* View manipulation */
2034 { 'q', REQ_VIEW_CLOSE },
2035 { KEY_TAB, REQ_VIEW_NEXT },
2036 { KEY_RETURN, REQ_ENTER },
2037 { KEY_UP, REQ_PREVIOUS },
2038 { KEY_DOWN, REQ_NEXT },
2039
2040 /* Cursor navigation */
2041 { 'k', REQ_MOVE_UP },
2042 { 'j', REQ_MOVE_DOWN },
2043 { KEY_HOME, REQ_MOVE_FIRST_LINE },
2044 { KEY_END, REQ_MOVE_LAST_LINE },
2045 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
2046 { ' ', REQ_MOVE_PAGE_DOWN },
2047 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
2048 { 'b', REQ_MOVE_PAGE_UP },
2049 { '-', REQ_MOVE_PAGE_UP },
2050
2051 /* Scrolling */
2052 { KEY_IC, REQ_SCROLL_LINE_UP },
2053 { KEY_DC, REQ_SCROLL_LINE_DOWN },
2054 { 'w', REQ_SCROLL_PAGE_UP },
2055 { 's', REQ_SCROLL_PAGE_DOWN },
2056
2057 /* Misc */
2058 { 'Q', REQ_QUIT },
2059 { 'z', REQ_STOP_LOADING },
2060 { 'v', REQ_SHOW_VERSION },
2061 { 'r', REQ_SCREEN_REDRAW },
2062 { 'n', REQ_TOGGLE_LINENO },
2063 { 'g', REQ_TOGGLE_REV_GRAPH},
2064 { ':', REQ_PROMPT },
2065
2066 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
2067 { ERR, REQ_SCREEN_UPDATE },
2068
2069 /* Use the ncurses SIGWINCH handler. */
2070 { KEY_RESIZE, REQ_SCREEN_RESIZE },
2071 };
2072
2073 static enum request
2074 get_request(int key)
2075 {
2076 int i;
2077
2078 for (i = 0; i < ARRAY_SIZE(keymap); i++)
2079 if (keymap[i].alias == key)
2080 return keymap[i].request;
2081
2082 return (enum request) key;
2083 }
2084
2085 struct key {
2086 char *name;
2087 int value;
2088 };
2089
2090 static struct key key_table[] = {
2091 { "Enter", KEY_RETURN },
2092 { "Space", ' ' },
2093 { "Backspace", KEY_BACKSPACE },
2094 { "Tab", KEY_TAB },
2095 { "Escape", KEY_ESC },
2096 { "Left", KEY_LEFT },
2097 { "Right", KEY_RIGHT },
2098 { "Up", KEY_UP },
2099 { "Down", KEY_DOWN },
2100 { "Insert", KEY_IC },
2101 { "Delete", KEY_DC },
2102 { "Home", KEY_HOME },
2103 { "End", KEY_END },
2104 { "PageUp", KEY_PPAGE },
2105 { "PageDown", KEY_NPAGE },
2106 { "F1", KEY_F(1) },
2107 { "F2", KEY_F(2) },
2108 { "F3", KEY_F(3) },
2109 { "F4", KEY_F(4) },
2110 { "F5", KEY_F(5) },
2111 { "F6", KEY_F(6) },
2112 { "F7", KEY_F(7) },
2113 { "F8", KEY_F(8) },
2114 { "F9", KEY_F(9) },
2115 { "F10", KEY_F(10) },
2116 { "F11", KEY_F(11) },
2117 { "F12", KEY_F(12) },
2118 };
2119
2120 static char *
2121 get_key(enum request request)
2122 {
2123 static char buf[BUFSIZ];
2124 static char key_char[] = "'X'";
2125 int pos = 0;
2126 char *sep = " ";
2127 int i;
2128
2129 buf[pos] = 0;
2130
2131 for (i = 0; i < ARRAY_SIZE(keymap); i++) {
2132 char *seq = NULL;
2133 int key;
2134
2135 if (keymap[i].request != request)
2136 continue;
2137
2138 for (key = 0; key < ARRAY_SIZE(key_table); key++)
2139 if (key_table[key].value == keymap[i].alias)
2140 seq = key_table[key].name;
2141
2142 if (seq == NULL &&
2143 keymap[i].alias < 127 &&
2144 isprint(keymap[i].alias)) {
2145 key_char[1] = (char) keymap[i].alias;
2146 seq = key_char;
2147 }
2148
2149 if (!seq)
2150 seq = "'?'";
2151
2152 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
2153 return "Too many keybindings!";
2154 sep = ", ";
2155 }
2156
2157 return buf;
2158 }
2159
2160 static void load_help_page(void)
2161 {
2162 char buf[BUFSIZ];
2163 struct view *view = VIEW(REQ_VIEW_HELP);
2164 int lines = ARRAY_SIZE(req_info) + 2;
2165 int i;
2166
2167 if (view->lines > 0)
2168 return;
2169
2170 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2171 if (!req_info[i].request)
2172 lines++;
2173
2174 view->line = calloc(lines, sizeof(*view->line));
2175 if (!view->line) {
2176 report("Allocation failure");
2177 return;
2178 }
2179
2180 pager_read(view, "Quick reference for tig keybindings:");
2181
2182 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2183 char *key;
2184
2185 if (!req_info[i].request) {
2186 pager_read(view, "");
2187 pager_read(view, req_info[i].help);
2188 continue;
2189 }
2190
2191 key = get_key(req_info[i].request);
2192 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
2193 continue;
2194
2195 pager_read(view, buf);
2196 }
2197 }
2198
2199
2200 /*
2201 * Unicode / UTF-8 handling
2202 *
2203 * NOTE: Much of the following code for dealing with unicode is derived from
2204 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2205 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2206 */
2207
2208 /* I've (over)annotated a lot of code snippets because I am not entirely
2209 * confident that the approach taken by this small UTF-8 interface is correct.
2210 * --jonas */
2211
2212 static inline int
2213 unicode_width(unsigned long c)
2214 {
2215 if (c >= 0x1100 &&
2216 (c <= 0x115f /* Hangul Jamo */
2217 || c == 0x2329
2218 || c == 0x232a
2219 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
2220 /* CJK ... Yi */
2221 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
2222 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
2223 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
2224 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
2225 || (c >= 0xffe0 && c <= 0xffe6)
2226 || (c >= 0x20000 && c <= 0x2fffd)
2227 || (c >= 0x30000 && c <= 0x3fffd)))
2228 return 2;
2229
2230 return 1;
2231 }
2232
2233 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2234 * Illegal bytes are set one. */
2235 static const unsigned char utf8_bytes[256] = {
2236 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,
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 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,
2243 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,
2244 };
2245
2246 /* Decode UTF-8 multi-byte representation into a unicode character. */
2247 static inline unsigned long
2248 utf8_to_unicode(const char *string, size_t length)
2249 {
2250 unsigned long unicode;
2251
2252 switch (length) {
2253 case 1:
2254 unicode = string[0];
2255 break;
2256 case 2:
2257 unicode = (string[0] & 0x1f) << 6;
2258 unicode += (string[1] & 0x3f);
2259 break;
2260 case 3:
2261 unicode = (string[0] & 0x0f) << 12;
2262 unicode += ((string[1] & 0x3f) << 6);
2263 unicode += (string[2] & 0x3f);
2264 break;
2265 case 4:
2266 unicode = (string[0] & 0x0f) << 18;
2267 unicode += ((string[1] & 0x3f) << 12);
2268 unicode += ((string[2] & 0x3f) << 6);
2269 unicode += (string[3] & 0x3f);
2270 break;
2271 case 5:
2272 unicode = (string[0] & 0x0f) << 24;
2273 unicode += ((string[1] & 0x3f) << 18);
2274 unicode += ((string[2] & 0x3f) << 12);
2275 unicode += ((string[3] & 0x3f) << 6);
2276 unicode += (string[4] & 0x3f);
2277 break;
2278 case 6:
2279 unicode = (string[0] & 0x01) << 30;
2280 unicode += ((string[1] & 0x3f) << 24);
2281 unicode += ((string[2] & 0x3f) << 18);
2282 unicode += ((string[3] & 0x3f) << 12);
2283 unicode += ((string[4] & 0x3f) << 6);
2284 unicode += (string[5] & 0x3f);
2285 break;
2286 default:
2287 die("Invalid unicode length");
2288 }
2289
2290 /* Invalid characters could return the special 0xfffd value but NUL
2291 * should be just as good. */
2292 return unicode > 0xffff ? 0 : unicode;
2293 }
2294
2295 /* Calculates how much of string can be shown within the given maximum width
2296 * and sets trimmed parameter to non-zero value if all of string could not be
2297 * shown.
2298 *
2299 * Additionally, adds to coloffset how many many columns to move to align with
2300 * the expected position. Takes into account how multi-byte and double-width
2301 * characters will effect the cursor position.
2302 *
2303 * Returns the number of bytes to output from string to satisfy max_width. */
2304 static size_t
2305 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2306 {
2307 const char *start = string;
2308 const char *end = strchr(string, '\0');
2309 size_t mbwidth = 0;
2310 size_t width = 0;
2311
2312 *trimmed = 0;
2313
2314 while (string < end) {
2315 int c = *(unsigned char *) string;
2316 unsigned char bytes = utf8_bytes[c];
2317 size_t ucwidth;
2318 unsigned long unicode;
2319
2320 if (string + bytes > end)
2321 break;
2322
2323 /* Change representation to figure out whether
2324 * it is a single- or double-width character. */
2325
2326 unicode = utf8_to_unicode(string, bytes);
2327 /* FIXME: Graceful handling of invalid unicode character. */
2328 if (!unicode)
2329 break;
2330
2331 ucwidth = unicode_width(unicode);
2332 width += ucwidth;
2333 if (width > max_width) {
2334 *trimmed = 1;
2335 break;
2336 }
2337
2338 /* The column offset collects the differences between the
2339 * number of bytes encoding a character and the number of
2340 * columns will be used for rendering said character.
2341 *
2342 * So if some character A is encoded in 2 bytes, but will be
2343 * represented on the screen using only 1 byte this will and up
2344 * adding 1 to the multi-byte column offset.
2345 *
2346 * Assumes that no double-width character can be encoding in
2347 * less than two bytes. */
2348 if (bytes > ucwidth)
2349 mbwidth += bytes - ucwidth;
2350
2351 string += bytes;
2352 }
2353
2354 *coloffset += mbwidth;
2355
2356 return string - start;
2357 }
2358
2359
2360 /*
2361 * Status management
2362 */
2363
2364 /* Whether or not the curses interface has been initialized. */
2365 static bool cursed = FALSE;
2366
2367 /* The status window is used for polling keystrokes. */
2368 static WINDOW *status_win;
2369
2370 /* Update status and title window. */
2371 static void
2372 report(const char *msg, ...)
2373 {
2374 static bool empty = TRUE;
2375 struct view *view = display[current_view];
2376
2377 if (!empty || *msg) {
2378 va_list args;
2379
2380 va_start(args, msg);
2381
2382 werase(status_win);
2383 wmove(status_win, 0, 0);
2384 if (*msg) {
2385 vwprintw(status_win, msg, args);
2386 empty = FALSE;
2387 } else {
2388 empty = TRUE;
2389 }
2390 wrefresh(status_win);
2391
2392 va_end(args);
2393 }
2394
2395 update_view_title(view);
2396 update_display_cursor();
2397 }
2398
2399 /* Controls when nodelay should be in effect when polling user input. */
2400 static void
2401 set_nonblocking_input(bool loading)
2402 {
2403 static unsigned int loading_views;
2404
2405 if ((loading == FALSE && loading_views-- == 1) ||
2406 (loading == TRUE && loading_views++ == 0))
2407 nodelay(status_win, loading);
2408 }
2409
2410 static void
2411 init_display(void)
2412 {
2413 int x, y;
2414
2415 /* Initialize the curses library */
2416 if (isatty(STDIN_FILENO)) {
2417 cursed = !!initscr();
2418 } else {
2419 /* Leave stdin and stdout alone when acting as a pager. */
2420 FILE *io = fopen("/dev/tty", "r+");
2421
2422 cursed = !!newterm(NULL, io, io);
2423 }
2424
2425 if (!cursed)
2426 die("Failed to initialize curses");
2427
2428 nonl(); /* Tell curses not to do NL->CR/NL on output */
2429 cbreak(); /* Take input chars one at a time, no wait for \n */
2430 noecho(); /* Don't echo input */
2431 leaveok(stdscr, TRUE);
2432
2433 if (has_colors())
2434 init_colors();
2435
2436 getmaxyx(stdscr, y, x);
2437 status_win = newwin(1, 0, y - 1, 0);
2438 if (!status_win)
2439 die("Failed to create status window");
2440
2441 /* Enable keyboard mapping */
2442 keypad(status_win, TRUE);
2443 wbkgdset(status_win, get_line_attr(LINE_STATUS));
2444 }
2445
2446
2447 /*
2448 * Repository references
2449 */
2450
2451 static struct ref *refs;
2452 static size_t refs_size;
2453
2454 /* Id <-> ref store */
2455 static struct ref ***id_refs;
2456 static size_t id_refs_size;
2457
2458 static struct ref **
2459 get_refs(char *id)
2460 {
2461 struct ref ***tmp_id_refs;
2462 struct ref **ref_list = NULL;
2463 size_t ref_list_size = 0;
2464 size_t i;
2465
2466 for (i = 0; i < id_refs_size; i++)
2467 if (!strcmp(id, id_refs[i][0]->id))
2468 return id_refs[i];
2469
2470 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2471 if (!tmp_id_refs)
2472 return NULL;
2473
2474 id_refs = tmp_id_refs;
2475
2476 for (i = 0; i < refs_size; i++) {
2477 struct ref **tmp;
2478
2479 if (strcmp(id, refs[i].id))
2480 continue;
2481
2482 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2483 if (!tmp) {
2484 if (ref_list)
2485 free(ref_list);
2486 return NULL;
2487 }
2488
2489 ref_list = tmp;
2490 if (ref_list_size > 0)
2491 ref_list[ref_list_size - 1]->next = 1;
2492 ref_list[ref_list_size] = &refs[i];
2493
2494 /* XXX: The properties of the commit chains ensures that we can
2495 * safely modify the shared ref. The repo references will
2496 * always be similar for the same id. */
2497 ref_list[ref_list_size]->next = 0;
2498 ref_list_size++;
2499 }
2500
2501 if (ref_list)
2502 id_refs[id_refs_size++] = ref_list;
2503
2504 return ref_list;
2505 }
2506
2507 static int
2508 read_ref(char *id, int idlen, char *name, int namelen)
2509 {
2510 struct ref *ref;
2511 bool tag = FALSE;
2512
2513 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2514 /* Commits referenced by tags has "^{}" appended. */
2515 if (name[namelen - 1] != '}')
2516 return OK;
2517
2518 while (namelen > 0 && name[namelen] != '^')
2519 namelen--;
2520
2521 tag = TRUE;
2522 namelen -= STRING_SIZE("refs/tags/");
2523 name += STRING_SIZE("refs/tags/");
2524
2525 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2526 namelen -= STRING_SIZE("refs/heads/");
2527 name += STRING_SIZE("refs/heads/");
2528
2529 } else if (!strcmp(name, "HEAD")) {
2530 return OK;
2531 }
2532
2533 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2534 if (!refs)
2535 return ERR;
2536
2537 ref = &refs[refs_size++];
2538 ref->name = malloc(namelen + 1);
2539 if (!ref->name)
2540 return ERR;
2541
2542 strncpy(ref->name, name, namelen);
2543 ref->name[namelen] = 0;
2544 ref->tag = tag;
2545 string_copy(ref->id, id);
2546
2547 return OK;
2548 }
2549
2550 static int
2551 load_refs(void)
2552 {
2553 const char *cmd_env = getenv("TIG_LS_REMOTE");
2554 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2555
2556 return read_properties(popen(cmd, "r"), "\t", read_ref);
2557 }
2558
2559 static int
2560 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2561 {
2562 if (!strcmp(name, "i18n.commitencoding"))
2563 string_copy(opt_encoding, value);
2564
2565 return OK;
2566 }
2567
2568 static int
2569 load_repo_config(void)
2570 {
2571 return read_properties(popen("git repo-config --list", "r"),
2572 "=", read_repo_config_option);
2573 }
2574
2575 static int
2576 read_properties(FILE *pipe, const char *separators,
2577 int (*read_property)(char *, int, char *, int))
2578 {
2579 char buffer[BUFSIZ];
2580 char *name;
2581 int state = OK;
2582
2583 if (!pipe)
2584 return ERR;
2585
2586 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2587 char *value;
2588 size_t namelen;
2589 size_t valuelen;
2590
2591 name = chomp_string(name);
2592 namelen = strcspn(name, separators);
2593
2594 if (name[namelen]) {
2595 name[namelen] = 0;
2596 value = chomp_string(name + namelen + 1);
2597 valuelen = strlen(value);
2598
2599 } else {
2600 value = "";
2601 valuelen = 0;
2602 }
2603
2604 state = read_property(name, namelen, value, valuelen);
2605 }
2606
2607 if (state != ERR && ferror(pipe))
2608 state = ERR;
2609
2610 pclose(pipe);
2611
2612 return state;
2613 }
2614
2615
2616 /*
2617 * Main
2618 */
2619
2620 static void __NORETURN
2621 quit(int sig)
2622 {
2623 /* XXX: Restore tty modes and let the OS cleanup the rest! */
2624 if (cursed)
2625 endwin();
2626 exit(0);
2627 }
2628
2629 static void __NORETURN
2630 die(const char *err, ...)
2631 {
2632 va_list args;
2633
2634 endwin();
2635
2636 va_start(args, err);
2637 fputs("tig: ", stderr);
2638 vfprintf(stderr, err, args);
2639 fputs("\n", stderr);
2640 va_end(args);
2641
2642 exit(1);
2643 }
2644
2645 int
2646 main(int argc, char *argv[])
2647 {
2648 struct view *view;
2649 enum request request;
2650 size_t i;
2651
2652 signal(SIGINT, quit);
2653
2654 if (load_options() == ERR)
2655 die("Failed to load user config.");
2656
2657 /* Load the repo config file so options can be overwritten from
2658 * the command line. */
2659 if (load_repo_config() == ERR)
2660 die("Failed to load repo config.");
2661
2662 if (!parse_options(argc, argv))
2663 return 0;
2664
2665 if (load_refs() == ERR)
2666 die("Failed to load refs.");
2667
2668 /* Require a git repository unless when running in pager mode. */
2669 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2670 die("Not a git repository");
2671
2672 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2673 view->cmd_env = getenv(view->cmd_env);
2674
2675 request = opt_request;
2676
2677 init_display();
2678
2679 while (view_driver(display[current_view], request)) {
2680 int key;
2681 int i;
2682
2683 foreach_view (view, i)
2684 update_view(view);
2685
2686 /* Refresh, accept single keystroke of input */
2687 key = wgetch(status_win);
2688 request = get_request(key);
2689
2690 /* Some low-level request handling. This keeps access to
2691 * status_win restricted. */
2692 switch (request) {
2693 case REQ_PROMPT:
2694 report(":");
2695 /* Temporarily switch to line-oriented and echoed
2696 * input. */
2697 nocbreak();
2698 echo();
2699
2700 if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2701 memcpy(opt_cmd, "git ", 4);
2702 opt_request = REQ_VIEW_PAGER;
2703 } else {
2704 report("Prompt interrupted by loading view, "
2705 "press 'z' to stop loading views");
2706 request = REQ_SCREEN_UPDATE;
2707 }
2708
2709 noecho();
2710 cbreak();
2711 break;
2712
2713 case REQ_SCREEN_RESIZE:
2714 {
2715 int height, width;
2716
2717 getmaxyx(stdscr, height, width);
2718
2719 /* Resize the status view and let the view driver take
2720 * care of resizing the displayed views. */
2721 wresize(status_win, 1, width);
2722 mvwin(status_win, height - 1, 0);
2723 wrefresh(status_win);
2724 break;
2725 }
2726 default:
2727 break;
2728 }
2729 }
2730
2731 quit(0);
2732
2733 return 0;
2734 }