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