Refactor current line activation to new select view operation
[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.4.git"
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 <sys/types.h>
34 #include <regex.h>
35
36 #include <locale.h>
37 #include <langinfo.h>
38 #include <iconv.h>
39
40 #include <curses.h>
41
42 #if __GNUC__ >= 3
43 #define __NORETURN __attribute__((__noreturn__))
44 #else
45 #define __NORETURN
46 #endif
47
48 static void __NORETURN die(const char *err, ...);
49 static void report(const char *msg, ...);
50 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
51 static void set_nonblocking_input(bool loading);
52 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
53
54 #define ABS(x) ((x) >= 0 ? (x) : -(x))
55 #define MIN(x, y) ((x) < (y) ? (x) : (y))
56
57 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
58 #define STRING_SIZE(x) (sizeof(x) - 1)
59
60 #define SIZEOF_STR 1024 /* Default string size. */
61 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
62 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
63
64 /* This color name can be used to refer to the default term colors. */
65 #define COLOR_DEFAULT (-1)
66
67 #define ICONV_NONE ((iconv_t) -1)
68
69 /* The format and size of the date column in the main view. */
70 #define DATE_FORMAT "%Y-%m-%d %H:%M"
71 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
72
73 #define AUTHOR_COLS 20
74
75 /* The default interval between line numbers. */
76 #define NUMBER_INTERVAL 1
77
78 #define TABSIZE 8
79
80 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
81
82 #define TIG_LS_REMOTE \
83 "git ls-remote . 2>/dev/null"
84
85 #define TIG_DIFF_CMD \
86 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
87
88 #define TIG_LOG_CMD \
89 "git log --cc --stat -n100 %s 2>/dev/null"
90
91 #define TIG_MAIN_CMD \
92 "git log --topo-order --pretty=raw %s 2>/dev/null"
93
94 #define TIG_TREE_CMD \
95 "git ls-tree %s %s"
96
97 #define TIG_BLOB_CMD \
98 "git cat-file blob %s"
99
100 /* XXX: Needs to be defined to the empty string. */
101 #define TIG_HELP_CMD ""
102 #define TIG_PAGER_CMD ""
103
104 /* Some ascii-shorthands fitted into the ncurses namespace. */
105 #define KEY_TAB '\t'
106 #define KEY_RETURN '\r'
107 #define KEY_ESC 27
108
109
110 struct ref {
111 char *name; /* Ref name; tag or head names are shortened. */
112 char id[41]; /* Commit SHA1 ID */
113 unsigned int tag:1; /* Is it a tag? */
114 unsigned int next:1; /* For ref lists: are there more refs? */
115 };
116
117 static struct ref **get_refs(char *id);
118
119 struct int_map {
120 const char *name;
121 int namelen;
122 int value;
123 };
124
125 static int
126 set_from_int_map(struct int_map *map, size_t map_size,
127 int *value, const char *name, int namelen)
128 {
129
130 int i;
131
132 for (i = 0; i < map_size; i++)
133 if (namelen == map[i].namelen &&
134 !strncasecmp(name, map[i].name, namelen)) {
135 *value = map[i].value;
136 return OK;
137 }
138
139 return ERR;
140 }
141
142
143 /*
144 * String helpers
145 */
146
147 static inline void
148 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
149 {
150 if (srclen > dstlen - 1)
151 srclen = dstlen - 1;
152
153 strncpy(dst, src, srclen);
154 dst[srclen] = 0;
155 }
156
157 /* Shorthands for safely copying into a fixed buffer. */
158
159 #define string_copy(dst, src) \
160 string_ncopy_do(dst, sizeof(dst), src, sizeof(dst))
161
162 #define string_ncopy(dst, src, srclen) \
163 string_ncopy_do(dst, sizeof(dst), src, srclen)
164
165 static char *
166 chomp_string(char *name)
167 {
168 int namelen;
169
170 while (isspace(*name))
171 name++;
172
173 namelen = strlen(name) - 1;
174 while (namelen > 0 && isspace(name[namelen]))
175 name[namelen--] = 0;
176
177 return name;
178 }
179
180 static bool
181 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
182 {
183 va_list args;
184 size_t pos = bufpos ? *bufpos : 0;
185
186 va_start(args, fmt);
187 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
188 va_end(args);
189
190 if (bufpos)
191 *bufpos = pos;
192
193 return pos >= bufsize ? FALSE : TRUE;
194 }
195
196 #define string_format(buf, fmt, args...) \
197 string_nformat(buf, sizeof(buf), NULL, fmt, args)
198
199 #define string_format_from(buf, from, fmt, args...) \
200 string_nformat(buf, sizeof(buf), from, fmt, args)
201
202 static int
203 string_enum_compare(const char *str1, const char *str2, int len)
204 {
205 size_t i;
206
207 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
208
209 /* Diff-Header == DIFF_HEADER */
210 for (i = 0; i < len; i++) {
211 if (toupper(str1[i]) == toupper(str2[i]))
212 continue;
213
214 if (string_enum_sep(str1[i]) &&
215 string_enum_sep(str2[i]))
216 continue;
217
218 return str1[i] - str2[i];
219 }
220
221 return 0;
222 }
223
224 /* Shell quoting
225 *
226 * NOTE: The following is a slightly modified copy of the git project's shell
227 * quoting routines found in the quote.c file.
228 *
229 * Help to copy the thing properly quoted for the shell safety. any single
230 * quote is replaced with '\'', any exclamation point is replaced with '\!',
231 * and the whole thing is enclosed in a
232 *
233 * E.g.
234 * original sq_quote result
235 * name ==> name ==> 'name'
236 * a b ==> a b ==> 'a b'
237 * a'b ==> a'\''b ==> 'a'\''b'
238 * a!b ==> a'\!'b ==> 'a'\!'b'
239 */
240
241 static size_t
242 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
243 {
244 char c;
245
246 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
247
248 BUFPUT('\'');
249 while ((c = *src++)) {
250 if (c == '\'' || c == '!') {
251 BUFPUT('\'');
252 BUFPUT('\\');
253 BUFPUT(c);
254 BUFPUT('\'');
255 } else {
256 BUFPUT(c);
257 }
258 }
259 BUFPUT('\'');
260
261 return bufsize;
262 }
263
264
265 /*
266 * User requests
267 */
268
269 #define REQ_INFO \
270 /* XXX: Keep the view request first and in sync with views[]. */ \
271 REQ_GROUP("View switching") \
272 REQ_(VIEW_MAIN, "Show main view"), \
273 REQ_(VIEW_DIFF, "Show diff view"), \
274 REQ_(VIEW_LOG, "Show log view"), \
275 REQ_(VIEW_TREE, "Show tree view"), \
276 REQ_(VIEW_BLOB, "Show blob view"), \
277 REQ_(VIEW_HELP, "Show help page"), \
278 REQ_(VIEW_PAGER, "Show pager view"), \
279 \
280 REQ_GROUP("View manipulation") \
281 REQ_(ENTER, "Enter current line and scroll"), \
282 REQ_(NEXT, "Move to next"), \
283 REQ_(PREVIOUS, "Move to previous"), \
284 REQ_(VIEW_NEXT, "Move focus to next view"), \
285 REQ_(VIEW_CLOSE, "Close the current view"), \
286 REQ_(QUIT, "Close all views and quit"), \
287 \
288 REQ_GROUP("Cursor navigation") \
289 REQ_(MOVE_UP, "Move cursor one line up"), \
290 REQ_(MOVE_DOWN, "Move cursor one line down"), \
291 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
292 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
293 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
294 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
295 \
296 REQ_GROUP("Scrolling") \
297 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
298 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
299 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
300 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
301 \
302 REQ_GROUP("Searching") \
303 REQ_(SEARCH, "Search the view"), \
304 REQ_(SEARCH_BACK, "Search backwards in the view"), \
305 REQ_(FIND_NEXT, "Find next search match"), \
306 REQ_(FIND_PREV, "Find previous search match"), \
307 \
308 REQ_GROUP("Misc") \
309 REQ_(NONE, "Do nothing"), \
310 REQ_(PROMPT, "Bring up the prompt"), \
311 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
312 REQ_(SCREEN_RESIZE, "Resize the screen"), \
313 REQ_(SHOW_VERSION, "Show version information"), \
314 REQ_(STOP_LOADING, "Stop all loading views"), \
315 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
316 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization")
317
318
319 /* User action requests. */
320 enum request {
321 #define REQ_GROUP(help)
322 #define REQ_(req, help) REQ_##req
323
324 /* Offset all requests to avoid conflicts with ncurses getch values. */
325 REQ_OFFSET = KEY_MAX + 1,
326 REQ_INFO,
327 REQ_UNKNOWN,
328
329 #undef REQ_GROUP
330 #undef REQ_
331 };
332
333 struct request_info {
334 enum request request;
335 char *name;
336 int namelen;
337 char *help;
338 };
339
340 static struct request_info req_info[] = {
341 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
342 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
343 REQ_INFO
344 #undef REQ_GROUP
345 #undef REQ_
346 };
347
348 static enum request
349 get_request(const char *name)
350 {
351 int namelen = strlen(name);
352 int i;
353
354 for (i = 0; i < ARRAY_SIZE(req_info); i++)
355 if (req_info[i].namelen == namelen &&
356 !string_enum_compare(req_info[i].name, name, namelen))
357 return req_info[i].request;
358
359 return REQ_UNKNOWN;
360 }
361
362
363 /*
364 * Options
365 */
366
367 static const char usage[] =
368 VERSION " (" __DATE__ ")\n"
369 "\n"
370 "Usage: tig [options]\n"
371 " or: tig [options] [--] [git log options]\n"
372 " or: tig [options] log [git log options]\n"
373 " or: tig [options] diff [git diff options]\n"
374 " or: tig [options] show [git show options]\n"
375 " or: tig [options] < [git command output]\n"
376 "\n"
377 "Options:\n"
378 " -l Start up in log view\n"
379 " -d Start up in diff view\n"
380 " -n[I], --line-number[=I] Show line numbers with given interval\n"
381 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
382 " -- Mark end of tig options\n"
383 " -v, --version Show version and exit\n"
384 " -h, --help Show help message and exit\n";
385
386 /* Option and state variables. */
387 static bool opt_line_number = FALSE;
388 static bool opt_rev_graph = TRUE;
389 static int opt_num_interval = NUMBER_INTERVAL;
390 static int opt_tab_size = TABSIZE;
391 static enum request opt_request = REQ_VIEW_MAIN;
392 static char opt_cmd[SIZEOF_STR] = "";
393 static char opt_path[SIZEOF_STR] = "";
394 static FILE *opt_pipe = NULL;
395 static char opt_encoding[20] = "UTF-8";
396 static bool opt_utf8 = TRUE;
397 static char opt_codeset[20] = "UTF-8";
398 static iconv_t opt_iconv = ICONV_NONE;
399 static char opt_search[SIZEOF_STR] = "";
400
401 enum option_type {
402 OPT_NONE,
403 OPT_INT,
404 };
405
406 static bool
407 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
408 {
409 va_list args;
410 char *value = "";
411 int *number;
412
413 if (opt[0] != '-')
414 return FALSE;
415
416 if (opt[1] == '-') {
417 int namelen = strlen(name);
418
419 opt += 2;
420
421 if (strncmp(opt, name, namelen))
422 return FALSE;
423
424 if (opt[namelen] == '=')
425 value = opt + namelen + 1;
426
427 } else {
428 if (!short_name || opt[1] != short_name)
429 return FALSE;
430 value = opt + 2;
431 }
432
433 va_start(args, type);
434 if (type == OPT_INT) {
435 number = va_arg(args, int *);
436 if (isdigit(*value))
437 *number = atoi(value);
438 }
439 va_end(args);
440
441 return TRUE;
442 }
443
444 /* Returns the index of log or diff command or -1 to exit. */
445 static bool
446 parse_options(int argc, char *argv[])
447 {
448 int i;
449
450 for (i = 1; i < argc; i++) {
451 char *opt = argv[i];
452
453 if (!strcmp(opt, "-l")) {
454 opt_request = REQ_VIEW_LOG;
455 continue;
456 }
457
458 if (!strcmp(opt, "-d")) {
459 opt_request = REQ_VIEW_DIFF;
460 continue;
461 }
462
463 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
464 opt_line_number = TRUE;
465 continue;
466 }
467
468 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
469 opt_tab_size = MIN(opt_tab_size, TABSIZE);
470 continue;
471 }
472
473 if (check_option(opt, 'v', "version", OPT_NONE)) {
474 printf("tig version %s\n", VERSION);
475 return FALSE;
476 }
477
478 if (check_option(opt, 'h', "help", OPT_NONE)) {
479 printf(usage);
480 return FALSE;
481 }
482
483 if (!strcmp(opt, "--")) {
484 i++;
485 break;
486 }
487
488 if (!strcmp(opt, "log") ||
489 !strcmp(opt, "diff") ||
490 !strcmp(opt, "show")) {
491 opt_request = opt[0] == 'l'
492 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
493 break;
494 }
495
496 if (opt[0] && opt[0] != '-')
497 break;
498
499 die("unknown option '%s'\n\n%s", opt, usage);
500 }
501
502 if (!isatty(STDIN_FILENO)) {
503 opt_request = REQ_VIEW_PAGER;
504 opt_pipe = stdin;
505
506 } else if (i < argc) {
507 size_t buf_size;
508
509 if (opt_request == REQ_VIEW_MAIN)
510 /* XXX: This is vulnerable to the user overriding
511 * options required for the main view parser. */
512 string_copy(opt_cmd, "git log --stat --pretty=raw");
513 else
514 string_copy(opt_cmd, "git");
515 buf_size = strlen(opt_cmd);
516
517 while (buf_size < sizeof(opt_cmd) && i < argc) {
518 opt_cmd[buf_size++] = ' ';
519 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
520 }
521
522 if (buf_size >= sizeof(opt_cmd))
523 die("command too long");
524
525 opt_cmd[buf_size] = 0;
526
527 }
528
529 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
530 opt_utf8 = FALSE;
531
532 return TRUE;
533 }
534
535
536 /*
537 * Line-oriented content detection.
538 */
539
540 #define LINE_INFO \
541 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
542 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
543 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
544 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
545 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
546 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
547 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
548 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
549 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
550 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
551 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
552 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
553 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
554 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
555 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
556 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
557 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
558 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
559 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
560 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
561 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
562 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
563 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
564 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
565 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
566 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
567 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
568 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
569 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
570 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
571 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
572 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
573 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
574 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
575 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
576 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
577 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
578 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
579 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
580 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL)
581
582 enum line_type {
583 #define LINE(type, line, fg, bg, attr) \
584 LINE_##type
585 LINE_INFO
586 #undef LINE
587 };
588
589 struct line_info {
590 const char *name; /* Option name. */
591 int namelen; /* Size of option name. */
592 const char *line; /* The start of line to match. */
593 int linelen; /* Size of string to match. */
594 int fg, bg, attr; /* Color and text attributes for the lines. */
595 };
596
597 static struct line_info line_info[] = {
598 #define LINE(type, line, fg, bg, attr) \
599 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
600 LINE_INFO
601 #undef LINE
602 };
603
604 static enum line_type
605 get_line_type(char *line)
606 {
607 int linelen = strlen(line);
608 enum line_type type;
609
610 for (type = 0; type < ARRAY_SIZE(line_info); type++)
611 /* Case insensitive search matches Signed-off-by lines better. */
612 if (linelen >= line_info[type].linelen &&
613 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
614 return type;
615
616 return LINE_DEFAULT;
617 }
618
619 static inline int
620 get_line_attr(enum line_type type)
621 {
622 assert(type < ARRAY_SIZE(line_info));
623 return COLOR_PAIR(type) | line_info[type].attr;
624 }
625
626 static struct line_info *
627 get_line_info(char *name, int namelen)
628 {
629 enum line_type type;
630
631 for (type = 0; type < ARRAY_SIZE(line_info); type++)
632 if (namelen == line_info[type].namelen &&
633 !string_enum_compare(line_info[type].name, name, namelen))
634 return &line_info[type];
635
636 return NULL;
637 }
638
639 static void
640 init_colors(void)
641 {
642 int default_bg = COLOR_BLACK;
643 int default_fg = COLOR_WHITE;
644 enum line_type type;
645
646 start_color();
647
648 if (use_default_colors() != ERR) {
649 default_bg = -1;
650 default_fg = -1;
651 }
652
653 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
654 struct line_info *info = &line_info[type];
655 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
656 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
657
658 init_pair(type, fg, bg);
659 }
660 }
661
662 struct line {
663 enum line_type type;
664 void *data; /* User data */
665 };
666
667
668 /*
669 * Keys
670 */
671
672 struct keybinding {
673 int alias;
674 enum request request;
675 struct keybinding *next;
676 };
677
678 static struct keybinding default_keybindings[] = {
679 /* View switching */
680 { 'm', REQ_VIEW_MAIN },
681 { 'd', REQ_VIEW_DIFF },
682 { 'l', REQ_VIEW_LOG },
683 { 't', REQ_VIEW_TREE },
684 { 'f', REQ_VIEW_BLOB },
685 { 'p', REQ_VIEW_PAGER },
686 { 'h', REQ_VIEW_HELP },
687
688 /* View manipulation */
689 { 'q', REQ_VIEW_CLOSE },
690 { KEY_TAB, REQ_VIEW_NEXT },
691 { KEY_RETURN, REQ_ENTER },
692 { KEY_UP, REQ_PREVIOUS },
693 { KEY_DOWN, REQ_NEXT },
694
695 /* Cursor navigation */
696 { 'k', REQ_MOVE_UP },
697 { 'j', REQ_MOVE_DOWN },
698 { KEY_HOME, REQ_MOVE_FIRST_LINE },
699 { KEY_END, REQ_MOVE_LAST_LINE },
700 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
701 { ' ', REQ_MOVE_PAGE_DOWN },
702 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
703 { 'b', REQ_MOVE_PAGE_UP },
704 { '-', REQ_MOVE_PAGE_UP },
705
706 /* Scrolling */
707 { KEY_IC, REQ_SCROLL_LINE_UP },
708 { KEY_DC, REQ_SCROLL_LINE_DOWN },
709 { 'w', REQ_SCROLL_PAGE_UP },
710 { 's', REQ_SCROLL_PAGE_DOWN },
711
712 /* Searching */
713 { '/', REQ_SEARCH },
714 { '?', REQ_SEARCH_BACK },
715 { 'n', REQ_FIND_NEXT },
716 { 'N', REQ_FIND_PREV },
717
718 /* Misc */
719 { 'Q', REQ_QUIT },
720 { 'z', REQ_STOP_LOADING },
721 { 'v', REQ_SHOW_VERSION },
722 { 'r', REQ_SCREEN_REDRAW },
723 { '.', REQ_TOGGLE_LINENO },
724 { 'g', REQ_TOGGLE_REV_GRAPH },
725 { ':', REQ_PROMPT },
726
727 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
728 { ERR, REQ_NONE },
729
730 /* Using the ncurses SIGWINCH handler. */
731 { KEY_RESIZE, REQ_SCREEN_RESIZE },
732 };
733
734 #define KEYMAP_INFO \
735 KEYMAP_(GENERIC), \
736 KEYMAP_(MAIN), \
737 KEYMAP_(DIFF), \
738 KEYMAP_(LOG), \
739 KEYMAP_(TREE), \
740 KEYMAP_(BLOB), \
741 KEYMAP_(PAGER), \
742 KEYMAP_(HELP) \
743
744 enum keymap {
745 #define KEYMAP_(name) KEYMAP_##name
746 KEYMAP_INFO
747 #undef KEYMAP_
748 };
749
750 static struct int_map keymap_table[] = {
751 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
752 KEYMAP_INFO
753 #undef KEYMAP_
754 };
755
756 #define set_keymap(map, name) \
757 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
758
759 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
760
761 static void
762 add_keybinding(enum keymap keymap, enum request request, int key)
763 {
764 struct keybinding *keybinding;
765
766 keybinding = calloc(1, sizeof(*keybinding));
767 if (!keybinding)
768 die("Failed to allocate keybinding");
769
770 keybinding->alias = key;
771 keybinding->request = request;
772 keybinding->next = keybindings[keymap];
773 keybindings[keymap] = keybinding;
774 }
775
776 /* Looks for a key binding first in the given map, then in the generic map, and
777 * lastly in the default keybindings. */
778 static enum request
779 get_keybinding(enum keymap keymap, int key)
780 {
781 struct keybinding *kbd;
782 int i;
783
784 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
785 if (kbd->alias == key)
786 return kbd->request;
787
788 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
789 if (kbd->alias == key)
790 return kbd->request;
791
792 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
793 if (default_keybindings[i].alias == key)
794 return default_keybindings[i].request;
795
796 return (enum request) key;
797 }
798
799
800 struct key {
801 char *name;
802 int value;
803 };
804
805 static struct key key_table[] = {
806 { "Enter", KEY_RETURN },
807 { "Space", ' ' },
808 { "Backspace", KEY_BACKSPACE },
809 { "Tab", KEY_TAB },
810 { "Escape", KEY_ESC },
811 { "Left", KEY_LEFT },
812 { "Right", KEY_RIGHT },
813 { "Up", KEY_UP },
814 { "Down", KEY_DOWN },
815 { "Insert", KEY_IC },
816 { "Delete", KEY_DC },
817 { "Hash", '#' },
818 { "Home", KEY_HOME },
819 { "End", KEY_END },
820 { "PageUp", KEY_PPAGE },
821 { "PageDown", KEY_NPAGE },
822 { "F1", KEY_F(1) },
823 { "F2", KEY_F(2) },
824 { "F3", KEY_F(3) },
825 { "F4", KEY_F(4) },
826 { "F5", KEY_F(5) },
827 { "F6", KEY_F(6) },
828 { "F7", KEY_F(7) },
829 { "F8", KEY_F(8) },
830 { "F9", KEY_F(9) },
831 { "F10", KEY_F(10) },
832 { "F11", KEY_F(11) },
833 { "F12", KEY_F(12) },
834 };
835
836 static int
837 get_key_value(const char *name)
838 {
839 int i;
840
841 for (i = 0; i < ARRAY_SIZE(key_table); i++)
842 if (!strcasecmp(key_table[i].name, name))
843 return key_table[i].value;
844
845 if (strlen(name) == 1 && isprint(*name))
846 return (int) *name;
847
848 return ERR;
849 }
850
851 static char *
852 get_key(enum request request)
853 {
854 static char buf[BUFSIZ];
855 static char key_char[] = "'X'";
856 size_t pos = 0;
857 char *sep = " ";
858 int i;
859
860 buf[pos] = 0;
861
862 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
863 struct keybinding *keybinding = &default_keybindings[i];
864 char *seq = NULL;
865 int key;
866
867 if (keybinding->request != request)
868 continue;
869
870 for (key = 0; key < ARRAY_SIZE(key_table); key++)
871 if (key_table[key].value == keybinding->alias)
872 seq = key_table[key].name;
873
874 if (seq == NULL &&
875 keybinding->alias < 127 &&
876 isprint(keybinding->alias)) {
877 key_char[1] = (char) keybinding->alias;
878 seq = key_char;
879 }
880
881 if (!seq)
882 seq = "'?'";
883
884 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
885 return "Too many keybindings!";
886 sep = ", ";
887 }
888
889 return buf;
890 }
891
892
893 /*
894 * User config file handling.
895 */
896
897 static struct int_map color_map[] = {
898 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
899 COLOR_MAP(DEFAULT),
900 COLOR_MAP(BLACK),
901 COLOR_MAP(BLUE),
902 COLOR_MAP(CYAN),
903 COLOR_MAP(GREEN),
904 COLOR_MAP(MAGENTA),
905 COLOR_MAP(RED),
906 COLOR_MAP(WHITE),
907 COLOR_MAP(YELLOW),
908 };
909
910 #define set_color(color, name) \
911 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
912
913 static struct int_map attr_map[] = {
914 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
915 ATTR_MAP(NORMAL),
916 ATTR_MAP(BLINK),
917 ATTR_MAP(BOLD),
918 ATTR_MAP(DIM),
919 ATTR_MAP(REVERSE),
920 ATTR_MAP(STANDOUT),
921 ATTR_MAP(UNDERLINE),
922 };
923
924 #define set_attribute(attr, name) \
925 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
926
927 static int config_lineno;
928 static bool config_errors;
929 static char *config_msg;
930
931 /* Wants: object fgcolor bgcolor [attr] */
932 static int
933 option_color_command(int argc, char *argv[])
934 {
935 struct line_info *info;
936
937 if (argc != 3 && argc != 4) {
938 config_msg = "Wrong number of arguments given to color command";
939 return ERR;
940 }
941
942 info = get_line_info(argv[0], strlen(argv[0]));
943 if (!info) {
944 config_msg = "Unknown color name";
945 return ERR;
946 }
947
948 if (set_color(&info->fg, argv[1]) == ERR ||
949 set_color(&info->bg, argv[2]) == ERR) {
950 config_msg = "Unknown color";
951 return ERR;
952 }
953
954 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
955 config_msg = "Unknown attribute";
956 return ERR;
957 }
958
959 return OK;
960 }
961
962 /* Wants: name = value */
963 static int
964 option_set_command(int argc, char *argv[])
965 {
966 if (argc != 3) {
967 config_msg = "Wrong number of arguments given to set command";
968 return ERR;
969 }
970
971 if (strcmp(argv[1], "=")) {
972 config_msg = "No value assigned";
973 return ERR;
974 }
975
976 if (!strcmp(argv[0], "show-rev-graph")) {
977 opt_rev_graph = (!strcmp(argv[2], "1") ||
978 !strcmp(argv[2], "true") ||
979 !strcmp(argv[2], "yes"));
980 return OK;
981 }
982
983 if (!strcmp(argv[0], "line-number-interval")) {
984 opt_num_interval = atoi(argv[2]);
985 return OK;
986 }
987
988 if (!strcmp(argv[0], "tab-size")) {
989 opt_tab_size = atoi(argv[2]);
990 return OK;
991 }
992
993 if (!strcmp(argv[0], "commit-encoding")) {
994 char *arg = argv[2];
995 int delimiter = *arg;
996 int i;
997
998 switch (delimiter) {
999 case '"':
1000 case '\'':
1001 for (arg++, i = 0; arg[i]; i++)
1002 if (arg[i] == delimiter) {
1003 arg[i] = 0;
1004 break;
1005 }
1006 default:
1007 string_copy(opt_encoding, arg);
1008 return OK;
1009 }
1010 }
1011
1012 config_msg = "Unknown variable name";
1013 return ERR;
1014 }
1015
1016 /* Wants: mode request key */
1017 static int
1018 option_bind_command(int argc, char *argv[])
1019 {
1020 enum request request;
1021 int keymap;
1022 int key;
1023
1024 if (argc != 3) {
1025 config_msg = "Wrong number of arguments given to bind command";
1026 return ERR;
1027 }
1028
1029 if (set_keymap(&keymap, argv[0]) == ERR) {
1030 config_msg = "Unknown key map";
1031 return ERR;
1032 }
1033
1034 key = get_key_value(argv[1]);
1035 if (key == ERR) {
1036 config_msg = "Unknown key";
1037 return ERR;
1038 }
1039
1040 request = get_request(argv[2]);
1041 if (request == REQ_UNKNOWN) {
1042 config_msg = "Unknown request name";
1043 return ERR;
1044 }
1045
1046 add_keybinding(keymap, request, key);
1047
1048 return OK;
1049 }
1050
1051 static int
1052 set_option(char *opt, char *value)
1053 {
1054 char *argv[16];
1055 int valuelen;
1056 int argc = 0;
1057
1058 /* Tokenize */
1059 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1060 argv[argc++] = value;
1061
1062 value += valuelen;
1063 if (!*value)
1064 break;
1065
1066 *value++ = 0;
1067 while (isspace(*value))
1068 value++;
1069 }
1070
1071 if (!strcmp(opt, "color"))
1072 return option_color_command(argc, argv);
1073
1074 if (!strcmp(opt, "set"))
1075 return option_set_command(argc, argv);
1076
1077 if (!strcmp(opt, "bind"))
1078 return option_bind_command(argc, argv);
1079
1080 config_msg = "Unknown option command";
1081 return ERR;
1082 }
1083
1084 static int
1085 read_option(char *opt, int optlen, char *value, int valuelen)
1086 {
1087 int status = OK;
1088
1089 config_lineno++;
1090 config_msg = "Internal error";
1091
1092 /* Check for comment markers, since read_properties() will
1093 * only ensure opt and value are split at first " \t". */
1094 optlen = strcspn(opt, "#");
1095 if (optlen == 0)
1096 return OK;
1097
1098 if (opt[optlen] != 0) {
1099 config_msg = "No option value";
1100 status = ERR;
1101
1102 } else {
1103 /* Look for comment endings in the value. */
1104 int len = strcspn(value, "#");
1105
1106 if (len < valuelen) {
1107 valuelen = len;
1108 value[valuelen] = 0;
1109 }
1110
1111 status = set_option(opt, value);
1112 }
1113
1114 if (status == ERR) {
1115 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1116 config_lineno, optlen, opt, config_msg);
1117 config_errors = TRUE;
1118 }
1119
1120 /* Always keep going if errors are encountered. */
1121 return OK;
1122 }
1123
1124 static int
1125 load_options(void)
1126 {
1127 char *home = getenv("HOME");
1128 char buf[SIZEOF_STR];
1129 FILE *file;
1130
1131 config_lineno = 0;
1132 config_errors = FALSE;
1133
1134 if (!home || !string_format(buf, "%s/.tigrc", home))
1135 return ERR;
1136
1137 /* It's ok that the file doesn't exist. */
1138 file = fopen(buf, "r");
1139 if (!file)
1140 return OK;
1141
1142 if (read_properties(file, " \t", read_option) == ERR ||
1143 config_errors == TRUE)
1144 fprintf(stderr, "Errors while loading %s.\n", buf);
1145
1146 return OK;
1147 }
1148
1149
1150 /*
1151 * The viewer
1152 */
1153
1154 struct view;
1155 struct view_ops;
1156
1157 /* The display array of active views and the index of the current view. */
1158 static struct view *display[2];
1159 static unsigned int current_view;
1160
1161 #define foreach_displayed_view(view, i) \
1162 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1163
1164 #define displayed_views() (display[1] != NULL ? 2 : 1)
1165
1166 /* Current head and commit ID */
1167 static char ref_blob[SIZEOF_REF] = "";
1168 static char ref_commit[SIZEOF_REF] = "HEAD";
1169 static char ref_head[SIZEOF_REF] = "HEAD";
1170
1171 struct view {
1172 const char *name; /* View name */
1173 const char *cmd_fmt; /* Default command line format */
1174 const char *cmd_env; /* Command line set via environment */
1175 const char *id; /* Points to either of ref_{head,commit,blob} */
1176
1177 struct view_ops *ops; /* View operations */
1178
1179 enum keymap keymap; /* What keymap does this view have */
1180
1181 char cmd[SIZEOF_STR]; /* Command buffer */
1182 char ref[SIZEOF_REF]; /* Hovered commit reference */
1183 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1184
1185 int height, width; /* The width and height of the main window */
1186 WINDOW *win; /* The main window */
1187 WINDOW *title; /* The title window living below the main window */
1188
1189 /* Navigation */
1190 unsigned long offset; /* Offset of the window top */
1191 unsigned long lineno; /* Current line number */
1192
1193 /* Searching */
1194 char grep[SIZEOF_STR]; /* Search string */
1195 regex_t *regex; /* Pre-compiled regex */
1196
1197 /* If non-NULL, points to the view that opened this view. If this view
1198 * is closed tig will switch back to the parent view. */
1199 struct view *parent;
1200
1201 /* Buffering */
1202 unsigned long lines; /* Total number of lines */
1203 struct line *line; /* Line index */
1204 unsigned long line_size;/* Total number of allocated lines */
1205 unsigned int digits; /* Number of digits in the lines member. */
1206
1207 /* Loading */
1208 FILE *pipe;
1209 time_t start_time;
1210 };
1211
1212 struct view_ops {
1213 /* What type of content being displayed. Used in the title bar. */
1214 const char *type;
1215 /* Draw one line; @lineno must be < view->height. */
1216 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1217 /* Read one line; updates view->line. */
1218 bool (*read)(struct view *view, char *data);
1219 /* Depending on view, change display based on current line. */
1220 bool (*enter)(struct view *view, struct line *line);
1221 /* Search for regex in a line. */
1222 bool (*grep)(struct view *view, struct line *line);
1223 /* Select line */
1224 void (*select)(struct view *view, struct line *line);
1225 };
1226
1227 static struct view_ops pager_ops;
1228 static struct view_ops main_ops;
1229 static struct view_ops tree_ops;
1230 static struct view_ops blob_ops;
1231
1232 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1233 { name, cmd, #env, ref, ops, map}
1234
1235 #define VIEW_(id, name, ops, ref) \
1236 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1237
1238
1239 static struct view views[] = {
1240 VIEW_(MAIN, "main", &main_ops, ref_head),
1241 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1242 VIEW_(LOG, "log", &pager_ops, ref_head),
1243 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1244 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1245 VIEW_(HELP, "help", &pager_ops, "static"),
1246 VIEW_(PAGER, "pager", &pager_ops, "static"),
1247 };
1248
1249 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1250
1251 #define foreach_view(view, i) \
1252 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1253
1254 #define view_is_displayed(view) \
1255 (view == display[0] || view == display[1])
1256
1257 static bool
1258 draw_view_line(struct view *view, unsigned int lineno)
1259 {
1260 struct line *line;
1261
1262 assert(view_is_displayed(view));
1263
1264 if (view->offset + lineno >= view->lines)
1265 return FALSE;
1266
1267 line = &view->line[view->offset + lineno];
1268
1269 if (view->offset + lineno == view->lineno)
1270 view->ops->select(view, line);
1271
1272 return view->ops->draw(view, line, lineno);
1273 }
1274
1275 static void
1276 redraw_view_from(struct view *view, int lineno)
1277 {
1278 assert(0 <= lineno && lineno < view->height);
1279
1280 for (; lineno < view->height; lineno++) {
1281 if (!draw_view_line(view, lineno))
1282 break;
1283 }
1284
1285 redrawwin(view->win);
1286 wrefresh(view->win);
1287 }
1288
1289 static void
1290 redraw_view(struct view *view)
1291 {
1292 wclear(view->win);
1293 redraw_view_from(view, 0);
1294 }
1295
1296
1297 static void
1298 update_view_title(struct view *view)
1299 {
1300 assert(view_is_displayed(view));
1301
1302 if (view == display[current_view])
1303 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1304 else
1305 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1306
1307 werase(view->title);
1308 wmove(view->title, 0, 0);
1309
1310 if (*view->ref)
1311 wprintw(view->title, "[%s] %s", view->name, view->ref);
1312 else
1313 wprintw(view->title, "[%s]", view->name);
1314
1315 if (view->lines || view->pipe) {
1316 unsigned int view_lines = view->offset + view->height;
1317 unsigned int lines = view->lines
1318 ? MIN(view_lines, view->lines) * 100 / view->lines
1319 : 0;
1320
1321 wprintw(view->title, " - %s %d of %d (%d%%)",
1322 view->ops->type,
1323 view->lineno + 1,
1324 view->lines,
1325 lines);
1326 }
1327
1328 if (view->pipe) {
1329 time_t secs = time(NULL) - view->start_time;
1330
1331 /* Three git seconds are a long time ... */
1332 if (secs > 2)
1333 wprintw(view->title, " %lds", secs);
1334 }
1335
1336 wmove(view->title, 0, view->width - 1);
1337 wrefresh(view->title);
1338 }
1339
1340 static void
1341 resize_display(void)
1342 {
1343 int offset, i;
1344 struct view *base = display[0];
1345 struct view *view = display[1] ? display[1] : display[0];
1346
1347 /* Setup window dimensions */
1348
1349 getmaxyx(stdscr, base->height, base->width);
1350
1351 /* Make room for the status window. */
1352 base->height -= 1;
1353
1354 if (view != base) {
1355 /* Horizontal split. */
1356 view->width = base->width;
1357 view->height = SCALE_SPLIT_VIEW(base->height);
1358 base->height -= view->height;
1359
1360 /* Make room for the title bar. */
1361 view->height -= 1;
1362 }
1363
1364 /* Make room for the title bar. */
1365 base->height -= 1;
1366
1367 offset = 0;
1368
1369 foreach_displayed_view (view, i) {
1370 if (!view->win) {
1371 view->win = newwin(view->height, 0, offset, 0);
1372 if (!view->win)
1373 die("Failed to create %s view", view->name);
1374
1375 scrollok(view->win, TRUE);
1376
1377 view->title = newwin(1, 0, offset + view->height, 0);
1378 if (!view->title)
1379 die("Failed to create title window");
1380
1381 } else {
1382 wresize(view->win, view->height, view->width);
1383 mvwin(view->win, offset, 0);
1384 mvwin(view->title, offset + view->height, 0);
1385 }
1386
1387 offset += view->height + 1;
1388 }
1389 }
1390
1391 static void
1392 redraw_display(void)
1393 {
1394 struct view *view;
1395 int i;
1396
1397 foreach_displayed_view (view, i) {
1398 redraw_view(view);
1399 update_view_title(view);
1400 }
1401 }
1402
1403 static void
1404 update_display_cursor(void)
1405 {
1406 struct view *view = display[current_view];
1407
1408 /* Move the cursor to the right-most column of the cursor line.
1409 *
1410 * XXX: This could turn out to be a bit expensive, but it ensures that
1411 * the cursor does not jump around. */
1412 if (view->lines) {
1413 wmove(view->win, view->lineno - view->offset, view->width - 1);
1414 wrefresh(view->win);
1415 }
1416 }
1417
1418 /*
1419 * Navigation
1420 */
1421
1422 /* Scrolling backend */
1423 static void
1424 do_scroll_view(struct view *view, int lines, bool redraw)
1425 {
1426 assert(view_is_displayed(view));
1427
1428 /* The rendering expects the new offset. */
1429 view->offset += lines;
1430
1431 assert(0 <= view->offset && view->offset < view->lines);
1432 assert(lines);
1433
1434 /* Redraw the whole screen if scrolling is pointless. */
1435 if (view->height < ABS(lines)) {
1436 redraw_view(view);
1437
1438 } else {
1439 int line = lines > 0 ? view->height - lines : 0;
1440 int end = line + ABS(lines);
1441
1442 wscrl(view->win, lines);
1443
1444 for (; line < end; line++) {
1445 if (!draw_view_line(view, line))
1446 break;
1447 }
1448 }
1449
1450 /* Move current line into the view. */
1451 if (view->lineno < view->offset) {
1452 view->lineno = view->offset;
1453 draw_view_line(view, 0);
1454
1455 } else if (view->lineno >= view->offset + view->height) {
1456 if (view->lineno == view->offset + view->height) {
1457 /* Clear the hidden line so it doesn't show if the view
1458 * is scrolled up. */
1459 wmove(view->win, view->height, 0);
1460 wclrtoeol(view->win);
1461 }
1462 view->lineno = view->offset + view->height - 1;
1463 draw_view_line(view, view->lineno - view->offset);
1464 }
1465
1466 assert(view->offset <= view->lineno && view->lineno < view->lines);
1467
1468 if (!redraw)
1469 return;
1470
1471 redrawwin(view->win);
1472 wrefresh(view->win);
1473 report("");
1474 }
1475
1476 /* Scroll frontend */
1477 static void
1478 scroll_view(struct view *view, enum request request)
1479 {
1480 int lines = 1;
1481
1482 switch (request) {
1483 case REQ_SCROLL_PAGE_DOWN:
1484 lines = view->height;
1485 case REQ_SCROLL_LINE_DOWN:
1486 if (view->offset + lines > view->lines)
1487 lines = view->lines - view->offset;
1488
1489 if (lines == 0 || view->offset + view->height >= view->lines) {
1490 report("Cannot scroll beyond the last line");
1491 return;
1492 }
1493 break;
1494
1495 case REQ_SCROLL_PAGE_UP:
1496 lines = view->height;
1497 case REQ_SCROLL_LINE_UP:
1498 if (lines > view->offset)
1499 lines = view->offset;
1500
1501 if (lines == 0) {
1502 report("Cannot scroll beyond the first line");
1503 return;
1504 }
1505
1506 lines = -lines;
1507 break;
1508
1509 default:
1510 die("request %d not handled in switch", request);
1511 }
1512
1513 do_scroll_view(view, lines, TRUE);
1514 }
1515
1516 /* Cursor moving */
1517 static void
1518 move_view(struct view *view, enum request request, bool redraw)
1519 {
1520 int steps;
1521
1522 switch (request) {
1523 case REQ_MOVE_FIRST_LINE:
1524 steps = -view->lineno;
1525 break;
1526
1527 case REQ_MOVE_LAST_LINE:
1528 steps = view->lines - view->lineno - 1;
1529 break;
1530
1531 case REQ_MOVE_PAGE_UP:
1532 steps = view->height > view->lineno
1533 ? -view->lineno : -view->height;
1534 break;
1535
1536 case REQ_MOVE_PAGE_DOWN:
1537 steps = view->lineno + view->height >= view->lines
1538 ? view->lines - view->lineno - 1 : view->height;
1539 break;
1540
1541 case REQ_MOVE_UP:
1542 steps = -1;
1543 break;
1544
1545 case REQ_MOVE_DOWN:
1546 steps = 1;
1547 break;
1548
1549 default:
1550 die("request %d not handled in switch", request);
1551 }
1552
1553 if (steps <= 0 && view->lineno == 0) {
1554 report("Cannot move beyond the first line");
1555 return;
1556
1557 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1558 report("Cannot move beyond the last line");
1559 return;
1560 }
1561
1562 /* Move the current line */
1563 view->lineno += steps;
1564 assert(0 <= view->lineno && view->lineno < view->lines);
1565
1566 /* Repaint the old "current" line if we be scrolling */
1567 if (ABS(steps) < view->height) {
1568 int prev_lineno = view->lineno - steps - view->offset;
1569
1570 wmove(view->win, prev_lineno, 0);
1571 wclrtoeol(view->win);
1572 draw_view_line(view, prev_lineno);
1573 }
1574
1575 /* Check whether the view needs to be scrolled */
1576 if (view->lineno < view->offset ||
1577 view->lineno >= view->offset + view->height) {
1578 if (steps < 0 && -steps > view->offset) {
1579 steps = -view->offset;
1580
1581 } else if (steps > 0) {
1582 if (view->lineno == view->lines - 1 &&
1583 view->lines > view->height) {
1584 steps = view->lines - view->offset - 1;
1585 if (steps >= view->height)
1586 steps -= view->height - 1;
1587 }
1588 }
1589
1590 do_scroll_view(view, steps, redraw);
1591 return;
1592 }
1593
1594 /* Draw the current line */
1595 draw_view_line(view, view->lineno - view->offset);
1596
1597 if (!redraw)
1598 return;
1599
1600 redrawwin(view->win);
1601 wrefresh(view->win);
1602 report("");
1603 }
1604
1605
1606 /*
1607 * Searching
1608 */
1609
1610 static void search_view(struct view *view, enum request request, const char *search);
1611
1612 static bool
1613 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1614 {
1615 assert(view_is_displayed(view));
1616
1617 if (!view->ops->grep(view, line))
1618 return FALSE;
1619
1620 if (lineno - view->offset >= view->height) {
1621 view->offset = lineno;
1622 view->lineno = lineno;
1623 redraw_view(view);
1624
1625 } else {
1626 unsigned long old_lineno = view->lineno - view->offset;
1627
1628 view->lineno = lineno;
1629
1630 wmove(view->win, old_lineno, 0);
1631 wclrtoeol(view->win);
1632 draw_view_line(view, old_lineno);
1633
1634 draw_view_line(view, view->lineno - view->offset);
1635 redrawwin(view->win);
1636 wrefresh(view->win);
1637 }
1638
1639 report("Line %ld matches '%s'", lineno + 1, view->grep);
1640 return TRUE;
1641 }
1642
1643 static void
1644 find_next(struct view *view, enum request request)
1645 {
1646 unsigned long lineno = view->lineno;
1647 int direction;
1648
1649 if (!*view->grep) {
1650 if (!*opt_search)
1651 report("No previous search");
1652 else
1653 search_view(view, request, opt_search);
1654 return;
1655 }
1656
1657 switch (request) {
1658 case REQ_SEARCH:
1659 case REQ_FIND_NEXT:
1660 direction = 1;
1661 break;
1662
1663 case REQ_SEARCH_BACK:
1664 case REQ_FIND_PREV:
1665 direction = -1;
1666 break;
1667
1668 default:
1669 return;
1670 }
1671
1672 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1673 lineno += direction;
1674
1675 /* Note, lineno is unsigned long so will wrap around in which case it
1676 * will become bigger than view->lines. */
1677 for (; lineno < view->lines; lineno += direction) {
1678 struct line *line = &view->line[lineno];
1679
1680 if (find_next_line(view, lineno, line))
1681 return;
1682 }
1683
1684 report("No match found for '%s'", view->grep);
1685 }
1686
1687 static void
1688 search_view(struct view *view, enum request request, const char *search)
1689 {
1690 int regex_err;
1691
1692 if (view->regex) {
1693 regfree(view->regex);
1694 *view->grep = 0;
1695 } else {
1696 view->regex = calloc(1, sizeof(*view->regex));
1697 if (!view->regex)
1698 return;
1699 }
1700
1701 regex_err = regcomp(view->regex, search, REG_EXTENDED);
1702 if (regex_err != 0) {
1703 char buf[SIZEOF_STR] = "unknown error";
1704
1705 regerror(regex_err, view->regex, buf, sizeof(buf));
1706 report("Search failed: %s", buf);
1707 return;
1708 }
1709
1710 string_copy(view->grep, search);
1711
1712 find_next(view, request);
1713 }
1714
1715 /*
1716 * Incremental updating
1717 */
1718
1719 static void
1720 end_update(struct view *view)
1721 {
1722 if (!view->pipe)
1723 return;
1724 set_nonblocking_input(FALSE);
1725 if (view->pipe == stdin)
1726 fclose(view->pipe);
1727 else
1728 pclose(view->pipe);
1729 view->pipe = NULL;
1730 }
1731
1732 static bool
1733 begin_update(struct view *view)
1734 {
1735 const char *id = view->id;
1736
1737 if (view->pipe)
1738 end_update(view);
1739
1740 if (opt_cmd[0]) {
1741 string_copy(view->cmd, opt_cmd);
1742 opt_cmd[0] = 0;
1743 /* When running random commands, the view ref could have become
1744 * invalid so clear it. */
1745 view->ref[0] = 0;
1746
1747 } else if (view == VIEW(REQ_VIEW_TREE)) {
1748 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1749
1750 if (strcmp(view->vid, view->id))
1751 opt_path[0] = 0;
1752
1753 if (!string_format(view->cmd, format, id, opt_path))
1754 return FALSE;
1755
1756 } else {
1757 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1758
1759 if (!string_format(view->cmd, format, id, id, id, id, id))
1760 return FALSE;
1761 }
1762
1763 /* Special case for the pager view. */
1764 if (opt_pipe) {
1765 view->pipe = opt_pipe;
1766 opt_pipe = NULL;
1767 } else {
1768 view->pipe = popen(view->cmd, "r");
1769 }
1770
1771 if (!view->pipe)
1772 return FALSE;
1773
1774 set_nonblocking_input(TRUE);
1775
1776 view->offset = 0;
1777 view->lines = 0;
1778 view->lineno = 0;
1779 string_copy(view->vid, id);
1780
1781 if (view->line) {
1782 int i;
1783
1784 for (i = 0; i < view->lines; i++)
1785 if (view->line[i].data)
1786 free(view->line[i].data);
1787
1788 free(view->line);
1789 view->line = NULL;
1790 }
1791
1792 view->start_time = time(NULL);
1793
1794 return TRUE;
1795 }
1796
1797 static struct line *
1798 realloc_lines(struct view *view, size_t line_size)
1799 {
1800 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1801
1802 if (!tmp)
1803 return NULL;
1804
1805 view->line = tmp;
1806 view->line_size = line_size;
1807 return view->line;
1808 }
1809
1810 static bool
1811 update_view(struct view *view)
1812 {
1813 char in_buffer[BUFSIZ];
1814 char out_buffer[BUFSIZ * 2];
1815 char *line;
1816 /* The number of lines to read. If too low it will cause too much
1817 * redrawing (and possible flickering), if too high responsiveness
1818 * will suffer. */
1819 unsigned long lines = view->height;
1820 int redraw_from = -1;
1821
1822 if (!view->pipe)
1823 return TRUE;
1824
1825 /* Only redraw if lines are visible. */
1826 if (view->offset + view->height >= view->lines)
1827 redraw_from = view->lines - view->offset;
1828
1829 /* FIXME: This is probably not perfect for backgrounded views. */
1830 if (!realloc_lines(view, view->lines + lines))
1831 goto alloc_error;
1832
1833 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1834 size_t linelen = strlen(line);
1835
1836 if (linelen)
1837 line[linelen - 1] = 0;
1838
1839 if (opt_iconv != ICONV_NONE) {
1840 char *inbuf = line;
1841 size_t inlen = linelen;
1842
1843 char *outbuf = out_buffer;
1844 size_t outlen = sizeof(out_buffer);
1845
1846 size_t ret;
1847
1848 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1849 if (ret != (size_t) -1) {
1850 line = out_buffer;
1851 linelen = strlen(out_buffer);
1852 }
1853 }
1854
1855 if (!view->ops->read(view, line))
1856 goto alloc_error;
1857
1858 if (lines-- == 1)
1859 break;
1860 }
1861
1862 {
1863 int digits;
1864
1865 lines = view->lines;
1866 for (digits = 0; lines; digits++)
1867 lines /= 10;
1868
1869 /* Keep the displayed view in sync with line number scaling. */
1870 if (digits != view->digits) {
1871 view->digits = digits;
1872 redraw_from = 0;
1873 }
1874 }
1875
1876 if (!view_is_displayed(view))
1877 goto check_pipe;
1878
1879 if (view == VIEW(REQ_VIEW_TREE)) {
1880 /* Clear the view and redraw everything since the tree sorting
1881 * might have rearranged things. */
1882 redraw_view(view);
1883
1884 } else if (redraw_from >= 0) {
1885 /* If this is an incremental update, redraw the previous line
1886 * since for commits some members could have changed when
1887 * loading the main view. */
1888 if (redraw_from > 0)
1889 redraw_from--;
1890
1891 /* Incrementally draw avoids flickering. */
1892 redraw_view_from(view, redraw_from);
1893 }
1894
1895 /* Update the title _after_ the redraw so that if the redraw picks up a
1896 * commit reference in view->ref it'll be available here. */
1897 update_view_title(view);
1898
1899 check_pipe:
1900 if (ferror(view->pipe)) {
1901 report("Failed to read: %s", strerror(errno));
1902 goto end;
1903
1904 } else if (feof(view->pipe)) {
1905 report("");
1906 goto end;
1907 }
1908
1909 return TRUE;
1910
1911 alloc_error:
1912 report("Allocation failure");
1913
1914 end:
1915 end_update(view);
1916 return FALSE;
1917 }
1918
1919
1920 /*
1921 * View opening
1922 */
1923
1924 static void open_help_view(struct view *view)
1925 {
1926 char buf[BUFSIZ];
1927 int lines = ARRAY_SIZE(req_info) + 2;
1928 int i;
1929
1930 if (view->lines > 0)
1931 return;
1932
1933 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1934 if (!req_info[i].request)
1935 lines++;
1936
1937 view->line = calloc(lines, sizeof(*view->line));
1938 if (!view->line) {
1939 report("Allocation failure");
1940 return;
1941 }
1942
1943 view->ops->read(view, "Quick reference for tig keybindings:");
1944
1945 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1946 char *key;
1947
1948 if (!req_info[i].request) {
1949 view->ops->read(view, "");
1950 view->ops->read(view, req_info[i].help);
1951 continue;
1952 }
1953
1954 key = get_key(req_info[i].request);
1955 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1956 continue;
1957
1958 view->ops->read(view, buf);
1959 }
1960 }
1961
1962 enum open_flags {
1963 OPEN_DEFAULT = 0, /* Use default view switching. */
1964 OPEN_SPLIT = 1, /* Split current view. */
1965 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
1966 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
1967 };
1968
1969 static void
1970 open_view(struct view *prev, enum request request, enum open_flags flags)
1971 {
1972 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1973 bool split = !!(flags & OPEN_SPLIT);
1974 bool reload = !!(flags & OPEN_RELOAD);
1975 struct view *view = VIEW(request);
1976 int nviews = displayed_views();
1977 struct view *base_view = display[0];
1978
1979 if (view == prev && nviews == 1 && !reload) {
1980 report("Already in %s view", view->name);
1981 return;
1982 }
1983
1984 if (view == VIEW(REQ_VIEW_HELP)) {
1985 open_help_view(view);
1986
1987 } else if ((reload || strcmp(view->vid, view->id)) &&
1988 !begin_update(view)) {
1989 report("Failed to load %s view", view->name);
1990 return;
1991 }
1992
1993 if (split) {
1994 display[1] = view;
1995 if (!backgrounded)
1996 current_view = 1;
1997 } else {
1998 /* Maximize the current view. */
1999 memset(display, 0, sizeof(display));
2000 current_view = 0;
2001 display[current_view] = view;
2002 }
2003
2004 /* Resize the view when switching between split- and full-screen,
2005 * or when switching between two different full-screen views. */
2006 if (nviews != displayed_views() ||
2007 (nviews == 1 && base_view != display[0]))
2008 resize_display();
2009
2010 if (split && prev->lineno - prev->offset >= prev->height) {
2011 /* Take the title line into account. */
2012 int lines = prev->lineno - prev->offset - prev->height + 1;
2013
2014 /* Scroll the view that was split if the current line is
2015 * outside the new limited view. */
2016 do_scroll_view(prev, lines, TRUE);
2017 }
2018
2019 if (prev && view != prev) {
2020 if (split && !backgrounded) {
2021 /* "Blur" the previous view. */
2022 update_view_title(prev);
2023 }
2024
2025 view->parent = prev;
2026 }
2027
2028 if (view->pipe && view->lines == 0) {
2029 /* Clear the old view and let the incremental updating refill
2030 * the screen. */
2031 wclear(view->win);
2032 report("");
2033 } else {
2034 redraw_view(view);
2035 report("");
2036 }
2037
2038 /* If the view is backgrounded the above calls to report()
2039 * won't redraw the view title. */
2040 if (backgrounded)
2041 update_view_title(view);
2042 }
2043
2044
2045 /*
2046 * User request switch noodle
2047 */
2048
2049 static int
2050 view_driver(struct view *view, enum request request)
2051 {
2052 int i;
2053
2054 switch (request) {
2055 case REQ_MOVE_UP:
2056 case REQ_MOVE_DOWN:
2057 case REQ_MOVE_PAGE_UP:
2058 case REQ_MOVE_PAGE_DOWN:
2059 case REQ_MOVE_FIRST_LINE:
2060 case REQ_MOVE_LAST_LINE:
2061 move_view(view, request, TRUE);
2062 break;
2063
2064 case REQ_SCROLL_LINE_DOWN:
2065 case REQ_SCROLL_LINE_UP:
2066 case REQ_SCROLL_PAGE_DOWN:
2067 case REQ_SCROLL_PAGE_UP:
2068 scroll_view(view, request);
2069 break;
2070
2071 case REQ_VIEW_BLOB:
2072 if (!ref_blob[0]) {
2073 report("No file chosen, press 't' to open tree view");
2074 break;
2075 }
2076 /* Fall-through */
2077 case REQ_VIEW_MAIN:
2078 case REQ_VIEW_DIFF:
2079 case REQ_VIEW_LOG:
2080 case REQ_VIEW_TREE:
2081 case REQ_VIEW_HELP:
2082 case REQ_VIEW_PAGER:
2083 open_view(view, request, OPEN_DEFAULT);
2084 break;
2085
2086 case REQ_NEXT:
2087 case REQ_PREVIOUS:
2088 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2089
2090 if ((view == VIEW(REQ_VIEW_DIFF) &&
2091 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2092 (view == VIEW(REQ_VIEW_BLOB) &&
2093 view->parent == VIEW(REQ_VIEW_TREE))) {
2094 bool redraw = display[1] == view;
2095
2096 view = view->parent;
2097 move_view(view, request, redraw);
2098 if (redraw)
2099 update_view_title(view);
2100 } else {
2101 move_view(view, request, TRUE);
2102 break;
2103 }
2104 /* Fall-through */
2105
2106 case REQ_ENTER:
2107 if (!view->lines) {
2108 report("Nothing to enter");
2109 break;
2110 }
2111 return view->ops->enter(view, &view->line[view->lineno]);
2112
2113 case REQ_VIEW_NEXT:
2114 {
2115 int nviews = displayed_views();
2116 int next_view = (current_view + 1) % nviews;
2117
2118 if (next_view == current_view) {
2119 report("Only one view is displayed");
2120 break;
2121 }
2122
2123 current_view = next_view;
2124 /* Blur out the title of the previous view. */
2125 update_view_title(view);
2126 report("");
2127 break;
2128 }
2129 case REQ_TOGGLE_LINENO:
2130 opt_line_number = !opt_line_number;
2131 redraw_display();
2132 break;
2133
2134 case REQ_TOGGLE_REV_GRAPH:
2135 opt_rev_graph = !opt_rev_graph;
2136 redraw_display();
2137 break;
2138
2139 case REQ_PROMPT:
2140 /* Always reload^Wrerun commands from the prompt. */
2141 open_view(view, opt_request, OPEN_RELOAD);
2142 break;
2143
2144 case REQ_SEARCH:
2145 case REQ_SEARCH_BACK:
2146 search_view(view, request, opt_search);
2147 break;
2148
2149 case REQ_FIND_NEXT:
2150 case REQ_FIND_PREV:
2151 find_next(view, request);
2152 break;
2153
2154 case REQ_STOP_LOADING:
2155 for (i = 0; i < ARRAY_SIZE(views); i++) {
2156 view = &views[i];
2157 if (view->pipe)
2158 report("Stopped loading the %s view", view->name),
2159 end_update(view);
2160 }
2161 break;
2162
2163 case REQ_SHOW_VERSION:
2164 report("%s (built %s)", VERSION, __DATE__);
2165 return TRUE;
2166
2167 case REQ_SCREEN_RESIZE:
2168 resize_display();
2169 /* Fall-through */
2170 case REQ_SCREEN_REDRAW:
2171 redraw_display();
2172 break;
2173
2174 case REQ_NONE:
2175 doupdate();
2176 return TRUE;
2177
2178 case REQ_VIEW_CLOSE:
2179 /* XXX: Mark closed views by letting view->parent point to the
2180 * view itself. Parents to closed view should never be
2181 * followed. */
2182 if (view->parent &&
2183 view->parent->parent != view->parent) {
2184 memset(display, 0, sizeof(display));
2185 current_view = 0;
2186 display[current_view] = view->parent;
2187 view->parent = view;
2188 resize_display();
2189 redraw_display();
2190 break;
2191 }
2192 /* Fall-through */
2193 case REQ_QUIT:
2194 return FALSE;
2195
2196 default:
2197 /* An unknown key will show most commonly used commands. */
2198 report("Unknown key, press 'h' for help");
2199 return TRUE;
2200 }
2201
2202 return TRUE;
2203 }
2204
2205
2206 /*
2207 * Pager backend
2208 */
2209
2210 static bool
2211 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2212 {
2213 char *text = line->data;
2214 enum line_type type = line->type;
2215 int textlen = strlen(text);
2216 int attr;
2217
2218 wmove(view->win, lineno, 0);
2219
2220 if (view->offset + lineno == view->lineno) {
2221 type = LINE_CURSOR;
2222 wchgat(view->win, -1, 0, type, NULL);
2223 }
2224
2225 attr = get_line_attr(type);
2226 wattrset(view->win, attr);
2227
2228 if (opt_line_number || opt_tab_size < TABSIZE) {
2229 static char spaces[] = " ";
2230 int col_offset = 0, col = 0;
2231
2232 if (opt_line_number) {
2233 unsigned long real_lineno = view->offset + lineno + 1;
2234
2235 if (real_lineno == 1 ||
2236 (real_lineno % opt_num_interval) == 0) {
2237 wprintw(view->win, "%.*d", view->digits, real_lineno);
2238
2239 } else {
2240 waddnstr(view->win, spaces,
2241 MIN(view->digits, STRING_SIZE(spaces)));
2242 }
2243 waddstr(view->win, ": ");
2244 col_offset = view->digits + 2;
2245 }
2246
2247 while (text && col_offset + col < view->width) {
2248 int cols_max = view->width - col_offset - col;
2249 char *pos = text;
2250 int cols;
2251
2252 if (*text == '\t') {
2253 text++;
2254 assert(sizeof(spaces) > TABSIZE);
2255 pos = spaces;
2256 cols = opt_tab_size - (col % opt_tab_size);
2257
2258 } else {
2259 text = strchr(text, '\t');
2260 cols = line ? text - pos : strlen(pos);
2261 }
2262
2263 waddnstr(view->win, pos, MIN(cols, cols_max));
2264 col += cols;
2265 }
2266
2267 } else {
2268 int col = 0, pos = 0;
2269
2270 for (; pos < textlen && col < view->width; pos++, col++)
2271 if (text[pos] == '\t')
2272 col += TABSIZE - (col % TABSIZE) - 1;
2273
2274 waddnstr(view->win, text, pos);
2275 }
2276
2277 return TRUE;
2278 }
2279
2280 static bool
2281 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2282 {
2283 char refbuf[SIZEOF_STR];
2284 char *ref = NULL;
2285 FILE *pipe;
2286
2287 if (!string_format(refbuf, "git describe %s", commit_id))
2288 return TRUE;
2289
2290 pipe = popen(refbuf, "r");
2291 if (!pipe)
2292 return TRUE;
2293
2294 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2295 ref = chomp_string(ref);
2296 pclose(pipe);
2297
2298 if (!ref || !*ref)
2299 return TRUE;
2300
2301 /* This is the only fatal call, since it can "corrupt" the buffer. */
2302 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2303 return FALSE;
2304
2305 return TRUE;
2306 }
2307
2308 static void
2309 add_pager_refs(struct view *view, struct line *line)
2310 {
2311 char buf[SIZEOF_STR];
2312 char *commit_id = line->data + STRING_SIZE("commit ");
2313 struct ref **refs;
2314 size_t bufpos = 0, refpos = 0;
2315 const char *sep = "Refs: ";
2316 bool is_tag = FALSE;
2317
2318 assert(line->type == LINE_COMMIT);
2319
2320 refs = get_refs(commit_id);
2321 if (!refs) {
2322 if (view == VIEW(REQ_VIEW_DIFF))
2323 goto try_add_describe_ref;
2324 return;
2325 }
2326
2327 do {
2328 struct ref *ref = refs[refpos];
2329 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
2330
2331 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2332 return;
2333 sep = ", ";
2334 if (ref->tag)
2335 is_tag = TRUE;
2336 } while (refs[refpos++]->next);
2337
2338 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2339 try_add_describe_ref:
2340 /* Add <tag>-g<commit_id> "fake" reference. */
2341 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2342 return;
2343 }
2344
2345 if (bufpos == 0)
2346 return;
2347
2348 if (!realloc_lines(view, view->line_size + 1))
2349 return;
2350
2351 line = &view->line[view->lines];
2352 line->data = strdup(buf);
2353 if (!line->data)
2354 return;
2355
2356 line->type = LINE_PP_REFS;
2357 view->lines++;
2358 }
2359
2360 static bool
2361 pager_read(struct view *view, char *data)
2362 {
2363 struct line *line = &view->line[view->lines];
2364
2365 line->data = strdup(data);
2366 if (!line->data)
2367 return FALSE;
2368
2369 line->type = get_line_type(line->data);
2370 view->lines++;
2371
2372 if (line->type == LINE_COMMIT &&
2373 (view == VIEW(REQ_VIEW_DIFF) ||
2374 view == VIEW(REQ_VIEW_LOG)))
2375 add_pager_refs(view, line);
2376
2377 return TRUE;
2378 }
2379
2380 static bool
2381 pager_enter(struct view *view, struct line *line)
2382 {
2383 int split = 0;
2384
2385 if (line->type == LINE_COMMIT &&
2386 (view == VIEW(REQ_VIEW_LOG) ||
2387 view == VIEW(REQ_VIEW_PAGER))) {
2388 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2389 split = 1;
2390 }
2391
2392 /* Always scroll the view even if it was split. That way
2393 * you can use Enter to scroll through the log view and
2394 * split open each commit diff. */
2395 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2396
2397 /* FIXME: A minor workaround. Scrolling the view will call report("")
2398 * but if we are scrolling a non-current view this won't properly
2399 * update the view title. */
2400 if (split)
2401 update_view_title(view);
2402
2403 return TRUE;
2404 }
2405
2406 static bool
2407 pager_grep(struct view *view, struct line *line)
2408 {
2409 regmatch_t pmatch;
2410 char *text = line->data;
2411
2412 if (!*text)
2413 return FALSE;
2414
2415 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2416 return FALSE;
2417
2418 return TRUE;
2419 }
2420
2421 static void
2422 pager_select(struct view *view, struct line *line)
2423 {
2424 if (line->type == LINE_COMMIT) {
2425 char *text = line->data;
2426
2427 string_copy(view->ref, text + 7);
2428 string_copy(ref_commit, view->ref);
2429 }
2430 }
2431
2432 static struct view_ops pager_ops = {
2433 "line",
2434 pager_draw,
2435 pager_read,
2436 pager_enter,
2437 pager_grep,
2438 pager_select,
2439 };
2440
2441
2442 /*
2443 * Tree backend
2444 */
2445
2446 /* Parse output from git ls-tree:
2447 *
2448 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2449 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2450 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2451 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2452 */
2453
2454 #define SIZEOF_TREE_ATTR \
2455 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2456
2457 #define TREE_UP_FORMAT "040000 tree %s\t.."
2458
2459 static int
2460 tree_compare_entry(enum line_type type1, char *name1,
2461 enum line_type type2, char *name2)
2462 {
2463 if (type1 != type2) {
2464 if (type1 == LINE_TREE_DIR)
2465 return -1;
2466 return 1;
2467 }
2468
2469 return strcmp(name1, name2);
2470 }
2471
2472 static bool
2473 tree_read(struct view *view, char *text)
2474 {
2475 size_t textlen = strlen(text);
2476 char buf[SIZEOF_STR];
2477 unsigned long pos;
2478 enum line_type type;
2479 bool first_read = view->lines == 0;
2480
2481 if (textlen <= SIZEOF_TREE_ATTR)
2482 return FALSE;
2483
2484 type = text[STRING_SIZE("100644 ")] == 't'
2485 ? LINE_TREE_DIR : LINE_TREE_FILE;
2486
2487 if (first_read) {
2488 /* Add path info line */
2489 if (string_format(buf, "Directory path /%s", opt_path) &&
2490 realloc_lines(view, view->line_size + 1) &&
2491 pager_read(view, buf))
2492 view->line[view->lines - 1].type = LINE_DEFAULT;
2493 else
2494 return FALSE;
2495
2496 /* Insert "link" to parent directory. */
2497 if (*opt_path &&
2498 string_format(buf, TREE_UP_FORMAT, view->ref) &&
2499 realloc_lines(view, view->line_size + 1) &&
2500 pager_read(view, buf))
2501 view->line[view->lines - 1].type = LINE_TREE_DIR;
2502 else if (*opt_path)
2503 return FALSE;
2504 }
2505
2506 /* Strip the path part ... */
2507 if (*opt_path) {
2508 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2509 size_t striplen = strlen(opt_path);
2510 char *path = text + SIZEOF_TREE_ATTR;
2511
2512 if (pathlen > striplen)
2513 memmove(path, path + striplen,
2514 pathlen - striplen + 1);
2515 }
2516
2517 /* Skip "Directory ..." and ".." line. */
2518 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2519 struct line *line = &view->line[pos];
2520 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2521 char *path2 = text + SIZEOF_TREE_ATTR;
2522 int cmp = tree_compare_entry(line->type, path1, type, path2);
2523
2524 if (cmp <= 0)
2525 continue;
2526
2527 text = strdup(text);
2528 if (!text)
2529 return FALSE;
2530
2531 if (view->lines > pos)
2532 memmove(&view->line[pos + 1], &view->line[pos],
2533 (view->lines - pos) * sizeof(*line));
2534
2535 line = &view->line[pos];
2536 line->data = text;
2537 line->type = type;
2538 view->lines++;
2539 return TRUE;
2540 }
2541
2542 if (!pager_read(view, text))
2543 return FALSE;
2544
2545 /* Move the current line to the first tree entry. */
2546 if (first_read)
2547 view->lineno++;
2548
2549 view->line[view->lines - 1].type = type;
2550 return TRUE;
2551 }
2552
2553 static bool
2554 tree_enter(struct view *view, struct line *line)
2555 {
2556 enum open_flags flags = OPEN_DEFAULT;
2557 char *data = line->data;
2558 enum request request;
2559
2560 switch (line->type) {
2561 case LINE_TREE_DIR:
2562 /* Depending on whether it is a subdir or parent (updir?) link
2563 * mangle the path buffer. */
2564 if (line == &view->line[1] && *opt_path) {
2565 size_t path_len = strlen(opt_path);
2566 char *dirsep = opt_path + path_len - 1;
2567
2568 while (dirsep > opt_path && dirsep[-1] != '/')
2569 dirsep--;
2570
2571 dirsep[0] = 0;
2572
2573 } else {
2574 size_t pathlen = strlen(opt_path);
2575 size_t origlen = pathlen;
2576 char *basename = data + SIZEOF_TREE_ATTR;
2577
2578 if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2579 opt_path[origlen] = 0;
2580 return TRUE;
2581 }
2582 }
2583
2584 /* Trees and subtrees share the same ID, so they are not not
2585 * unique like blobs. */
2586 flags |= OPEN_RELOAD;
2587 request = REQ_VIEW_TREE;
2588 break;
2589
2590 case LINE_TREE_FILE:
2591 /* This causes the blob view to become split, and not having it
2592 * in the tree dir case will make the blob view automatically
2593 * disappear when moving to a different directory. */
2594 flags |= OPEN_SPLIT;
2595 request = REQ_VIEW_BLOB;
2596 break;
2597
2598 default:
2599 return TRUE;
2600 }
2601
2602 open_view(view, request, flags);
2603
2604 if (!VIEW(request)->pipe)
2605 return TRUE;
2606
2607 /* For tree views insert the path to the parent as the first line. */
2608 if (request == REQ_VIEW_BLOB) {
2609 /* Mirror what is showed in the title bar. */
2610 string_ncopy(ref_blob, data + STRING_SIZE("100644 blob "), 40);
2611 string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob);
2612 return TRUE;
2613 }
2614
2615 return TRUE;
2616 }
2617
2618 static void
2619 tree_select(struct view *view, struct line *line)
2620 {
2621 if (line->type == LINE_TREE_DIR || line->type == LINE_TREE_FILE) {
2622 char *text = line->data;
2623
2624 string_ncopy(view->ref, text + STRING_SIZE("100644 blob "), 40);
2625 string_copy(ref_blob, view->ref);
2626 }
2627 }
2628
2629 static struct view_ops tree_ops = {
2630 "file",
2631 pager_draw,
2632 tree_read,
2633 tree_enter,
2634 pager_grep,
2635 tree_select,
2636 };
2637
2638 static bool
2639 blob_read(struct view *view, char *line)
2640 {
2641 bool state = pager_read(view, line);
2642
2643 if (state == TRUE)
2644 view->line[view->lines - 1].type = LINE_DEFAULT;
2645
2646 return state;
2647 }
2648
2649 static struct view_ops blob_ops = {
2650 "line",
2651 pager_draw,
2652 blob_read,
2653 pager_enter,
2654 pager_grep,
2655 pager_select,
2656 };
2657
2658
2659 /*
2660 * Main view backend
2661 */
2662
2663 struct commit {
2664 char id[41]; /* SHA1 ID. */
2665 char title[75]; /* First line of the commit message. */
2666 char author[75]; /* Author of the commit. */
2667 struct tm time; /* Date from the author ident. */
2668 struct ref **refs; /* Repository references. */
2669 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
2670 size_t graph_size; /* The width of the graph array. */
2671 };
2672
2673 static bool
2674 main_draw(struct view *view, struct line *line, unsigned int lineno)
2675 {
2676 char buf[DATE_COLS + 1];
2677 struct commit *commit = line->data;
2678 enum line_type type;
2679 int col = 0;
2680 size_t timelen;
2681 size_t authorlen;
2682 int trimmed = 1;
2683
2684 if (!*commit->author)
2685 return FALSE;
2686
2687 wmove(view->win, lineno, col);
2688
2689 if (view->offset + lineno == view->lineno) {
2690 type = LINE_CURSOR;
2691 wattrset(view->win, get_line_attr(type));
2692 wchgat(view->win, -1, 0, type, NULL);
2693
2694 } else {
2695 type = LINE_MAIN_COMMIT;
2696 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2697 }
2698
2699 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2700 waddnstr(view->win, buf, timelen);
2701 waddstr(view->win, " ");
2702
2703 col += DATE_COLS;
2704 wmove(view->win, lineno, col);
2705 if (type != LINE_CURSOR)
2706 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2707
2708 if (opt_utf8) {
2709 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2710 } else {
2711 authorlen = strlen(commit->author);
2712 if (authorlen > AUTHOR_COLS - 2) {
2713 authorlen = AUTHOR_COLS - 2;
2714 trimmed = 1;
2715 }
2716 }
2717
2718 if (trimmed) {
2719 waddnstr(view->win, commit->author, authorlen);
2720 if (type != LINE_CURSOR)
2721 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2722 waddch(view->win, '~');
2723 } else {
2724 waddstr(view->win, commit->author);
2725 }
2726
2727 col += AUTHOR_COLS;
2728 if (type != LINE_CURSOR)
2729 wattrset(view->win, A_NORMAL);
2730
2731 if (opt_rev_graph && commit->graph_size) {
2732 size_t i;
2733
2734 wmove(view->win, lineno, col);
2735 /* Using waddch() instead of waddnstr() ensures that
2736 * they'll be rendered correctly for the cursor line. */
2737 for (i = 0; i < commit->graph_size; i++)
2738 waddch(view->win, commit->graph[i]);
2739
2740 col += commit->graph_size + 1;
2741 }
2742
2743 wmove(view->win, lineno, col);
2744
2745 if (commit->refs) {
2746 size_t i = 0;
2747
2748 do {
2749 if (type == LINE_CURSOR)
2750 ;
2751 else if (commit->refs[i]->tag)
2752 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2753 else
2754 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2755 waddstr(view->win, "[");
2756 waddstr(view->win, commit->refs[i]->name);
2757 waddstr(view->win, "]");
2758 if (type != LINE_CURSOR)
2759 wattrset(view->win, A_NORMAL);
2760 waddstr(view->win, " ");
2761 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2762 } while (commit->refs[i++]->next);
2763 }
2764
2765 if (type != LINE_CURSOR)
2766 wattrset(view->win, get_line_attr(type));
2767
2768 {
2769 int titlelen = strlen(commit->title);
2770
2771 if (col + titlelen > view->width)
2772 titlelen = view->width - col;
2773
2774 waddnstr(view->win, commit->title, titlelen);
2775 }
2776
2777 return TRUE;
2778 }
2779
2780 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2781 static bool
2782 main_read(struct view *view, char *line)
2783 {
2784 enum line_type type = get_line_type(line);
2785 struct commit *commit = view->lines
2786 ? view->line[view->lines - 1].data : NULL;
2787
2788 switch (type) {
2789 case LINE_COMMIT:
2790 commit = calloc(1, sizeof(struct commit));
2791 if (!commit)
2792 return FALSE;
2793
2794 line += STRING_SIZE("commit ");
2795
2796 view->line[view->lines++].data = commit;
2797 string_copy(commit->id, line);
2798 commit->refs = get_refs(commit->id);
2799 commit->graph[commit->graph_size++] = ACS_LTEE;
2800 break;
2801
2802 case LINE_AUTHOR:
2803 {
2804 char *ident = line + STRING_SIZE("author ");
2805 char *end = strchr(ident, '<');
2806
2807 if (!commit)
2808 break;
2809
2810 if (end) {
2811 char *email = end + 1;
2812
2813 for (; end > ident && isspace(end[-1]); end--) ;
2814
2815 if (end == ident && *email) {
2816 ident = email;
2817 end = strchr(ident, '>');
2818 for (; end > ident && isspace(end[-1]); end--) ;
2819 }
2820 *end = 0;
2821 }
2822
2823 /* End is NULL or ident meaning there's no author. */
2824 if (end <= ident)
2825 ident = "Unknown";
2826
2827 string_copy(commit->author, ident);
2828
2829 /* Parse epoch and timezone */
2830 if (end) {
2831 char *secs = strchr(end + 1, '>');
2832 char *zone;
2833 time_t time;
2834
2835 if (!secs || secs[1] != ' ')
2836 break;
2837
2838 secs += 2;
2839 time = (time_t) atol(secs);
2840 zone = strchr(secs, ' ');
2841 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
2842 long tz;
2843
2844 zone++;
2845 tz = ('0' - zone[1]) * 60 * 60 * 10;
2846 tz += ('0' - zone[2]) * 60 * 60;
2847 tz += ('0' - zone[3]) * 60;
2848 tz += ('0' - zone[4]) * 60;
2849
2850 if (zone[0] == '-')
2851 tz = -tz;
2852
2853 time -= tz;
2854 }
2855 gmtime_r(&time, &commit->time);
2856 }
2857 break;
2858 }
2859 default:
2860 if (!commit)
2861 break;
2862
2863 /* Fill in the commit title if it has not already been set. */
2864 if (commit->title[0])
2865 break;
2866
2867 /* Require titles to start with a non-space character at the
2868 * offset used by git log. */
2869 /* FIXME: More gracefull handling of titles; append "..." to
2870 * shortened titles, etc. */
2871 if (strncmp(line, " ", 4) ||
2872 isspace(line[4]))
2873 break;
2874
2875 string_copy(commit->title, line + 4);
2876 }
2877
2878 return TRUE;
2879 }
2880
2881 static bool
2882 main_enter(struct view *view, struct line *line)
2883 {
2884 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2885
2886 open_view(view, REQ_VIEW_DIFF, flags);
2887 return TRUE;
2888 }
2889
2890 static bool
2891 main_grep(struct view *view, struct line *line)
2892 {
2893 struct commit *commit = line->data;
2894 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
2895 char buf[DATE_COLS + 1];
2896 regmatch_t pmatch;
2897
2898 for (state = S_TITLE; state < S_END; state++) {
2899 char *text;
2900
2901 switch (state) {
2902 case S_TITLE: text = commit->title; break;
2903 case S_AUTHOR: text = commit->author; break;
2904 case S_DATE:
2905 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
2906 continue;
2907 text = buf;
2908 break;
2909
2910 default:
2911 return FALSE;
2912 }
2913
2914 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
2915 return TRUE;
2916 }
2917
2918 return FALSE;
2919 }
2920
2921 static void
2922 main_select(struct view *view, struct line *line)
2923 {
2924 struct commit *commit = line->data;
2925
2926 string_copy(view->ref, commit->id);
2927 string_copy(ref_commit, view->ref);
2928 }
2929
2930 static struct view_ops main_ops = {
2931 "commit",
2932 main_draw,
2933 main_read,
2934 main_enter,
2935 main_grep,
2936 main_select,
2937 };
2938
2939
2940 /*
2941 * Unicode / UTF-8 handling
2942 *
2943 * NOTE: Much of the following code for dealing with unicode is derived from
2944 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2945 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2946 */
2947
2948 /* I've (over)annotated a lot of code snippets because I am not entirely
2949 * confident that the approach taken by this small UTF-8 interface is correct.
2950 * --jonas */
2951
2952 static inline int
2953 unicode_width(unsigned long c)
2954 {
2955 if (c >= 0x1100 &&
2956 (c <= 0x115f /* Hangul Jamo */
2957 || c == 0x2329
2958 || c == 0x232a
2959 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
2960 /* CJK ... Yi */
2961 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
2962 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
2963 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
2964 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
2965 || (c >= 0xffe0 && c <= 0xffe6)
2966 || (c >= 0x20000 && c <= 0x2fffd)
2967 || (c >= 0x30000 && c <= 0x3fffd)))
2968 return 2;
2969
2970 return 1;
2971 }
2972
2973 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2974 * Illegal bytes are set one. */
2975 static const unsigned char utf8_bytes[256] = {
2976 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,
2977 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,
2978 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,
2979 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,
2980 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,
2981 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,
2982 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,
2983 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,
2984 };
2985
2986 /* Decode UTF-8 multi-byte representation into a unicode character. */
2987 static inline unsigned long
2988 utf8_to_unicode(const char *string, size_t length)
2989 {
2990 unsigned long unicode;
2991
2992 switch (length) {
2993 case 1:
2994 unicode = string[0];
2995 break;
2996 case 2:
2997 unicode = (string[0] & 0x1f) << 6;
2998 unicode += (string[1] & 0x3f);
2999 break;
3000 case 3:
3001 unicode = (string[0] & 0x0f) << 12;
3002 unicode += ((string[1] & 0x3f) << 6);
3003 unicode += (string[2] & 0x3f);
3004 break;
3005 case 4:
3006 unicode = (string[0] & 0x0f) << 18;
3007 unicode += ((string[1] & 0x3f) << 12);
3008 unicode += ((string[2] & 0x3f) << 6);
3009 unicode += (string[3] & 0x3f);
3010 break;
3011 case 5:
3012 unicode = (string[0] & 0x0f) << 24;
3013 unicode += ((string[1] & 0x3f) << 18);
3014 unicode += ((string[2] & 0x3f) << 12);
3015 unicode += ((string[3] & 0x3f) << 6);
3016 unicode += (string[4] & 0x3f);
3017 break;
3018 case 6:
3019 unicode = (string[0] & 0x01) << 30;
3020 unicode += ((string[1] & 0x3f) << 24);
3021 unicode += ((string[2] & 0x3f) << 18);
3022 unicode += ((string[3] & 0x3f) << 12);
3023 unicode += ((string[4] & 0x3f) << 6);
3024 unicode += (string[5] & 0x3f);
3025 break;
3026 default:
3027 die("Invalid unicode length");
3028 }
3029
3030 /* Invalid characters could return the special 0xfffd value but NUL
3031 * should be just as good. */
3032 return unicode > 0xffff ? 0 : unicode;
3033 }
3034
3035 /* Calculates how much of string can be shown within the given maximum width
3036 * and sets trimmed parameter to non-zero value if all of string could not be
3037 * shown.
3038 *
3039 * Additionally, adds to coloffset how many many columns to move to align with
3040 * the expected position. Takes into account how multi-byte and double-width
3041 * characters will effect the cursor position.
3042 *
3043 * Returns the number of bytes to output from string to satisfy max_width. */
3044 static size_t
3045 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3046 {
3047 const char *start = string;
3048 const char *end = strchr(string, '\0');
3049 size_t mbwidth = 0;
3050 size_t width = 0;
3051
3052 *trimmed = 0;
3053
3054 while (string < end) {
3055 int c = *(unsigned char *) string;
3056 unsigned char bytes = utf8_bytes[c];
3057 size_t ucwidth;
3058 unsigned long unicode;
3059
3060 if (string + bytes > end)
3061 break;
3062
3063 /* Change representation to figure out whether
3064 * it is a single- or double-width character. */
3065
3066 unicode = utf8_to_unicode(string, bytes);
3067 /* FIXME: Graceful handling of invalid unicode character. */
3068 if (!unicode)
3069 break;
3070
3071 ucwidth = unicode_width(unicode);
3072 width += ucwidth;
3073 if (width > max_width) {
3074 *trimmed = 1;
3075 break;
3076 }
3077
3078 /* The column offset collects the differences between the
3079 * number of bytes encoding a character and the number of
3080 * columns will be used for rendering said character.
3081 *
3082 * So if some character A is encoded in 2 bytes, but will be
3083 * represented on the screen using only 1 byte this will and up
3084 * adding 1 to the multi-byte column offset.
3085 *
3086 * Assumes that no double-width character can be encoding in
3087 * less than two bytes. */
3088 if (bytes > ucwidth)
3089 mbwidth += bytes - ucwidth;
3090
3091 string += bytes;
3092 }
3093
3094 *coloffset += mbwidth;
3095
3096 return string - start;
3097 }
3098
3099
3100 /*
3101 * Status management
3102 */
3103
3104 /* Whether or not the curses interface has been initialized. */
3105 static bool cursed = FALSE;
3106
3107 /* The status window is used for polling keystrokes. */
3108 static WINDOW *status_win;
3109
3110 /* Update status and title window. */
3111 static void
3112 report(const char *msg, ...)
3113 {
3114 static bool empty = TRUE;
3115 struct view *view = display[current_view];
3116
3117 if (!empty || *msg) {
3118 va_list args;
3119
3120 va_start(args, msg);
3121
3122 werase(status_win);
3123 wmove(status_win, 0, 0);
3124 if (*msg) {
3125 vwprintw(status_win, msg, args);
3126 empty = FALSE;
3127 } else {
3128 empty = TRUE;
3129 }
3130 wrefresh(status_win);
3131
3132 va_end(args);
3133 }
3134
3135 update_view_title(view);
3136 update_display_cursor();
3137 }
3138
3139 /* Controls when nodelay should be in effect when polling user input. */
3140 static void
3141 set_nonblocking_input(bool loading)
3142 {
3143 static unsigned int loading_views;
3144
3145 if ((loading == FALSE && loading_views-- == 1) ||
3146 (loading == TRUE && loading_views++ == 0))
3147 nodelay(status_win, loading);
3148 }
3149
3150 static void
3151 init_display(void)
3152 {
3153 int x, y;
3154
3155 /* Initialize the curses library */
3156 if (isatty(STDIN_FILENO)) {
3157 cursed = !!initscr();
3158 } else {
3159 /* Leave stdin and stdout alone when acting as a pager. */
3160 FILE *io = fopen("/dev/tty", "r+");
3161
3162 if (!io)
3163 die("Failed to open /dev/tty");
3164 cursed = !!newterm(NULL, io, io);
3165 }
3166
3167 if (!cursed)
3168 die("Failed to initialize curses");
3169
3170 nonl(); /* Tell curses not to do NL->CR/NL on output */
3171 cbreak(); /* Take input chars one at a time, no wait for \n */
3172 noecho(); /* Don't echo input */
3173 leaveok(stdscr, TRUE);
3174
3175 if (has_colors())
3176 init_colors();
3177
3178 getmaxyx(stdscr, y, x);
3179 status_win = newwin(1, 0, y - 1, 0);
3180 if (!status_win)
3181 die("Failed to create status window");
3182
3183 /* Enable keyboard mapping */
3184 keypad(status_win, TRUE);
3185 wbkgdset(status_win, get_line_attr(LINE_STATUS));
3186 }
3187
3188 static char *
3189 read_prompt(const char *prompt)
3190 {
3191 enum { READING, STOP, CANCEL } status = READING;
3192 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3193 int pos = 0;
3194
3195 while (status == READING) {
3196 struct view *view;
3197 int i, key;
3198
3199 foreach_view (view, i)
3200 update_view(view);
3201
3202 report("%s%.*s", prompt, pos, buf);
3203 /* Refresh, accept single keystroke of input */
3204 key = wgetch(status_win);
3205 switch (key) {
3206 case KEY_RETURN:
3207 case KEY_ENTER:
3208 case '\n':
3209 status = pos ? STOP : CANCEL;
3210 break;
3211
3212 case KEY_BACKSPACE:
3213 if (pos > 0)
3214 pos--;
3215 else
3216 status = CANCEL;
3217 break;
3218
3219 case KEY_ESC:
3220 status = CANCEL;
3221 break;
3222
3223 case ERR:
3224 break;
3225
3226 default:
3227 if (pos >= sizeof(buf)) {
3228 report("Input string too long");
3229 return NULL;
3230 }
3231
3232 if (isprint(key))
3233 buf[pos++] = (char) key;
3234 }
3235 }
3236
3237 if (status == CANCEL) {
3238 /* Clear the status window */
3239 report("");
3240 return NULL;
3241 }
3242
3243 buf[pos++] = 0;
3244
3245 return buf;
3246 }
3247
3248 /*
3249 * Repository references
3250 */
3251
3252 static struct ref *refs;
3253 static size_t refs_size;
3254
3255 /* Id <-> ref store */
3256 static struct ref ***id_refs;
3257 static size_t id_refs_size;
3258
3259 static struct ref **
3260 get_refs(char *id)
3261 {
3262 struct ref ***tmp_id_refs;
3263 struct ref **ref_list = NULL;
3264 size_t ref_list_size = 0;
3265 size_t i;
3266
3267 for (i = 0; i < id_refs_size; i++)
3268 if (!strcmp(id, id_refs[i][0]->id))
3269 return id_refs[i];
3270
3271 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3272 if (!tmp_id_refs)
3273 return NULL;
3274
3275 id_refs = tmp_id_refs;
3276
3277 for (i = 0; i < refs_size; i++) {
3278 struct ref **tmp;
3279
3280 if (strcmp(id, refs[i].id))
3281 continue;
3282
3283 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3284 if (!tmp) {
3285 if (ref_list)
3286 free(ref_list);
3287 return NULL;
3288 }
3289
3290 ref_list = tmp;
3291 if (ref_list_size > 0)
3292 ref_list[ref_list_size - 1]->next = 1;
3293 ref_list[ref_list_size] = &refs[i];
3294
3295 /* XXX: The properties of the commit chains ensures that we can
3296 * safely modify the shared ref. The repo references will
3297 * always be similar for the same id. */
3298 ref_list[ref_list_size]->next = 0;
3299 ref_list_size++;
3300 }
3301
3302 if (ref_list)
3303 id_refs[id_refs_size++] = ref_list;
3304
3305 return ref_list;
3306 }
3307
3308 static int
3309 read_ref(char *id, int idlen, char *name, int namelen)
3310 {
3311 struct ref *ref;
3312 bool tag = FALSE;
3313
3314 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3315 /* Commits referenced by tags has "^{}" appended. */
3316 if (name[namelen - 1] != '}')
3317 return OK;
3318
3319 while (namelen > 0 && name[namelen] != '^')
3320 namelen--;
3321
3322 tag = TRUE;
3323 namelen -= STRING_SIZE("refs/tags/");
3324 name += STRING_SIZE("refs/tags/");
3325
3326 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
3327 namelen -= STRING_SIZE("refs/heads/");
3328 name += STRING_SIZE("refs/heads/");
3329
3330 } else if (!strcmp(name, "HEAD")) {
3331 return OK;
3332 }
3333
3334 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3335 if (!refs)
3336 return ERR;
3337
3338 ref = &refs[refs_size++];
3339 ref->name = malloc(namelen + 1);
3340 if (!ref->name)
3341 return ERR;
3342
3343 strncpy(ref->name, name, namelen);
3344 ref->name[namelen] = 0;
3345 ref->tag = tag;
3346 string_copy(ref->id, id);
3347
3348 return OK;
3349 }
3350
3351 static int
3352 load_refs(void)
3353 {
3354 const char *cmd_env = getenv("TIG_LS_REMOTE");
3355 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
3356
3357 return read_properties(popen(cmd, "r"), "\t", read_ref);
3358 }
3359
3360 static int
3361 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
3362 {
3363 if (!strcmp(name, "i18n.commitencoding"))
3364 string_copy(opt_encoding, value);
3365
3366 return OK;
3367 }
3368
3369 static int
3370 load_repo_config(void)
3371 {
3372 return read_properties(popen("git repo-config --list", "r"),
3373 "=", read_repo_config_option);
3374 }
3375
3376 static int
3377 read_properties(FILE *pipe, const char *separators,
3378 int (*read_property)(char *, int, char *, int))
3379 {
3380 char buffer[BUFSIZ];
3381 char *name;
3382 int state = OK;
3383
3384 if (!pipe)
3385 return ERR;
3386
3387 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
3388 char *value;
3389 size_t namelen;
3390 size_t valuelen;
3391
3392 name = chomp_string(name);
3393 namelen = strcspn(name, separators);
3394
3395 if (name[namelen]) {
3396 name[namelen] = 0;
3397 value = chomp_string(name + namelen + 1);
3398 valuelen = strlen(value);
3399
3400 } else {
3401 value = "";
3402 valuelen = 0;
3403 }
3404
3405 state = read_property(name, namelen, value, valuelen);
3406 }
3407
3408 if (state != ERR && ferror(pipe))
3409 state = ERR;
3410
3411 pclose(pipe);
3412
3413 return state;
3414 }
3415
3416
3417 /*
3418 * Main
3419 */
3420
3421 static void __NORETURN
3422 quit(int sig)
3423 {
3424 /* XXX: Restore tty modes and let the OS cleanup the rest! */
3425 if (cursed)
3426 endwin();
3427 exit(0);
3428 }
3429
3430 static void __NORETURN
3431 die(const char *err, ...)
3432 {
3433 va_list args;
3434
3435 endwin();
3436
3437 va_start(args, err);
3438 fputs("tig: ", stderr);
3439 vfprintf(stderr, err, args);
3440 fputs("\n", stderr);
3441 va_end(args);
3442
3443 exit(1);
3444 }
3445
3446 int
3447 main(int argc, char *argv[])
3448 {
3449 struct view *view;
3450 enum request request;
3451 size_t i;
3452
3453 signal(SIGINT, quit);
3454
3455 if (setlocale(LC_ALL, "")) {
3456 string_copy(opt_codeset, nl_langinfo(CODESET));
3457 }
3458
3459 if (load_options() == ERR)
3460 die("Failed to load user config.");
3461
3462 /* Load the repo config file so options can be overwritten from
3463 * the command line. */
3464 if (load_repo_config() == ERR)
3465 die("Failed to load repo config.");
3466
3467 if (!parse_options(argc, argv))
3468 return 0;
3469
3470 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3471 opt_iconv = iconv_open(opt_codeset, opt_encoding);
3472 if (opt_iconv == ICONV_NONE)
3473 die("Failed to initialize character set conversion");
3474 }
3475
3476 if (load_refs() == ERR)
3477 die("Failed to load refs.");
3478
3479 /* Require a git repository unless when running in pager mode. */
3480 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3481 die("Not a git repository");
3482
3483 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3484 view->cmd_env = getenv(view->cmd_env);
3485
3486 request = opt_request;
3487
3488 init_display();
3489
3490 while (view_driver(display[current_view], request)) {
3491 int key;
3492 int i;
3493
3494 foreach_view (view, i)
3495 update_view(view);
3496
3497 /* Refresh, accept single keystroke of input */
3498 key = wgetch(status_win);
3499
3500 request = get_keybinding(display[current_view]->keymap, key);
3501
3502 /* Some low-level request handling. This keeps access to
3503 * status_win restricted. */
3504 switch (request) {
3505 case REQ_PROMPT:
3506 {
3507 char *cmd = read_prompt(":");
3508
3509 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3510 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3511 opt_request = REQ_VIEW_DIFF;
3512 } else {
3513 opt_request = REQ_VIEW_PAGER;
3514 }
3515 break;
3516 }
3517
3518 request = REQ_NONE;
3519 break;
3520 }
3521 case REQ_SEARCH:
3522 case REQ_SEARCH_BACK:
3523 {
3524 const char *prompt = request == REQ_SEARCH
3525 ? "/" : "?";
3526 char *search = read_prompt(prompt);
3527
3528 if (search)
3529 string_copy(opt_search, search);
3530 else
3531 request = REQ_NONE;
3532 break;
3533 }
3534 case REQ_SCREEN_RESIZE:
3535 {
3536 int height, width;
3537
3538 getmaxyx(stdscr, height, width);
3539
3540 /* Resize the status view and let the view driver take
3541 * care of resizing the displayed views. */
3542 wresize(status_win, 1, width);
3543 mvwin(status_win, height - 1, 0);
3544 wrefresh(status_win);
3545 break;
3546 }
3547 default:
3548 break;
3549 }
3550 }
3551
3552 quit(0);
3553
3554 return 0;
3555 }