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