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