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