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