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