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