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