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