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