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