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