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