main_read: cleanup and simplify
[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
a1d28554 15#define VERSION "tig-0.6.git"
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 32
4af34daa
JF
33#include <sys/types.h>
34#include <regex.h>
35
6b68fd24
JF
36#include <locale.h>
37#include <langinfo.h>
38#include <iconv.h>
39
b801d8b2 40#include <curses.h>
b801d8b2 41
e2da526d
JF
42#if __GNUC__ >= 3
43#define __NORETURN __attribute__((__noreturn__))
44#else
45#define __NORETURN
46#endif
47
48static void __NORETURN die(const char *err, ...);
b801d8b2 49static void report(const char *msg, ...);
4a63c884 50static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
1ba2ae4b 51static void set_nonblocking_input(bool loading);
10e290ee 52static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
6b161b31
JF
53
54#define ABS(x) ((x) >= 0 ? (x) : -(x))
55#define MIN(x, y) ((x) < (y) ? (x) : (y))
56
57#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
58#define STRING_SIZE(x) (sizeof(x) - 1)
b76c2afc 59
17482b11 60#define SIZEOF_STR 1024 /* Default string size. */
2e8488b4 61#define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
10446330 62#define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
c8d60a25
JF
63
64/* Revision graph */
65
66#define REVGRAPH_INIT 'I'
67#define REVGRAPH_MERGE 'M'
68#define REVGRAPH_BRANCH '+'
69#define REVGRAPH_COMMIT '*'
70#define REVGRAPH_LINE '|'
71
54efb62b 72#define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
b801d8b2 73
82e78006
JF
74/* This color name can be used to refer to the default term colors. */
75#define COLOR_DEFAULT (-1)
78c70acd 76
6b68fd24
JF
77#define ICONV_NONE ((iconv_t) -1)
78
82e78006 79/* The format and size of the date column in the main view. */
4c6fabc2 80#define DATE_FORMAT "%Y-%m-%d %H:%M"
6b161b31 81#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
4c6fabc2 82
10e290ee
JF
83#define AUTHOR_COLS 20
84
a28bcc22 85/* The default interval between line numbers. */
4a2909a7 86#define NUMBER_INTERVAL 1
82e78006 87
6706b2ba
JF
88#define TABSIZE 8
89
a28bcc22
JF
90#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
91
8eb62770 92#define TIG_LS_REMOTE \
337d7377 93 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
8eb62770
JF
94
95#define TIG_DIFF_CMD \
73fb51d5 96 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
8eb62770
JF
97
98#define TIG_LOG_CMD \
8fee6614 99 "git log --cc --stat -n100 %s 2>/dev/null"
8eb62770
JF
100
101#define TIG_MAIN_CMD \
8fee6614 102 "git log --topo-order --pretty=raw %s 2>/dev/null"
8eb62770 103
e733ee54
JF
104#define TIG_TREE_CMD \
105 "git ls-tree %s %s"
106
107#define TIG_BLOB_CMD \
108 "git cat-file blob %s"
109
8eb62770
JF
110/* XXX: Needs to be defined to the empty string. */
111#define TIG_HELP_CMD ""
112#define TIG_PAGER_CMD ""
113
8855ada4 114/* Some ascii-shorthands fitted into the ncurses namespace. */
a28bcc22
JF
115#define KEY_TAB '\t'
116#define KEY_RETURN '\r'
4a2909a7
JF
117#define KEY_ESC 27
118
6706b2ba 119
c34d9c9f 120struct ref {
468876c9 121 char *name; /* Ref name; tag or head names are shortened. */
10446330 122 char id[SIZEOF_REV]; /* Commit SHA1 ID */
468876c9 123 unsigned int tag:1; /* Is it a tag? */
e15ec88e 124 unsigned int remote:1; /* Is it a remote ref? */
468876c9 125 unsigned int next:1; /* For ref lists: are there more refs? */
c34d9c9f
JF
126};
127
ff26aa29 128static struct ref **get_refs(char *id);
4c6fabc2 129
660e09ad
JF
130struct int_map {
131 const char *name;
132 int namelen;
133 int value;
134};
135
136static int
137set_from_int_map(struct int_map *map, size_t map_size,
138 int *value, const char *name, int namelen)
139{
140
141 int i;
142
143 for (i = 0; i < map_size; i++)
144 if (namelen == map[i].namelen &&
145 !strncasecmp(name, map[i].name, namelen)) {
146 *value = map[i].value;
147 return OK;
148 }
149
150 return ERR;
151}
152
6706b2ba 153
03a93dbb
JF
154/*
155 * String helpers
156 */
78c70acd 157
82e78006 158static inline void
9a48919b 159string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
82e78006 160{
9a48919b
JF
161 if (srclen > dstlen - 1)
162 srclen = dstlen - 1;
03a93dbb 163
9a48919b
JF
164 strncpy(dst, src, srclen);
165 dst[srclen] = 0;
82e78006
JF
166}
167
9a48919b
JF
168/* Shorthands for safely copying into a fixed buffer. */
169
82e78006 170#define string_copy(dst, src) \
9a48919b
JF
171 string_ncopy_do(dst, sizeof(dst), src, sizeof(dst))
172
173#define string_ncopy(dst, src, srclen) \
174 string_ncopy_do(dst, sizeof(dst), src, srclen)
82e78006 175
2463b4ea
JF
176#define string_copy_rev(dst, src) \
177 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
178
4a63c884
JF
179static char *
180chomp_string(char *name)
181{
182 int namelen;
183
184 while (isspace(*name))
185 name++;
186
187 namelen = strlen(name) - 1;
188 while (namelen > 0 && isspace(name[namelen]))
189 name[namelen--] = 0;
190
191 return name;
192}
193
cc2d1364 194static bool
d65ced0d 195string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
cc2d1364
JF
196{
197 va_list args;
d65ced0d 198 size_t pos = bufpos ? *bufpos : 0;
cc2d1364
JF
199
200 va_start(args, fmt);
201 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
202 va_end(args);
203
204 if (bufpos)
205 *bufpos = pos;
206
207 return pos >= bufsize ? FALSE : TRUE;
208}
209
210#define string_format(buf, fmt, args...) \
211 string_nformat(buf, sizeof(buf), NULL, fmt, args)
212
213#define string_format_from(buf, from, fmt, args...) \
214 string_nformat(buf, sizeof(buf), from, fmt, args)
6706b2ba 215
201f5a18
JF
216static int
217string_enum_compare(const char *str1, const char *str2, int len)
218{
219 size_t i;
220
221#define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
222
223 /* Diff-Header == DIFF_HEADER */
224 for (i = 0; i < len; i++) {
225 if (toupper(str1[i]) == toupper(str2[i]))
226 continue;
227
228 if (string_enum_sep(str1[i]) &&
229 string_enum_sep(str2[i]))
230 continue;
231
232 return str1[i] - str2[i];
233 }
234
235 return 0;
236}
237
03a93dbb
JF
238/* Shell quoting
239 *
240 * NOTE: The following is a slightly modified copy of the git project's shell
241 * quoting routines found in the quote.c file.
242 *
243 * Help to copy the thing properly quoted for the shell safety. any single
244 * quote is replaced with '\'', any exclamation point is replaced with '\!',
245 * and the whole thing is enclosed in a
246 *
247 * E.g.
248 * original sq_quote result
249 * name ==> name ==> 'name'
250 * a b ==> a b ==> 'a b'
251 * a'b ==> a'\''b ==> 'a'\''b'
252 * a!b ==> a'\!'b ==> 'a'\!'b'
253 */
254
255static size_t
17482b11 256sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
03a93dbb
JF
257{
258 char c;
259
17482b11 260#define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
03a93dbb
JF
261
262 BUFPUT('\'');
263 while ((c = *src++)) {
264 if (c == '\'' || c == '!') {
265 BUFPUT('\'');
266 BUFPUT('\\');
267 BUFPUT(c);
268 BUFPUT('\'');
269 } else {
270 BUFPUT(c);
271 }
272 }
273 BUFPUT('\'');
274
f0f114ac
JF
275 if (bufsize < SIZEOF_STR)
276 buf[bufsize] = 0;
277
03a93dbb
JF
278 return bufsize;
279}
280
82e78006 281
24b5b3e0
JF
282/*
283 * User requests
284 */
285
286#define REQ_INFO \
287 /* XXX: Keep the view request first and in sync with views[]. */ \
288 REQ_GROUP("View switching") \
289 REQ_(VIEW_MAIN, "Show main view"), \
290 REQ_(VIEW_DIFF, "Show diff view"), \
291 REQ_(VIEW_LOG, "Show log view"), \
e733ee54
JF
292 REQ_(VIEW_TREE, "Show tree view"), \
293 REQ_(VIEW_BLOB, "Show blob view"), \
24b5b3e0
JF
294 REQ_(VIEW_HELP, "Show help page"), \
295 REQ_(VIEW_PAGER, "Show pager view"), \
296 \
297 REQ_GROUP("View manipulation") \
298 REQ_(ENTER, "Enter current line and scroll"), \
299 REQ_(NEXT, "Move to next"), \
300 REQ_(PREVIOUS, "Move to previous"), \
301 REQ_(VIEW_NEXT, "Move focus to next view"), \
302 REQ_(VIEW_CLOSE, "Close the current view"), \
303 REQ_(QUIT, "Close all views and quit"), \
304 \
305 REQ_GROUP("Cursor navigation") \
306 REQ_(MOVE_UP, "Move cursor one line up"), \
307 REQ_(MOVE_DOWN, "Move cursor one line down"), \
308 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
309 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
310 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
311 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
312 \
313 REQ_GROUP("Scrolling") \
314 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
315 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
316 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
317 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
318 \
4af34daa
JF
319 REQ_GROUP("Searching") \
320 REQ_(SEARCH, "Search the view"), \
321 REQ_(SEARCH_BACK, "Search backwards in the view"), \
322 REQ_(FIND_NEXT, "Find next search match"), \
323 REQ_(FIND_PREV, "Find previous search match"), \
324 \
24b5b3e0 325 REQ_GROUP("Misc") \
1d754561 326 REQ_(NONE, "Do nothing"), \
24b5b3e0 327 REQ_(PROMPT, "Bring up the prompt"), \
24b5b3e0
JF
328 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
329 REQ_(SCREEN_RESIZE, "Resize the screen"), \
330 REQ_(SHOW_VERSION, "Show version information"), \
331 REQ_(STOP_LOADING, "Stop all loading views"), \
54efb62b 332 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
04e2b7b2 333 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization")
24b5b3e0
JF
334
335
336/* User action requests. */
337enum request {
338#define REQ_GROUP(help)
339#define REQ_(req, help) REQ_##req
340
341 /* Offset all requests to avoid conflicts with ncurses getch values. */
342 REQ_OFFSET = KEY_MAX + 1,
04e2b7b2
JF
343 REQ_INFO,
344 REQ_UNKNOWN,
24b5b3e0
JF
345
346#undef REQ_GROUP
347#undef REQ_
348};
349
350struct request_info {
351 enum request request;
04e2b7b2
JF
352 char *name;
353 int namelen;
24b5b3e0
JF
354 char *help;
355};
356
357static struct request_info req_info[] = {
04e2b7b2
JF
358#define REQ_GROUP(help) { 0, NULL, 0, (help) },
359#define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
24b5b3e0
JF
360 REQ_INFO
361#undef REQ_GROUP
362#undef REQ_
363};
364
04e2b7b2
JF
365static enum request
366get_request(const char *name)
367{
368 int namelen = strlen(name);
369 int i;
370
371 for (i = 0; i < ARRAY_SIZE(req_info); i++)
372 if (req_info[i].namelen == namelen &&
373 !string_enum_compare(req_info[i].name, name, namelen))
374 return req_info[i].request;
375
376 return REQ_UNKNOWN;
377}
378
379
8eb62770
JF
380/*
381 * Options
382 */
b76c2afc 383
4b8c01a3
JF
384static const char usage[] =
385VERSION " (" __DATE__ ")\n"
386"\n"
387"Usage: tig [options]\n"
388" or: tig [options] [--] [git log options]\n"
389" or: tig [options] log [git log options]\n"
390" or: tig [options] diff [git diff options]\n"
391" or: tig [options] show [git show options]\n"
392" or: tig [options] < [git command output]\n"
393"\n"
394"Options:\n"
395" -l Start up in log view\n"
396" -d Start up in diff view\n"
397" -n[I], --line-number[=I] Show line numbers with given interval\n"
b3c965c9 398" -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
4b8c01a3
JF
399" -- Mark end of tig options\n"
400" -v, --version Show version and exit\n"
401" -h, --help Show help message and exit\n";
402
6706b2ba 403/* Option and state variables. */
92d30f5c 404static bool opt_line_number = FALSE;
11ce319e 405static bool opt_rev_graph = FALSE;
92d30f5c
JF
406static int opt_num_interval = NUMBER_INTERVAL;
407static int opt_tab_size = TABSIZE;
408static enum request opt_request = REQ_VIEW_MAIN;
409static char opt_cmd[SIZEOF_STR] = "";
e733ee54 410static char opt_path[SIZEOF_STR] = "";
92d30f5c
JF
411static FILE *opt_pipe = NULL;
412static char opt_encoding[20] = "UTF-8";
413static bool opt_utf8 = TRUE;
414static char opt_codeset[20] = "UTF-8";
415static iconv_t opt_iconv = ICONV_NONE;
416static char opt_search[SIZEOF_STR] = "";
b76c2afc 417
6dbf6c19
JF
418enum option_type {
419 OPT_NONE,
420 OPT_INT,
421};
422
423static bool
424check_option(char *opt, char short_name, char *name, enum option_type type, ...)
425{
426 va_list args;
427 char *value = "";
428 int *number;
429
430 if (opt[0] != '-')
431 return FALSE;
432
433 if (opt[1] == '-') {
434 int namelen = strlen(name);
435
436 opt += 2;
437
438 if (strncmp(opt, name, namelen))
439 return FALSE;
440
441 if (opt[namelen] == '=')
442 value = opt + namelen + 1;
443
444 } else {
445 if (!short_name || opt[1] != short_name)
446 return FALSE;
447 value = opt + 2;
448 }
449
450 va_start(args, type);
451 if (type == OPT_INT) {
452 number = va_arg(args, int *);
453 if (isdigit(*value))
454 *number = atoi(value);
455 }
456 va_end(args);
457
458 return TRUE;
459}
460
b76c2afc 461/* Returns the index of log or diff command or -1 to exit. */
8855ada4 462static bool
b76c2afc
JF
463parse_options(int argc, char *argv[])
464{
465 int i;
466
467 for (i = 1; i < argc; i++) {
468 char *opt = argv[i];
469
3621d94e
JF
470 if (!strcmp(opt, "log") ||
471 !strcmp(opt, "diff") ||
472 !strcmp(opt, "show")) {
473 opt_request = opt[0] == 'l'
474 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
475 break;
476 }
477
8d27b36e
JF
478 if (opt[0] && opt[0] != '-')
479 break;
480
6b161b31 481 if (!strcmp(opt, "-l")) {
4a2909a7 482 opt_request = REQ_VIEW_LOG;
6b161b31
JF
483 continue;
484 }
b76c2afc 485
6b161b31 486 if (!strcmp(opt, "-d")) {
4a2909a7 487 opt_request = REQ_VIEW_DIFF;
6b161b31
JF
488 continue;
489 }
b76c2afc 490
6dbf6c19 491 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
6b161b31
JF
492 opt_line_number = TRUE;
493 continue;
494 }
b76c2afc 495
6dbf6c19
JF
496 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
497 opt_tab_size = MIN(opt_tab_size, TABSIZE);
6706b2ba
JF
498 continue;
499 }
500
6dbf6c19 501 if (check_option(opt, 'v', "version", OPT_NONE)) {
b76c2afc 502 printf("tig version %s\n", VERSION);
8855ada4 503 return FALSE;
6b161b31 504 }
b76c2afc 505
6dbf6c19 506 if (check_option(opt, 'h', "help", OPT_NONE)) {
4b8c01a3
JF
507 printf(usage);
508 return FALSE;
509 }
510
6908bdbd
JF
511 if (!strcmp(opt, "--")) {
512 i++;
513 break;
514 }
03a93dbb 515
bf174187 516 die("unknown option '%s'\n\n%s", opt, usage);
b76c2afc
JF
517 }
518
6908bdbd 519 if (!isatty(STDIN_FILENO)) {
6908bdbd
JF
520 opt_request = REQ_VIEW_PAGER;
521 opt_pipe = stdin;
522
523 } else if (i < argc) {
524 size_t buf_size;
525
6908bdbd 526 if (opt_request == REQ_VIEW_MAIN)
8855ada4
JF
527 /* XXX: This is vulnerable to the user overriding
528 * options required for the main view parser. */
832a47c4 529 string_copy(opt_cmd, "git log --pretty=raw");
6908bdbd
JF
530 else
531 string_copy(opt_cmd, "git");
532 buf_size = strlen(opt_cmd);
533
534 while (buf_size < sizeof(opt_cmd) && i < argc) {
535 opt_cmd[buf_size++] = ' ';
536 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
537 }
538
539 if (buf_size >= sizeof(opt_cmd))
540 die("command too long");
541
542 opt_cmd[buf_size] = 0;
6908bdbd
JF
543 }
544
afdc35b3
JF
545 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
546 opt_utf8 = FALSE;
547
8855ada4 548 return TRUE;
b76c2afc
JF
549}
550
551
54efb62b
JF
552/*
553 * Line-oriented content detection.
554 */
555
2e8488b4 556#define LINE_INFO \
660e09ad 557LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
a28bcc22
JF
558LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
559LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
560LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
660e09ad
JF
561LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
562LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
563LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
564LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
565LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
566LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
567LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
568LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
569LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
570LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
6908bdbd 571LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
8855ada4 572LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
6908bdbd
JF
573LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
574LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
8855ada4
JF
575LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
576LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
7b99a34c 577LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
a28bcc22
JF
578LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
579LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
580LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
8855ada4 581LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
a28bcc22 582LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
a28bcc22 583LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
d4d8de8f 584LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
a28bcc22
JF
585LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
586LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
587LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
6b161b31
JF
588LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
589LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
a28bcc22
JF
590LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
591LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
592LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
c34d9c9f
JF
593LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
594LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
e15ec88e 595LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
660e09ad 596LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
e733ee54
JF
597LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
598LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL)
660e09ad 599
78c70acd 600enum line_type {
2e8488b4
JF
601#define LINE(type, line, fg, bg, attr) \
602 LINE_##type
603 LINE_INFO
604#undef LINE
78c70acd
JF
605};
606
607struct line_info {
660e09ad
JF
608 const char *name; /* Option name. */
609 int namelen; /* Size of option name. */
4685845e 610 const char *line; /* The start of line to match. */
2e8488b4
JF
611 int linelen; /* Size of string to match. */
612 int fg, bg, attr; /* Color and text attributes for the lines. */
78c70acd
JF
613};
614
2e8488b4 615static struct line_info line_info[] = {
78c70acd 616#define LINE(type, line, fg, bg, attr) \
660e09ad 617 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
2e8488b4
JF
618 LINE_INFO
619#undef LINE
78c70acd
JF
620};
621
2e8488b4
JF
622static enum line_type
623get_line_type(char *line)
78c70acd
JF
624{
625 int linelen = strlen(line);
a28bcc22 626 enum line_type type;
78c70acd 627
a28bcc22 628 for (type = 0; type < ARRAY_SIZE(line_info); type++)
2e8488b4 629 /* Case insensitive search matches Signed-off-by lines better. */
a28bcc22
JF
630 if (linelen >= line_info[type].linelen &&
631 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
632 return type;
78c70acd 633
2e8488b4 634 return LINE_DEFAULT;
78c70acd
JF
635}
636
2e8488b4 637static inline int
78c70acd
JF
638get_line_attr(enum line_type type)
639{
2e8488b4
JF
640 assert(type < ARRAY_SIZE(line_info));
641 return COLOR_PAIR(type) | line_info[type].attr;
78c70acd
JF
642}
643
660e09ad
JF
644static struct line_info *
645get_line_info(char *name, int namelen)
646{
647 enum line_type type;
660e09ad
JF
648
649 for (type = 0; type < ARRAY_SIZE(line_info); type++)
650 if (namelen == line_info[type].namelen &&
201f5a18 651 !string_enum_compare(line_info[type].name, name, namelen))
660e09ad
JF
652 return &line_info[type];
653
654 return NULL;
655}
656
78c70acd
JF
657static void
658init_colors(void)
659{
82e78006
JF
660 int default_bg = COLOR_BLACK;
661 int default_fg = COLOR_WHITE;
a28bcc22 662 enum line_type type;
78c70acd
JF
663
664 start_color();
665
666 if (use_default_colors() != ERR) {
82e78006
JF
667 default_bg = -1;
668 default_fg = -1;
78c70acd
JF
669 }
670
a28bcc22
JF
671 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
672 struct line_info *info = &line_info[type];
82e78006
JF
673 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
674 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
78c70acd 675
a28bcc22 676 init_pair(type, fg, bg);
78c70acd
JF
677 }
678}
679
fe7233c3
JF
680struct line {
681 enum line_type type;
3c571d67
JF
682
683 /* State flags */
684 unsigned int selected:1;
685
fe7233c3
JF
686 void *data; /* User data */
687};
688
78c70acd 689
1899507c 690/*
37157fa0
JF
691 * Keys
692 */
693
93a97d86 694struct keybinding {
37157fa0 695 int alias;
93a97d86 696 enum request request;
04e2b7b2 697 struct keybinding *next;
37157fa0
JF
698};
699
93a97d86 700static struct keybinding default_keybindings[] = {
37157fa0
JF
701 /* View switching */
702 { 'm', REQ_VIEW_MAIN },
703 { 'd', REQ_VIEW_DIFF },
704 { 'l', REQ_VIEW_LOG },
e733ee54 705 { 't', REQ_VIEW_TREE },
0001fc34 706 { 'f', REQ_VIEW_BLOB },
37157fa0
JF
707 { 'p', REQ_VIEW_PAGER },
708 { 'h', REQ_VIEW_HELP },
37157fa0
JF
709
710 /* View manipulation */
711 { 'q', REQ_VIEW_CLOSE },
712 { KEY_TAB, REQ_VIEW_NEXT },
713 { KEY_RETURN, REQ_ENTER },
714 { KEY_UP, REQ_PREVIOUS },
715 { KEY_DOWN, REQ_NEXT },
716
717 /* Cursor navigation */
718 { 'k', REQ_MOVE_UP },
719 { 'j', REQ_MOVE_DOWN },
720 { KEY_HOME, REQ_MOVE_FIRST_LINE },
721 { KEY_END, REQ_MOVE_LAST_LINE },
722 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
723 { ' ', REQ_MOVE_PAGE_DOWN },
724 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
725 { 'b', REQ_MOVE_PAGE_UP },
726 { '-', REQ_MOVE_PAGE_UP },
727
728 /* Scrolling */
729 { KEY_IC, REQ_SCROLL_LINE_UP },
730 { KEY_DC, REQ_SCROLL_LINE_DOWN },
731 { 'w', REQ_SCROLL_PAGE_UP },
732 { 's', REQ_SCROLL_PAGE_DOWN },
733
4af34daa
JF
734 /* Searching */
735 { '/', REQ_SEARCH },
736 { '?', REQ_SEARCH_BACK },
737 { 'n', REQ_FIND_NEXT },
738 { 'N', REQ_FIND_PREV },
739
37157fa0
JF
740 /* Misc */
741 { 'Q', REQ_QUIT },
742 { 'z', REQ_STOP_LOADING },
743 { 'v', REQ_SHOW_VERSION },
744 { 'r', REQ_SCREEN_REDRAW },
904e68d8 745 { '.', REQ_TOGGLE_LINENO },
73fb51d5 746 { 'g', REQ_TOGGLE_REV_GRAPH },
37157fa0
JF
747 { ':', REQ_PROMPT },
748
1d754561 749 /* Using the ncurses SIGWINCH handler. */
37157fa0
JF
750 { KEY_RESIZE, REQ_SCREEN_RESIZE },
751};
752
04e2b7b2
JF
753#define KEYMAP_INFO \
754 KEYMAP_(GENERIC), \
755 KEYMAP_(MAIN), \
756 KEYMAP_(DIFF), \
757 KEYMAP_(LOG), \
e733ee54
JF
758 KEYMAP_(TREE), \
759 KEYMAP_(BLOB), \
04e2b7b2
JF
760 KEYMAP_(PAGER), \
761 KEYMAP_(HELP) \
762
763enum keymap {
764#define KEYMAP_(name) KEYMAP_##name
765 KEYMAP_INFO
766#undef KEYMAP_
767};
768
769static struct int_map keymap_table[] = {
770#define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
771 KEYMAP_INFO
772#undef KEYMAP_
773};
774
775#define set_keymap(map, name) \
776 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
777
778static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
779
780static void
781add_keybinding(enum keymap keymap, enum request request, int key)
782{
783 struct keybinding *keybinding;
784
785 keybinding = calloc(1, sizeof(*keybinding));
786 if (!keybinding)
787 die("Failed to allocate keybinding");
788
789 keybinding->alias = key;
790 keybinding->request = request;
791 keybinding->next = keybindings[keymap];
792 keybindings[keymap] = keybinding;
793}
794
795/* Looks for a key binding first in the given map, then in the generic map, and
796 * lastly in the default keybindings. */
37157fa0 797static enum request
04e2b7b2 798get_keybinding(enum keymap keymap, int key)
37157fa0 799{
04e2b7b2 800 struct keybinding *kbd;
37157fa0
JF
801 int i;
802
04e2b7b2
JF
803 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
804 if (kbd->alias == key)
805 return kbd->request;
806
807 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
808 if (kbd->alias == key)
809 return kbd->request;
810
93a97d86
JF
811 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
812 if (default_keybindings[i].alias == key)
813 return default_keybindings[i].request;
37157fa0
JF
814
815 return (enum request) key;
816}
817
93a97d86 818
37157fa0
JF
819struct key {
820 char *name;
821 int value;
822};
823
824static struct key key_table[] = {
825 { "Enter", KEY_RETURN },
826 { "Space", ' ' },
827 { "Backspace", KEY_BACKSPACE },
828 { "Tab", KEY_TAB },
829 { "Escape", KEY_ESC },
830 { "Left", KEY_LEFT },
831 { "Right", KEY_RIGHT },
832 { "Up", KEY_UP },
833 { "Down", KEY_DOWN },
834 { "Insert", KEY_IC },
835 { "Delete", KEY_DC },
74f83ee6 836 { "Hash", '#' },
37157fa0
JF
837 { "Home", KEY_HOME },
838 { "End", KEY_END },
839 { "PageUp", KEY_PPAGE },
840 { "PageDown", KEY_NPAGE },
841 { "F1", KEY_F(1) },
842 { "F2", KEY_F(2) },
843 { "F3", KEY_F(3) },
844 { "F4", KEY_F(4) },
845 { "F5", KEY_F(5) },
846 { "F6", KEY_F(6) },
847 { "F7", KEY_F(7) },
848 { "F8", KEY_F(8) },
849 { "F9", KEY_F(9) },
850 { "F10", KEY_F(10) },
851 { "F11", KEY_F(11) },
852 { "F12", KEY_F(12) },
853};
854
04e2b7b2
JF
855static int
856get_key_value(const char *name)
857{
858 int i;
859
860 for (i = 0; i < ARRAY_SIZE(key_table); i++)
861 if (!strcasecmp(key_table[i].name, name))
862 return key_table[i].value;
863
864 if (strlen(name) == 1 && isprint(*name))
865 return (int) *name;
866
867 return ERR;
868}
869
37157fa0
JF
870static char *
871get_key(enum request request)
872{
873 static char buf[BUFSIZ];
874 static char key_char[] = "'X'";
d65ced0d 875 size_t pos = 0;
520094b4 876 char *sep = "";
37157fa0
JF
877 int i;
878
879 buf[pos] = 0;
880
93a97d86
JF
881 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
882 struct keybinding *keybinding = &default_keybindings[i];
37157fa0
JF
883 char *seq = NULL;
884 int key;
885
93a97d86 886 if (keybinding->request != request)
37157fa0
JF
887 continue;
888
889 for (key = 0; key < ARRAY_SIZE(key_table); key++)
93a97d86 890 if (key_table[key].value == keybinding->alias)
37157fa0
JF
891 seq = key_table[key].name;
892
893 if (seq == NULL &&
93a97d86
JF
894 keybinding->alias < 127 &&
895 isprint(keybinding->alias)) {
896 key_char[1] = (char) keybinding->alias;
37157fa0
JF
897 seq = key_char;
898 }
899
900 if (!seq)
901 seq = "'?'";
902
903 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
904 return "Too many keybindings!";
905 sep = ", ";
906 }
907
908 return buf;
909}
910
911
912/*
1899507c
JF
913 * User config file handling.
914 */
915
5dc795f2
JF
916static struct int_map color_map[] = {
917#define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
918 COLOR_MAP(DEFAULT),
919 COLOR_MAP(BLACK),
920 COLOR_MAP(BLUE),
921 COLOR_MAP(CYAN),
922 COLOR_MAP(GREEN),
923 COLOR_MAP(MAGENTA),
924 COLOR_MAP(RED),
925 COLOR_MAP(WHITE),
926 COLOR_MAP(YELLOW),
927};
928
9256ab05
JF
929#define set_color(color, name) \
930 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
660e09ad 931
5dc795f2
JF
932static struct int_map attr_map[] = {
933#define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
934 ATTR_MAP(NORMAL),
935 ATTR_MAP(BLINK),
936 ATTR_MAP(BOLD),
937 ATTR_MAP(DIM),
938 ATTR_MAP(REVERSE),
939 ATTR_MAP(STANDOUT),
940 ATTR_MAP(UNDERLINE),
941};
942
9256ab05
JF
943#define set_attribute(attr, name) \
944 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
660e09ad 945
3c3801c2
JF
946static int config_lineno;
947static bool config_errors;
948static char *config_msg;
949
5bfd96c7 950/* Wants: object fgcolor bgcolor [attr] */
660e09ad 951static int
5bfd96c7 952option_color_command(int argc, char *argv[])
660e09ad 953{
bca8fcaa
JF
954 struct line_info *info;
955
9256ab05
JF
956 if (argc != 3 && argc != 4) {
957 config_msg = "Wrong number of arguments given to color command";
958 return ERR;
959 }
960
961 info = get_line_info(argv[0], strlen(argv[0]));
bca8fcaa
JF
962 if (!info) {
963 config_msg = "Unknown color name";
964 return ERR;
965 }
660e09ad 966
a3653368
JF
967 if (set_color(&info->fg, argv[1]) == ERR ||
968 set_color(&info->bg, argv[2]) == ERR) {
bca8fcaa
JF
969 config_msg = "Unknown color";
970 return ERR;
971 }
660e09ad 972
9256ab05 973 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
bca8fcaa
JF
974 config_msg = "Unknown attribute";
975 return ERR;
660e09ad
JF
976 }
977
bca8fcaa
JF
978 return OK;
979}
980
5bfd96c7
JF
981/* Wants: name = value */
982static int
983option_set_command(int argc, char *argv[])
984{
985 if (argc != 3) {
986 config_msg = "Wrong number of arguments given to set command";
987 return ERR;
988 }
989
990 if (strcmp(argv[1], "=")) {
991 config_msg = "No value assigned";
992 return ERR;
993 }
994
995 if (!strcmp(argv[0], "show-rev-graph")) {
996 opt_rev_graph = (!strcmp(argv[2], "1") ||
997 !strcmp(argv[2], "true") ||
998 !strcmp(argv[2], "yes"));
999 return OK;
1000 }
1001
1002 if (!strcmp(argv[0], "line-number-interval")) {
1003 opt_num_interval = atoi(argv[2]);
1004 return OK;
1005 }
1006
1007 if (!strcmp(argv[0], "tab-size")) {
1008 opt_tab_size = atoi(argv[2]);
1009 return OK;
1010 }
1011
cb7267ee 1012 if (!strcmp(argv[0], "commit-encoding")) {
3cc9a4d4
JF
1013 char *arg = argv[2];
1014 int delimiter = *arg;
1015 int i;
1016
1017 switch (delimiter) {
1018 case '"':
1019 case '\'':
1020 for (arg++, i = 0; arg[i]; i++)
1021 if (arg[i] == delimiter) {
1022 arg[i] = 0;
1023 break;
1024 }
1025 default:
1026 string_copy(opt_encoding, arg);
1027 return OK;
1028 }
5bfd96c7
JF
1029 }
1030
a3653368 1031 config_msg = "Unknown variable name";
5bfd96c7
JF
1032 return ERR;
1033}
1034
04e2b7b2
JF
1035/* Wants: mode request key */
1036static int
1037option_bind_command(int argc, char *argv[])
1038{
1039 enum request request;
1040 int keymap;
1041 int key;
1042
1043 if (argc != 3) {
1044 config_msg = "Wrong number of arguments given to bind command";
1045 return ERR;
1046 }
1047
1048 if (set_keymap(&keymap, argv[0]) == ERR) {
1049 config_msg = "Unknown key map";
1050 return ERR;
1051 }
1052
1053 key = get_key_value(argv[1]);
1054 if (key == ERR) {
1055 config_msg = "Unknown key";
1056 return ERR;
1057 }
1058
1059 request = get_request(argv[2]);
1060 if (request == REQ_UNKNOWN) {
1061 config_msg = "Unknown request name";
1062 return ERR;
1063 }
1064
1065 add_keybinding(keymap, request, key);
1066
1067 return OK;
1068}
1069
bca8fcaa 1070static int
9256ab05 1071set_option(char *opt, char *value)
bca8fcaa 1072{
9256ab05
JF
1073 char *argv[16];
1074 int valuelen;
1075 int argc = 0;
1076
1077 /* Tokenize */
1078 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1079 argv[argc++] = value;
1080
1081 value += valuelen;
1082 if (!*value)
1083 break;
1084
1085 *value++ = 0;
1086 while (isspace(*value))
1087 value++;
1088 }
1089
1090 if (!strcmp(opt, "color"))
5bfd96c7
JF
1091 return option_color_command(argc, argv);
1092
1093 if (!strcmp(opt, "set"))
1094 return option_set_command(argc, argv);
bca8fcaa 1095
04e2b7b2
JF
1096 if (!strcmp(opt, "bind"))
1097 return option_bind_command(argc, argv);
1098
a3653368 1099 config_msg = "Unknown option command";
660e09ad
JF
1100 return ERR;
1101}
1102
1103static int
3c3801c2
JF
1104read_option(char *opt, int optlen, char *value, int valuelen)
1105{
a3653368
JF
1106 int status = OK;
1107
3c3801c2
JF
1108 config_lineno++;
1109 config_msg = "Internal error";
1110
a3653368
JF
1111 /* Check for comment markers, since read_properties() will
1112 * only ensure opt and value are split at first " \t". */
74f83ee6 1113 optlen = strcspn(opt, "#");
a3653368 1114 if (optlen == 0)
3c3801c2
JF
1115 return OK;
1116
a3653368
JF
1117 if (opt[optlen] != 0) {
1118 config_msg = "No option value";
1119 status = ERR;
1120
1121 } else {
1122 /* Look for comment endings in the value. */
74f83ee6 1123 int len = strcspn(value, "#");
a3653368
JF
1124
1125 if (len < valuelen) {
1126 valuelen = len;
1127 value[valuelen] = 0;
1128 }
1129
1130 status = set_option(opt, value);
3c3801c2
JF
1131 }
1132
a3653368
JF
1133 if (status == ERR) {
1134 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
3c3801c2
JF
1135 config_lineno, optlen, opt, config_msg);
1136 config_errors = TRUE;
1137 }
1138
1139 /* Always keep going if errors are encountered. */
1140 return OK;
1141}
1142
1143static int
660e09ad
JF
1144load_options(void)
1145{
1146 char *home = getenv("HOME");
17482b11 1147 char buf[SIZEOF_STR];
660e09ad
JF
1148 FILE *file;
1149
3c3801c2
JF
1150 config_lineno = 0;
1151 config_errors = FALSE;
1152
cc2d1364 1153 if (!home || !string_format(buf, "%s/.tigrc", home))
660e09ad
JF
1154 return ERR;
1155
1156 /* It's ok that the file doesn't exist. */
1157 file = fopen(buf, "r");
1158 if (!file)
1159 return OK;
1160
3c3801c2
JF
1161 if (read_properties(file, " \t", read_option) == ERR ||
1162 config_errors == TRUE)
1163 fprintf(stderr, "Errors while loading %s.\n", buf);
1164
1165 return OK;
660e09ad
JF
1166}
1167
1168
d839253b 1169/*
468876c9 1170 * The viewer
d839253b 1171 */
c2124ccd
JF
1172
1173struct view;
fe7233c3 1174struct view_ops;
c2124ccd
JF
1175
1176/* The display array of active views and the index of the current view. */
1177static struct view *display[2];
1178static unsigned int current_view;
1179
ab4af23e
JF
1180/* Reading from the prompt? */
1181static bool input_mode = FALSE;
1182
33c4f9ea 1183#define foreach_displayed_view(view, i) \
c2124ccd
JF
1184 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1185
9f41488f 1186#define displayed_views() (display[1] != NULL ? 2 : 1)
c2124ccd 1187
d839253b 1188/* Current head and commit ID */
e733ee54 1189static char ref_blob[SIZEOF_REF] = "";
c2124ccd
JF
1190static char ref_commit[SIZEOF_REF] = "HEAD";
1191static char ref_head[SIZEOF_REF] = "HEAD";
1192
b801d8b2 1193struct view {
03a93dbb 1194 const char *name; /* View name */
4685845e
TH
1195 const char *cmd_fmt; /* Default command line format */
1196 const char *cmd_env; /* Command line set via environment */
e733ee54 1197 const char *id; /* Points to either of ref_{head,commit,blob} */
6b161b31 1198
fe7233c3 1199 struct view_ops *ops; /* View operations */
22f66b0a 1200
04e2b7b2
JF
1201 enum keymap keymap; /* What keymap does this view have */
1202
17482b11 1203 char cmd[SIZEOF_STR]; /* Command buffer */
49f2b43f
JF
1204 char ref[SIZEOF_REF]; /* Hovered commit reference */
1205 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2e8488b4 1206
8855ada4
JF
1207 int height, width; /* The width and height of the main window */
1208 WINDOW *win; /* The main window */
1209 WINDOW *title; /* The title window living below the main window */
b801d8b2
JF
1210
1211 /* Navigation */
1212 unsigned long offset; /* Offset of the window top */
1213 unsigned long lineno; /* Current line number */
1214
4af34daa
JF
1215 /* Searching */
1216 char grep[SIZEOF_STR]; /* Search string */
b77b2cb8 1217 regex_t *regex; /* Pre-compiled regex */
4af34daa 1218
f6da0b66
JF
1219 /* If non-NULL, points to the view that opened this view. If this view
1220 * is closed tig will switch back to the parent view. */
1221 struct view *parent;
1222
b801d8b2
JF
1223 /* Buffering */
1224 unsigned long lines; /* Total number of lines */
fe7233c3 1225 struct line *line; /* Line index */
e2c01617 1226 unsigned long line_size;/* Total number of allocated lines */
8855ada4 1227 unsigned int digits; /* Number of digits in the lines member. */
b801d8b2
JF
1228
1229 /* Loading */
1230 FILE *pipe;
2e8488b4 1231 time_t start_time;
b801d8b2
JF
1232};
1233
fe7233c3
JF
1234struct view_ops {
1235 /* What type of content being displayed. Used in the title bar. */
1236 const char *type;
f098944b
JF
1237 /* Open and reads in all view content. */
1238 bool (*open)(struct view *view);
fe7233c3 1239 /* Read one line; updates view->line. */
701e4f5d 1240 bool (*read)(struct view *view, char *data);
f098944b
JF
1241 /* Draw one line; @lineno must be < view->height. */
1242 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
fe7233c3
JF
1243 /* Depending on view, change display based on current line. */
1244 bool (*enter)(struct view *view, struct line *line);
4af34daa
JF
1245 /* Search for regex in a line. */
1246 bool (*grep)(struct view *view, struct line *line);
d720de4b
JF
1247 /* Select line */
1248 void (*select)(struct view *view, struct line *line);
fe7233c3
JF
1249};
1250
6b161b31
JF
1251static struct view_ops pager_ops;
1252static struct view_ops main_ops;
e733ee54
JF
1253static struct view_ops tree_ops;
1254static struct view_ops blob_ops;
f098944b 1255static struct view_ops help_ops;
a28bcc22 1256
04e2b7b2
JF
1257#define VIEW_STR(name, cmd, env, ref, ops, map) \
1258 { name, cmd, #env, ref, ops, map}
1ba2ae4b 1259
95d7ddcd 1260#define VIEW_(id, name, ops, ref) \
04e2b7b2 1261 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1ba2ae4b 1262
c2124ccd 1263
b801d8b2 1264static struct view views[] = {
95d7ddcd
JF
1265 VIEW_(MAIN, "main", &main_ops, ref_head),
1266 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1267 VIEW_(LOG, "log", &pager_ops, ref_head),
e733ee54
JF
1268 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1269 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
f098944b 1270 VIEW_(HELP, "help", &help_ops, ""),
035ba11f 1271 VIEW_(PAGER, "pager", &pager_ops, ""),
b801d8b2
JF
1272};
1273
a28bcc22
JF
1274#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1275
699ae55b
JF
1276#define foreach_view(view, i) \
1277 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1278
1279#define view_is_displayed(view) \
1280 (view == display[0] || view == display[1])
4c6fabc2 1281
fe7233c3
JF
1282static bool
1283draw_view_line(struct view *view, unsigned int lineno)
1284{
d720de4b 1285 struct line *line;
5dcf8064 1286 bool selected = (view->offset + lineno == view->lineno);
4887d44e 1287 bool draw_ok;
d720de4b 1288
699ae55b
JF
1289 assert(view_is_displayed(view));
1290
fe7233c3
JF
1291 if (view->offset + lineno >= view->lines)
1292 return FALSE;
1293
d720de4b
JF
1294 line = &view->line[view->offset + lineno];
1295
3c571d67
JF
1296 if (selected) {
1297 line->selected = TRUE;
d720de4b 1298 view->ops->select(view, line);
3c571d67
JF
1299 } else if (line->selected) {
1300 line->selected = FALSE;
1301 wmove(view->win, lineno, 0);
1302 wclrtoeol(view->win);
1303 }
d720de4b 1304
4887d44e
JF
1305 scrollok(view->win, FALSE);
1306 draw_ok = view->ops->draw(view, line, lineno, selected);
1307 scrollok(view->win, TRUE);
1308
1309 return draw_ok;
fe7233c3
JF
1310}
1311
b801d8b2 1312static void
82e78006 1313redraw_view_from(struct view *view, int lineno)
b801d8b2 1314{
82e78006 1315 assert(0 <= lineno && lineno < view->height);
b801d8b2 1316
82e78006 1317 for (; lineno < view->height; lineno++) {
fe7233c3 1318 if (!draw_view_line(view, lineno))
fd85fef1 1319 break;
b801d8b2
JF
1320 }
1321
1322 redrawwin(view->win);
ab4af23e
JF
1323 if (input_mode)
1324 wnoutrefresh(view->win);
1325 else
1326 wrefresh(view->win);
b801d8b2
JF
1327}
1328
b76c2afc 1329static void
82e78006
JF
1330redraw_view(struct view *view)
1331{
1332 wclear(view->win);
1333 redraw_view_from(view, 0);
1334}
1335
c2124ccd 1336
6b161b31 1337static void
81030ec8
JF
1338update_view_title(struct view *view)
1339{
3c112a88 1340 char buf[SIZEOF_STR];
71d1c7db
JF
1341 char state[SIZEOF_STR];
1342 size_t bufpos = 0, statelen = 0;
81030ec8 1343
3c112a88 1344 assert(view_is_displayed(view));
81030ec8 1345
c19f8017 1346 if (view->lines || view->pipe) {
6d9c07af 1347 unsigned int view_lines = view->offset + view->height;
c19f8017 1348 unsigned int lines = view->lines
6d9c07af 1349 ? MIN(view_lines, view->lines) * 100 / view->lines
c19f8017
JF
1350 : 0;
1351
71d1c7db 1352 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
3c112a88
JF
1353 view->ops->type,
1354 view->lineno + 1,
1355 view->lines,
1356 lines);
81030ec8 1357
5becf244
JF
1358 if (view->pipe) {
1359 time_t secs = time(NULL) - view->start_time;
f97f4012 1360
5becf244
JF
1361 /* Three git seconds are a long time ... */
1362 if (secs > 2)
71d1c7db 1363 string_format_from(state, &statelen, " %lds", secs);
5becf244 1364 }
81030ec8
JF
1365 }
1366
71d1c7db
JF
1367 string_format_from(buf, &bufpos, "[%s]", view->name);
1368 if (*view->ref && bufpos < view->width) {
1369 size_t refsize = strlen(view->ref);
1370 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1371
1372 if (minsize < view->width)
1373 refsize = view->width - minsize + 7;
1374 string_format_from(buf, &bufpos, " %.*s", refsize, view->ref);
1375 }
f97f4012 1376
71d1c7db
JF
1377 if (statelen && bufpos < view->width) {
1378 string_format_from(buf, &bufpos, " %s", state);
f97f4012
JF
1379 }
1380
3c112a88
JF
1381 if (view == display[current_view])
1382 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1383 else
1384 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1385
3c112a88 1386 mvwaddnstr(view->title, 0, 0, buf, bufpos);
390a8262 1387 wclrtoeol(view->title);
976447f8 1388 wmove(view->title, 0, view->width - 1);
ab4af23e
JF
1389
1390 if (input_mode)
1391 wnoutrefresh(view->title);
1392 else
1393 wrefresh(view->title);
81030ec8
JF
1394}
1395
1396static void
6b161b31 1397resize_display(void)
b76c2afc 1398{
03a93dbb 1399 int offset, i;
6b161b31
JF
1400 struct view *base = display[0];
1401 struct view *view = display[1] ? display[1] : display[0];
b76c2afc 1402
6b161b31 1403 /* Setup window dimensions */
b76c2afc 1404
03a93dbb 1405 getmaxyx(stdscr, base->height, base->width);
b76c2afc 1406
6b161b31 1407 /* Make room for the status window. */
03a93dbb 1408 base->height -= 1;
6b161b31
JF
1409
1410 if (view != base) {
03a93dbb
JF
1411 /* Horizontal split. */
1412 view->width = base->width;
6b161b31
JF
1413 view->height = SCALE_SPLIT_VIEW(base->height);
1414 base->height -= view->height;
1415
1416 /* Make room for the title bar. */
1417 view->height -= 1;
1418 }
1419
1420 /* Make room for the title bar. */
1421 base->height -= 1;
1422
1423 offset = 0;
1424
33c4f9ea 1425 foreach_displayed_view (view, i) {
b76c2afc 1426 if (!view->win) {
c19f8017 1427 view->win = newwin(view->height, 0, offset, 0);
6b161b31
JF
1428 if (!view->win)
1429 die("Failed to create %s view", view->name);
1430
1431 scrollok(view->win, TRUE);
1432
1433 view->title = newwin(1, 0, offset + view->height, 0);
1434 if (!view->title)
1435 die("Failed to create title window");
1436
1437 } else {
c19f8017 1438 wresize(view->win, view->height, view->width);
6b161b31
JF
1439 mvwin(view->win, offset, 0);
1440 mvwin(view->title, offset + view->height, 0);
a28bcc22 1441 }
a28bcc22 1442
6b161b31 1443 offset += view->height + 1;
b76c2afc 1444 }
6b161b31 1445}
b76c2afc 1446
6b161b31 1447static void
20bb5e18
JF
1448redraw_display(void)
1449{
1450 struct view *view;
1451 int i;
1452
33c4f9ea 1453 foreach_displayed_view (view, i) {
20bb5e18
JF
1454 redraw_view(view);
1455 update_view_title(view);
1456 }
1457}
1458
85af6284 1459static void
2bee3bde 1460update_display_cursor(struct view *view)
85af6284 1461{
85af6284
JF
1462 /* Move the cursor to the right-most column of the cursor line.
1463 *
1464 * XXX: This could turn out to be a bit expensive, but it ensures that
1465 * the cursor does not jump around. */
1466 if (view->lines) {
1467 wmove(view->win, view->lineno - view->offset, view->width - 1);
1468 wrefresh(view->win);
1469 }
1470}
20bb5e18 1471
2e8488b4
JF
1472/*
1473 * Navigation
1474 */
1475
4a2909a7 1476/* Scrolling backend */
b801d8b2 1477static void
8c317212 1478do_scroll_view(struct view *view, int lines)
b801d8b2 1479{
a0087dd5
JF
1480 bool redraw_current_line = FALSE;
1481
fd85fef1
JF
1482 /* The rendering expects the new offset. */
1483 view->offset += lines;
1484
1485 assert(0 <= view->offset && view->offset < view->lines);
1486 assert(lines);
b801d8b2 1487
a0087dd5
JF
1488 /* Move current line into the view. */
1489 if (view->lineno < view->offset) {
1490 view->lineno = view->offset;
1491 redraw_current_line = TRUE;
1492 } else if (view->lineno >= view->offset + view->height) {
1493 view->lineno = view->offset + view->height - 1;
1494 redraw_current_line = TRUE;
1495 }
1496
1497 assert(view->offset <= view->lineno && view->lineno < view->lines);
1498
82e78006 1499 /* Redraw the whole screen if scrolling is pointless. */
4c6fabc2 1500 if (view->height < ABS(lines)) {
b76c2afc
JF
1501 redraw_view(view);
1502
1503 } else {
22f66b0a 1504 int line = lines > 0 ? view->height - lines : 0;
82e78006 1505 int end = line + ABS(lines);
fd85fef1
JF
1506
1507 wscrl(view->win, lines);
1508
22f66b0a 1509 for (; line < end; line++) {
fe7233c3 1510 if (!draw_view_line(view, line))
fd85fef1
JF
1511 break;
1512 }
fd85fef1 1513
a0087dd5
JF
1514 if (redraw_current_line)
1515 draw_view_line(view, view->lineno - view->offset);
fd85fef1
JF
1516 }
1517
fd85fef1
JF
1518 redrawwin(view->win);
1519 wrefresh(view->win);
9d3f5834 1520 report("");
fd85fef1 1521}
78c70acd 1522
4a2909a7 1523/* Scroll frontend */
fd85fef1 1524static void
6b161b31 1525scroll_view(struct view *view, enum request request)
fd85fef1
JF
1526{
1527 int lines = 1;
b801d8b2 1528
8c317212
JF
1529 assert(view_is_displayed(view));
1530
b801d8b2 1531 switch (request) {
4a2909a7 1532 case REQ_SCROLL_PAGE_DOWN:
fd85fef1 1533 lines = view->height;
4a2909a7 1534 case REQ_SCROLL_LINE_DOWN:
b801d8b2 1535 if (view->offset + lines > view->lines)
bde3653a 1536 lines = view->lines - view->offset;
b801d8b2 1537
fd85fef1 1538 if (lines == 0 || view->offset + view->height >= view->lines) {
eb98559e 1539 report("Cannot scroll beyond the last line");
b801d8b2
JF
1540 return;
1541 }
1542 break;
1543
4a2909a7 1544 case REQ_SCROLL_PAGE_UP:
fd85fef1 1545 lines = view->height;
4a2909a7 1546 case REQ_SCROLL_LINE_UP:
b801d8b2
JF
1547 if (lines > view->offset)
1548 lines = view->offset;
1549
1550 if (lines == 0) {
eb98559e 1551 report("Cannot scroll beyond the first line");
b801d8b2
JF
1552 return;
1553 }
1554
fd85fef1 1555 lines = -lines;
b801d8b2 1556 break;
03a93dbb 1557
6b161b31
JF
1558 default:
1559 die("request %d not handled in switch", request);
b801d8b2
JF
1560 }
1561
8c317212 1562 do_scroll_view(view, lines);
fd85fef1 1563}
b801d8b2 1564
4a2909a7 1565/* Cursor moving */
fd85fef1 1566static void
8522ecc7 1567move_view(struct view *view, enum request request)
fd85fef1 1568{
dfaa6c81 1569 int scroll_steps = 0;
fd85fef1 1570 int steps;
b801d8b2 1571
fd85fef1 1572 switch (request) {
4a2909a7 1573 case REQ_MOVE_FIRST_LINE:
78c70acd
JF
1574 steps = -view->lineno;
1575 break;
1576
4a2909a7 1577 case REQ_MOVE_LAST_LINE:
78c70acd
JF
1578 steps = view->lines - view->lineno - 1;
1579 break;
1580
4a2909a7 1581 case REQ_MOVE_PAGE_UP:
78c70acd
JF
1582 steps = view->height > view->lineno
1583 ? -view->lineno : -view->height;
1584 break;
1585
4a2909a7 1586 case REQ_MOVE_PAGE_DOWN:
78c70acd
JF
1587 steps = view->lineno + view->height >= view->lines
1588 ? view->lines - view->lineno - 1 : view->height;
1589 break;
1590
4a2909a7 1591 case REQ_MOVE_UP:
fd85fef1
JF
1592 steps = -1;
1593 break;
b801d8b2 1594
4a2909a7 1595 case REQ_MOVE_DOWN:
fd85fef1
JF
1596 steps = 1;
1597 break;
6b161b31
JF
1598
1599 default:
1600 die("request %d not handled in switch", request);
78c70acd 1601 }
b801d8b2 1602
4c6fabc2 1603 if (steps <= 0 && view->lineno == 0) {
eb98559e 1604 report("Cannot move beyond the first line");
78c70acd 1605 return;
b801d8b2 1606
6908bdbd 1607 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
eb98559e 1608 report("Cannot move beyond the last line");
78c70acd 1609 return;
fd85fef1
JF
1610 }
1611
4c6fabc2 1612 /* Move the current line */
fd85fef1 1613 view->lineno += steps;
4c6fabc2
JF
1614 assert(0 <= view->lineno && view->lineno < view->lines);
1615
4c6fabc2 1616 /* Check whether the view needs to be scrolled */
fd85fef1
JF
1617 if (view->lineno < view->offset ||
1618 view->lineno >= view->offset + view->height) {
dfaa6c81 1619 scroll_steps = steps;
fd85fef1 1620 if (steps < 0 && -steps > view->offset) {
dfaa6c81 1621 scroll_steps = -view->offset;
b76c2afc
JF
1622
1623 } else if (steps > 0) {
1624 if (view->lineno == view->lines - 1 &&
1625 view->lines > view->height) {
dfaa6c81
JF
1626 scroll_steps = view->lines - view->offset - 1;
1627 if (scroll_steps >= view->height)
1628 scroll_steps -= view->height - 1;
b76c2afc 1629 }
b801d8b2 1630 }
8522ecc7
JF
1631 }
1632
1633 if (!view_is_displayed(view)) {
a3965365
JF
1634 view->offset += scroll_steps;
1635 assert(0 <= view->offset && view->offset < view->lines);
8522ecc7
JF
1636 view->ops->select(view, &view->line[view->lineno]);
1637 return;
1638 }
1639
1640 /* Repaint the old "current" line if we be scrolling */
1641 if (ABS(steps) < view->height)
1642 draw_view_line(view, view->lineno - steps - view->offset);
1643
dfaa6c81
JF
1644 if (scroll_steps) {
1645 do_scroll_view(view, scroll_steps);
fd85fef1 1646 return;
b801d8b2
JF
1647 }
1648
4c6fabc2 1649 /* Draw the current line */
fe7233c3 1650 draw_view_line(view, view->lineno - view->offset);
fd85fef1 1651
b801d8b2
JF
1652 redrawwin(view->win);
1653 wrefresh(view->win);
9d3f5834 1654 report("");
b801d8b2
JF
1655}
1656
b801d8b2 1657
2e8488b4 1658/*
4af34daa
JF
1659 * Searching
1660 */
1661
c02d8fce 1662static void search_view(struct view *view, enum request request);
4af34daa
JF
1663
1664static bool
1665find_next_line(struct view *view, unsigned long lineno, struct line *line)
1666{
699ae55b
JF
1667 assert(view_is_displayed(view));
1668
4af34daa
JF
1669 if (!view->ops->grep(view, line))
1670 return FALSE;
1671
1672 if (lineno - view->offset >= view->height) {
1673 view->offset = lineno;
1674 view->lineno = lineno;
1675 redraw_view(view);
1676
1677 } else {
1678 unsigned long old_lineno = view->lineno - view->offset;
1679
1680 view->lineno = lineno;
4af34daa
JF
1681 draw_view_line(view, old_lineno);
1682
1683 draw_view_line(view, view->lineno - view->offset);
1684 redrawwin(view->win);
1685 wrefresh(view->win);
1686 }
1687
1688 report("Line %ld matches '%s'", lineno + 1, view->grep);
1689 return TRUE;
1690}
1691
1692static void
1693find_next(struct view *view, enum request request)
1694{
1695 unsigned long lineno = view->lineno;
1696 int direction;
1697
1698 if (!*view->grep) {
1699 if (!*opt_search)
1700 report("No previous search");
1701 else
c02d8fce 1702 search_view(view, request);
4af34daa
JF
1703 return;
1704 }
1705
1706 switch (request) {
1707 case REQ_SEARCH:
1708 case REQ_FIND_NEXT:
1709 direction = 1;
1710 break;
1711
1712 case REQ_SEARCH_BACK:
1713 case REQ_FIND_PREV:
1714 direction = -1;
1715 break;
1716
1717 default:
1718 return;
1719 }
1720
1721 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1722 lineno += direction;
1723
1724 /* Note, lineno is unsigned long so will wrap around in which case it
1725 * will become bigger than view->lines. */
1726 for (; lineno < view->lines; lineno += direction) {
1727 struct line *line = &view->line[lineno];
1728
1729 if (find_next_line(view, lineno, line))
1730 return;
1731 }
1732
1733 report("No match found for '%s'", view->grep);
1734}
1735
1736static void
c02d8fce 1737search_view(struct view *view, enum request request)
4af34daa
JF
1738{
1739 int regex_err;
1740
b77b2cb8
JF
1741 if (view->regex) {
1742 regfree(view->regex);
4af34daa 1743 *view->grep = 0;
b77b2cb8
JF
1744 } else {
1745 view->regex = calloc(1, sizeof(*view->regex));
1746 if (!view->regex)
1747 return;
4af34daa
JF
1748 }
1749
c02d8fce 1750 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
4af34daa
JF
1751 if (regex_err != 0) {
1752 char buf[SIZEOF_STR] = "unknown error";
1753
b77b2cb8 1754 regerror(regex_err, view->regex, buf, sizeof(buf));
e9cacd58 1755 report("Search failed: %s", buf);
4af34daa
JF
1756 return;
1757 }
1758
c02d8fce 1759 string_copy(view->grep, opt_search);
4af34daa
JF
1760
1761 find_next(view, request);
1762}
1763
1764/*
2e8488b4
JF
1765 * Incremental updating
1766 */
b801d8b2 1767
199d1288
JF
1768static void
1769end_update(struct view *view)
1770{
1771 if (!view->pipe)
1772 return;
1773 set_nonblocking_input(FALSE);
1774 if (view->pipe == stdin)
1775 fclose(view->pipe);
1776 else
1777 pclose(view->pipe);
1778 view->pipe = NULL;
1779}
1780
03a93dbb 1781static bool
b801d8b2
JF
1782begin_update(struct view *view)
1783{
4685845e 1784 const char *id = view->id;
fd85fef1 1785
199d1288
JF
1786 if (view->pipe)
1787 end_update(view);
1788
03a93dbb
JF
1789 if (opt_cmd[0]) {
1790 string_copy(view->cmd, opt_cmd);
1791 opt_cmd[0] = 0;
035ba11f
JF
1792 /* When running random commands, initially show the
1793 * command in the title. However, it maybe later be
1794 * overwritten if a commit line is selected. */
1795 string_copy(view->ref, view->cmd);
e733ee54
JF
1796
1797 } else if (view == VIEW(REQ_VIEW_TREE)) {
1798 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
f0f114ac 1799 char path[SIZEOF_STR];
e733ee54
JF
1800
1801 if (strcmp(view->vid, view->id))
f0f114ac
JF
1802 opt_path[0] = path[0] = 0;
1803 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1804 return FALSE;
e733ee54 1805
f0f114ac 1806 if (!string_format(view->cmd, format, id, path))
e733ee54
JF
1807 return FALSE;
1808
03a93dbb 1809 } else {
4685845e 1810 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1ba2ae4b 1811
cc2d1364 1812 if (!string_format(view->cmd, format, id, id, id, id, id))
03a93dbb 1813 return FALSE;
035ba11f
JF
1814
1815 /* Put the current ref_* value to the view title ref
1816 * member. This is needed by the blob view. Most other
1817 * views sets it automatically after loading because the
1818 * first line is a commit line. */
1819 string_copy(view->ref, id);
03a93dbb 1820 }
b801d8b2 1821
6908bdbd
JF
1822 /* Special case for the pager view. */
1823 if (opt_pipe) {
1824 view->pipe = opt_pipe;
1825 opt_pipe = NULL;
1826 } else {
1827 view->pipe = popen(view->cmd, "r");
1828 }
1829
2e8488b4
JF
1830 if (!view->pipe)
1831 return FALSE;
b801d8b2 1832
6b161b31 1833 set_nonblocking_input(TRUE);
b801d8b2
JF
1834
1835 view->offset = 0;
1836 view->lines = 0;
1837 view->lineno = 0;
2463b4ea 1838 string_copy_rev(view->vid, id);
b801d8b2 1839
2e8488b4
JF
1840 if (view->line) {
1841 int i;
1842
1843 for (i = 0; i < view->lines; i++)
fe7233c3
JF
1844 if (view->line[i].data)
1845 free(view->line[i].data);
2e8488b4
JF
1846
1847 free(view->line);
1848 view->line = NULL;
1849 }
1850
1851 view->start_time = time(NULL);
1852
b801d8b2
JF
1853 return TRUE;
1854}
1855
e2c01617
JF
1856static struct line *
1857realloc_lines(struct view *view, size_t line_size)
1858{
1859 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1860
1861 if (!tmp)
1862 return NULL;
1863
1864 view->line = tmp;
1865 view->line_size = line_size;
1866 return view->line;
1867}
1868
03a93dbb 1869static bool
b801d8b2
JF
1870update_view(struct view *view)
1871{
6b68fd24
JF
1872 char in_buffer[BUFSIZ];
1873 char out_buffer[BUFSIZ * 2];
b801d8b2 1874 char *line;
82e78006
JF
1875 /* The number of lines to read. If too low it will cause too much
1876 * redrawing (and possible flickering), if too high responsiveness
1877 * will suffer. */
8855ada4 1878 unsigned long lines = view->height;
82e78006 1879 int redraw_from = -1;
b801d8b2
JF
1880
1881 if (!view->pipe)
1882 return TRUE;
1883
82e78006
JF
1884 /* Only redraw if lines are visible. */
1885 if (view->offset + view->height >= view->lines)
1886 redraw_from = view->lines - view->offset;
b801d8b2 1887
699ae55b 1888 /* FIXME: This is probably not perfect for backgrounded views. */
e2c01617 1889 if (!realloc_lines(view, view->lines + lines))
b801d8b2
JF
1890 goto alloc_error;
1891
6b68fd24
JF
1892 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1893 size_t linelen = strlen(line);
b801d8b2 1894
b801d8b2
JF
1895 if (linelen)
1896 line[linelen - 1] = 0;
1897
6b68fd24
JF
1898 if (opt_iconv != ICONV_NONE) {
1899 char *inbuf = line;
1900 size_t inlen = linelen;
1901
1902 char *outbuf = out_buffer;
1903 size_t outlen = sizeof(out_buffer);
1904
1905 size_t ret;
1906
7361622d 1907 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
6b68fd24
JF
1908 if (ret != (size_t) -1) {
1909 line = out_buffer;
1910 linelen = strlen(out_buffer);
1911 }
1912 }
1913
701e4f5d 1914 if (!view->ops->read(view, line))
b801d8b2 1915 goto alloc_error;
fd85fef1
JF
1916
1917 if (lines-- == 1)
1918 break;
b801d8b2
JF
1919 }
1920
8855ada4
JF
1921 {
1922 int digits;
1923
1924 lines = view->lines;
1925 for (digits = 0; lines; digits++)
1926 lines /= 10;
1927
1928 /* Keep the displayed view in sync with line number scaling. */
1929 if (digits != view->digits) {
1930 view->digits = digits;
1931 redraw_from = 0;
1932 }
1933 }
1934
699ae55b
JF
1935 if (!view_is_displayed(view))
1936 goto check_pipe;
1937
e733ee54
JF
1938 if (view == VIEW(REQ_VIEW_TREE)) {
1939 /* Clear the view and redraw everything since the tree sorting
1940 * might have rearranged things. */
1941 redraw_view(view);
1942
1943 } else if (redraw_from >= 0) {
82e78006 1944 /* If this is an incremental update, redraw the previous line
a28bcc22
JF
1945 * since for commits some members could have changed when
1946 * loading the main view. */
82e78006
JF
1947 if (redraw_from > 0)
1948 redraw_from--;
1949
9eded379
JF
1950 /* Since revision graph visualization requires knowledge
1951 * about the parent commit, it causes a further one-off
1952 * needed to be redrawn for incremental updates. */
1953 if (redraw_from > 0 && opt_rev_graph)
1954 redraw_from--;
1955
82e78006
JF
1956 /* Incrementally draw avoids flickering. */
1957 redraw_view_from(view, redraw_from);
4c6fabc2 1958 }
b801d8b2 1959
eb98559e
JF
1960 /* Update the title _after_ the redraw so that if the redraw picks up a
1961 * commit reference in view->ref it'll be available here. */
1962 update_view_title(view);
1963
699ae55b 1964check_pipe:
b801d8b2 1965 if (ferror(view->pipe)) {
03a93dbb 1966 report("Failed to read: %s", strerror(errno));
b801d8b2
JF
1967 goto end;
1968
1969 } else if (feof(view->pipe)) {
f97f4012 1970 report("");
b801d8b2
JF
1971 goto end;
1972 }
1973
1974 return TRUE;
1975
1976alloc_error:
2e8488b4 1977 report("Allocation failure");
b801d8b2
JF
1978
1979end:
be04d936 1980 view->ops->read(view, NULL);
b801d8b2
JF
1981 end_update(view);
1982 return FALSE;
1983}
1984
0a0d8910 1985static struct line *
e314c36d 1986add_line_data(struct view *view, void *data, enum line_type type)
0a0d8910 1987{
e314c36d 1988 struct line *line = &view->line[view->lines++];
0a0d8910 1989
e314c36d 1990 memset(line, 0, sizeof(*line));
0a0d8910 1991 line->type = type;
e314c36d 1992 line->data = data;
0a0d8910
JF
1993
1994 return line;
1995}
1996
e314c36d
JF
1997static struct line *
1998add_line_text(struct view *view, char *data, enum line_type type)
1999{
2000 if (data)
2001 data = strdup(data);
2002
2003 return data ? add_line_data(view, data, type) : NULL;
2004}
2005
79d445ca 2006
e10154d5
JF
2007/*
2008 * View opening
2009 */
2010
49f2b43f
JF
2011enum open_flags {
2012 OPEN_DEFAULT = 0, /* Use default view switching. */
2013 OPEN_SPLIT = 1, /* Split current view. */
2014 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2015 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2016};
2017
6b161b31 2018static void
49f2b43f 2019open_view(struct view *prev, enum request request, enum open_flags flags)
b801d8b2 2020{
49f2b43f
JF
2021 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2022 bool split = !!(flags & OPEN_SPLIT);
2023 bool reload = !!(flags & OPEN_RELOAD);
a28bcc22 2024 struct view *view = VIEW(request);
9f41488f 2025 int nviews = displayed_views();
6e950a52 2026 struct view *base_view = display[0];
b801d8b2 2027
49f2b43f 2028 if (view == prev && nviews == 1 && !reload) {
6b161b31
JF
2029 report("Already in %s view", view->name);
2030 return;
2031 }
b801d8b2 2032
f098944b
JF
2033 if (view->ops->open) {
2034 if (!view->ops->open(view)) {
2035 report("Failed to load %s view", view->name);
2036 return;
2037 }
2509b112
JF
2038
2039 } else if ((reload || strcmp(view->vid, view->id)) &&
2040 !begin_update(view)) {
6b161b31
JF
2041 report("Failed to load %s view", view->name);
2042 return;
2043 }
a28bcc22 2044
6b161b31 2045 if (split) {
8d741c06 2046 display[1] = view;
6b161b31 2047 if (!backgrounded)
8d741c06 2048 current_view = 1;
6b161b31
JF
2049 } else {
2050 /* Maximize the current view. */
2051 memset(display, 0, sizeof(display));
2052 current_view = 0;
2053 display[current_view] = view;
a28bcc22 2054 }
b801d8b2 2055
6e950a52
JF
2056 /* Resize the view when switching between split- and full-screen,
2057 * or when switching between two different full-screen views. */
2058 if (nviews != displayed_views() ||
2059 (nviews == 1 && base_view != display[0]))
a006db63 2060 resize_display();
b801d8b2 2061
a8891802 2062 if (split && prev->lineno - prev->offset >= prev->height) {
03a93dbb 2063 /* Take the title line into account. */
eb98559e 2064 int lines = prev->lineno - prev->offset - prev->height + 1;
03a93dbb
JF
2065
2066 /* Scroll the view that was split if the current line is
2067 * outside the new limited view. */
8c317212 2068 do_scroll_view(prev, lines);
03a93dbb
JF
2069 }
2070
6b161b31 2071 if (prev && view != prev) {
9b995f0c 2072 if (split && !backgrounded) {
f0b3ab80
JF
2073 /* "Blur" the previous view. */
2074 update_view_title(prev);
9f396969 2075 }
f0b3ab80 2076
f6da0b66 2077 view->parent = prev;
b801d8b2
JF
2078 }
2079
9f396969 2080 if (view->pipe && view->lines == 0) {
03a93dbb
JF
2081 /* Clear the old view and let the incremental updating refill
2082 * the screen. */
2083 wclear(view->win);
f97f4012 2084 report("");
03a93dbb
JF
2085 } else {
2086 redraw_view(view);
24b5b3e0 2087 report("");
03a93dbb 2088 }
6706b2ba
JF
2089
2090 /* If the view is backgrounded the above calls to report()
2091 * won't redraw the view title. */
2092 if (backgrounded)
2093 update_view_title(view);
b801d8b2
JF
2094}
2095
2096
6b161b31
JF
2097/*
2098 * User request switch noodle
2099 */
2100
b801d8b2 2101static int
6b161b31 2102view_driver(struct view *view, enum request request)
b801d8b2 2103{
b801d8b2
JF
2104 int i;
2105
2106 switch (request) {
4a2909a7
JF
2107 case REQ_MOVE_UP:
2108 case REQ_MOVE_DOWN:
2109 case REQ_MOVE_PAGE_UP:
2110 case REQ_MOVE_PAGE_DOWN:
2111 case REQ_MOVE_FIRST_LINE:
2112 case REQ_MOVE_LAST_LINE:
8522ecc7 2113 move_view(view, request);
fd85fef1
JF
2114 break;
2115
4a2909a7
JF
2116 case REQ_SCROLL_LINE_DOWN:
2117 case REQ_SCROLL_LINE_UP:
2118 case REQ_SCROLL_PAGE_DOWN:
2119 case REQ_SCROLL_PAGE_UP:
a28bcc22 2120 scroll_view(view, request);
b801d8b2
JF
2121 break;
2122
e733ee54
JF
2123 case REQ_VIEW_BLOB:
2124 if (!ref_blob[0]) {
550cd4b5
JF
2125 report("No file chosen, press %s to open tree view",
2126 get_key(REQ_VIEW_TREE));
e733ee54
JF
2127 break;
2128 }
5c4358d1
JF
2129 open_view(view, request, OPEN_DEFAULT);
2130 break;
2131
2132 case REQ_VIEW_PAGER:
2133 if (!VIEW(REQ_VIEW_PAGER)->lines) {
2134 report("No pager content, press %s to run command from prompt",
2135 get_key(REQ_PROMPT));
2136 break;
2137 }
2138 open_view(view, request, OPEN_DEFAULT);
2139 break;
2140
4a2909a7 2141 case REQ_VIEW_MAIN:
4a2909a7 2142 case REQ_VIEW_DIFF:
2e8488b4 2143 case REQ_VIEW_LOG:
e733ee54 2144 case REQ_VIEW_TREE:
2e8488b4 2145 case REQ_VIEW_HELP:
49f2b43f 2146 open_view(view, request, OPEN_DEFAULT);
b801d8b2
JF
2147 break;
2148
b3a54cba
JF
2149 case REQ_NEXT:
2150 case REQ_PREVIOUS:
2151 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2152
e733ee54
JF
2153 if ((view == VIEW(REQ_VIEW_DIFF) &&
2154 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2155 (view == VIEW(REQ_VIEW_BLOB) &&
2156 view->parent == VIEW(REQ_VIEW_TREE))) {
b3a54cba 2157 view = view->parent;
8522ecc7
JF
2158 move_view(view, request);
2159 if (view_is_displayed(view))
f0b3ab80 2160 update_view_title(view);
b3a54cba 2161 } else {
8522ecc7 2162 move_view(view, request);
b3a54cba
JF
2163 break;
2164 }
6706b2ba
JF
2165 /* Fall-through */
2166
6b161b31 2167 case REQ_ENTER:
6908bdbd
JF
2168 if (!view->lines) {
2169 report("Nothing to enter");
2170 break;
2171 }
fe7233c3 2172 return view->ops->enter(view, &view->line[view->lineno]);
6b161b31 2173
03a93dbb
JF
2174 case REQ_VIEW_NEXT:
2175 {
9f41488f 2176 int nviews = displayed_views();
03a93dbb
JF
2177 int next_view = (current_view + 1) % nviews;
2178
2179 if (next_view == current_view) {
2180 report("Only one view is displayed");
2181 break;
2182 }
2183
2184 current_view = next_view;
2185 /* Blur out the title of the previous view. */
2186 update_view_title(view);
6734f6b9 2187 report("");
03a93dbb
JF
2188 break;
2189 }
24b5b3e0 2190 case REQ_TOGGLE_LINENO:
b76c2afc 2191 opt_line_number = !opt_line_number;
20bb5e18 2192 redraw_display();
b801d8b2
JF
2193 break;
2194
54efb62b
JF
2195 case REQ_TOGGLE_REV_GRAPH:
2196 opt_rev_graph = !opt_rev_graph;
2197 redraw_display();
2198 break;
2199
03a93dbb 2200 case REQ_PROMPT:
8855ada4 2201 /* Always reload^Wrerun commands from the prompt. */
49f2b43f 2202 open_view(view, opt_request, OPEN_RELOAD);
03a93dbb
JF
2203 break;
2204
4af34daa
JF
2205 case REQ_SEARCH:
2206 case REQ_SEARCH_BACK:
c02d8fce 2207 search_view(view, request);
4af34daa
JF
2208 break;
2209
2210 case REQ_FIND_NEXT:
2211 case REQ_FIND_PREV:
2212 find_next(view, request);
2213 break;
2214
4a2909a7 2215 case REQ_STOP_LOADING:
59a45d3a
JF
2216 for (i = 0; i < ARRAY_SIZE(views); i++) {
2217 view = &views[i];
2e8488b4 2218 if (view->pipe)
6a7bb912 2219 report("Stopped loading the %s view", view->name),
03a93dbb
JF
2220 end_update(view);
2221 }
b801d8b2
JF
2222 break;
2223
4a2909a7 2224 case REQ_SHOW_VERSION:
6cb291b7 2225 report("%s (built %s)", VERSION, __DATE__);
b801d8b2
JF
2226 return TRUE;
2227
fac7db6c
JF
2228 case REQ_SCREEN_RESIZE:
2229 resize_display();
2230 /* Fall-through */
4a2909a7 2231 case REQ_SCREEN_REDRAW:
20bb5e18 2232 redraw_display();
4a2909a7
JF
2233 break;
2234
1d754561 2235 case REQ_NONE:
b801d8b2
JF
2236 doupdate();
2237 return TRUE;
2238
4f9b667a 2239 case REQ_VIEW_CLOSE:
2fcf5401
JF
2240 /* XXX: Mark closed views by letting view->parent point to the
2241 * view itself. Parents to closed view should never be
2242 * followed. */
2243 if (view->parent &&
2244 view->parent->parent != view->parent) {
4f9b667a
JF
2245 memset(display, 0, sizeof(display));
2246 current_view = 0;
f6da0b66 2247 display[current_view] = view->parent;
2fcf5401 2248 view->parent = view;
4f9b667a
JF
2249 resize_display();
2250 redraw_display();
2251 break;
2252 }
2253 /* Fall-through */
b801d8b2
JF
2254 case REQ_QUIT:
2255 return FALSE;
2256
2257 default:
2e8488b4 2258 /* An unknown key will show most commonly used commands. */
468876c9 2259 report("Unknown key, press 'h' for help");
b801d8b2
JF
2260 return TRUE;
2261 }
2262
2263 return TRUE;
2264}
2265
2266
2267/*
ff26aa29 2268 * Pager backend
b801d8b2
JF
2269 */
2270
6b161b31 2271static bool
5dcf8064 2272pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
b801d8b2 2273{
fe7233c3
JF
2274 char *text = line->data;
2275 enum line_type type = line->type;
2276 int textlen = strlen(text);
78c70acd 2277 int attr;
b801d8b2 2278
6706b2ba
JF
2279 wmove(view->win, lineno, 0);
2280
5dcf8064 2281 if (selected) {
78c70acd 2282 type = LINE_CURSOR;
6706b2ba 2283 wchgat(view->win, -1, 0, type, NULL);
fd85fef1
JF
2284 }
2285
78c70acd 2286 attr = get_line_attr(type);
b801d8b2 2287 wattrset(view->win, attr);
b76c2afc 2288
6706b2ba
JF
2289 if (opt_line_number || opt_tab_size < TABSIZE) {
2290 static char spaces[] = " ";
2291 int col_offset = 0, col = 0;
2292
2293 if (opt_line_number) {
2294 unsigned long real_lineno = view->offset + lineno + 1;
82e78006 2295
6706b2ba
JF
2296 if (real_lineno == 1 ||
2297 (real_lineno % opt_num_interval) == 0) {
2298 wprintw(view->win, "%.*d", view->digits, real_lineno);
8855ada4 2299
6706b2ba
JF
2300 } else {
2301 waddnstr(view->win, spaces,
2302 MIN(view->digits, STRING_SIZE(spaces)));
2303 }
2304 waddstr(view->win, ": ");
2305 col_offset = view->digits + 2;
2306 }
8855ada4 2307
fe7233c3 2308 while (text && col_offset + col < view->width) {
6706b2ba 2309 int cols_max = view->width - col_offset - col;
fe7233c3 2310 char *pos = text;
6706b2ba 2311 int cols;
4c6fabc2 2312
fe7233c3
JF
2313 if (*text == '\t') {
2314 text++;
6706b2ba 2315 assert(sizeof(spaces) > TABSIZE);
fe7233c3 2316 pos = spaces;
6706b2ba 2317 cols = opt_tab_size - (col % opt_tab_size);
82e78006 2318
b76c2afc 2319 } else {
fe7233c3
JF
2320 text = strchr(text, '\t');
2321 cols = line ? text - pos : strlen(pos);
b76c2afc 2322 }
6706b2ba 2323
fe7233c3 2324 waddnstr(view->win, pos, MIN(cols, cols_max));
6706b2ba 2325 col += cols;
b76c2afc 2326 }
b76c2afc
JF
2327
2328 } else {
6706b2ba 2329 int col = 0, pos = 0;
b801d8b2 2330
fe7233c3
JF
2331 for (; pos < textlen && col < view->width; pos++, col++)
2332 if (text[pos] == '\t')
6706b2ba
JF
2333 col += TABSIZE - (col % TABSIZE) - 1;
2334
fe7233c3 2335 waddnstr(view->win, text, pos);
6706b2ba 2336 }
2e8488b4 2337
b801d8b2
JF
2338 return TRUE;
2339}
2340
dc23c0e3 2341static bool
d65ced0d 2342add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
dc23c0e3 2343{
17482b11 2344 char refbuf[SIZEOF_STR];
dc23c0e3
JF
2345 char *ref = NULL;
2346 FILE *pipe;
2347
d3c345f7 2348 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
dc23c0e3
JF
2349 return TRUE;
2350
2351 pipe = popen(refbuf, "r");
2352 if (!pipe)
2353 return TRUE;
2354
2355 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2356 ref = chomp_string(ref);
2357 pclose(pipe);
2358
2359 if (!ref || !*ref)
2360 return TRUE;
2361
2362 /* This is the only fatal call, since it can "corrupt" the buffer. */
17482b11 2363 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
dc23c0e3
JF
2364 return FALSE;
2365
2366 return TRUE;
2367}
2368
7b99a34c
JF
2369static void
2370add_pager_refs(struct view *view, struct line *line)
2371{
17482b11 2372 char buf[SIZEOF_STR];
c9ca1ec3 2373 char *commit_id = line->data + STRING_SIZE("commit ");
7b99a34c 2374 struct ref **refs;
d65ced0d 2375 size_t bufpos = 0, refpos = 0;
7b99a34c 2376 const char *sep = "Refs: ";
dc23c0e3 2377 bool is_tag = FALSE;
7b99a34c
JF
2378
2379 assert(line->type == LINE_COMMIT);
2380
c9ca1ec3 2381 refs = get_refs(commit_id);
dc23c0e3
JF
2382 if (!refs) {
2383 if (view == VIEW(REQ_VIEW_DIFF))
2384 goto try_add_describe_ref;
7b99a34c 2385 return;
dc23c0e3 2386 }
7b99a34c
JF
2387
2388 do {
cc2d1364 2389 struct ref *ref = refs[refpos];
e15ec88e
JF
2390 char *fmt = ref->tag ? "%s[%s]" :
2391 ref->remote ? "%s<%s>" : "%s%s";
7b99a34c 2392
cc2d1364
JF
2393 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2394 return;
7b99a34c 2395 sep = ", ";
dc23c0e3
JF
2396 if (ref->tag)
2397 is_tag = TRUE;
7b99a34c
JF
2398 } while (refs[refpos++]->next);
2399
dc23c0e3
JF
2400 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2401try_add_describe_ref:
d42c8a35 2402 /* Add <tag>-g<commit_id> "fake" reference. */
dc23c0e3
JF
2403 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2404 return;
2405 }
2406
d42c8a35
JF
2407 if (bufpos == 0)
2408 return;
2409
cc2d1364 2410 if (!realloc_lines(view, view->line_size + 1))
7b99a34c
JF
2411 return;
2412
0a0d8910 2413 add_line_text(view, buf, LINE_PP_REFS);
7b99a34c
JF
2414}
2415
6b161b31 2416static bool
701e4f5d 2417pager_read(struct view *view, char *data)
22f66b0a 2418{
0a0d8910 2419 struct line *line;
22f66b0a 2420
be04d936
JF
2421 if (!data)
2422 return TRUE;
2423
0a0d8910
JF
2424 line = add_line_text(view, data, get_line_type(data));
2425 if (!line)
7b99a34c 2426 return FALSE;
fe7233c3 2427
7b99a34c
JF
2428 if (line->type == LINE_COMMIT &&
2429 (view == VIEW(REQ_VIEW_DIFF) ||
2430 view == VIEW(REQ_VIEW_LOG)))
2431 add_pager_refs(view, line);
2432
22f66b0a
JF
2433 return TRUE;
2434}
2435
6b161b31 2436static bool
fe7233c3 2437pager_enter(struct view *view, struct line *line)
6b161b31 2438{
91e8e277 2439 int split = 0;
6b161b31 2440
9fbbd28f
JF
2441 if (line->type == LINE_COMMIT &&
2442 (view == VIEW(REQ_VIEW_LOG) ||
2443 view == VIEW(REQ_VIEW_PAGER))) {
91e8e277
JF
2444 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2445 split = 1;
67e48ac5
JF
2446 }
2447
91e8e277
JF
2448 /* Always scroll the view even if it was split. That way
2449 * you can use Enter to scroll through the log view and
2450 * split open each commit diff. */
2451 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2452
2453 /* FIXME: A minor workaround. Scrolling the view will call report("")
9d82d824
JF
2454 * but if we are scrolling a non-current view this won't properly
2455 * update the view title. */
91e8e277
JF
2456 if (split)
2457 update_view_title(view);
6b161b31
JF
2458
2459 return TRUE;
2460}
2461
4af34daa
JF
2462static bool
2463pager_grep(struct view *view, struct line *line)
2464{
2465 regmatch_t pmatch;
2466 char *text = line->data;
2467
2468 if (!*text)
2469 return FALSE;
2470
b77b2cb8 2471 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
4af34daa
JF
2472 return FALSE;
2473
2474 return TRUE;
2475}
2476
d720de4b
JF
2477static void
2478pager_select(struct view *view, struct line *line)
2479{
2480 if (line->type == LINE_COMMIT) {
035ba11f 2481 char *text = line->data + STRING_SIZE("commit ");
d720de4b 2482
035ba11f 2483 if (view != VIEW(REQ_VIEW_PAGER))
2463b4ea
JF
2484 string_copy_rev(view->ref, text);
2485 string_copy_rev(ref_commit, text);
d720de4b
JF
2486 }
2487}
2488
6b161b31 2489static struct view_ops pager_ops = {
6734f6b9 2490 "line",
f098944b 2491 NULL,
6b161b31 2492 pager_read,
f098944b
JF
2493 pager_draw,
2494 pager_enter,
2495 pager_grep,
2496 pager_select,
2497};
2498
2499
2500/*
2501 * Help backend
2502 */
2503
2504static bool
2505help_open(struct view *view)
2506{
2507 char buf[BUFSIZ];
2508 int lines = ARRAY_SIZE(req_info) + 2;
2509 int i;
2510
2511 if (view->lines > 0)
2512 return TRUE;
2513
2514 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2515 if (!req_info[i].request)
2516 lines++;
2517
2518 view->line = calloc(lines, sizeof(*view->line));
2519 if (!view->line)
2520 return FALSE;
2521
2522 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2523
2524 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2525 char *key;
2526
2527 if (!req_info[i].request) {
2528 add_line_text(view, "", LINE_DEFAULT);
2529 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2530 continue;
2531 }
2532
2533 key = get_key(req_info[i].request);
2534 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2535 continue;
2536
2537 add_line_text(view, buf, LINE_DEFAULT);
2538 }
2539
2540 return TRUE;
2541}
2542
2543static struct view_ops help_ops = {
2544 "line",
2545 help_open,
2546 NULL,
2547 pager_draw,
6b161b31 2548 pager_enter,
4af34daa 2549 pager_grep,
d720de4b 2550 pager_select,
6b161b31
JF
2551};
2552
80ce96ea 2553
ff26aa29 2554/*
e733ee54
JF
2555 * Tree backend
2556 */
2557
4795d620 2558/* Parse output from git-ls-tree(1):
e733ee54
JF
2559 *
2560 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2561 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2562 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2563 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2564 */
2565
2566#define SIZEOF_TREE_ATTR \
2567 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2568
2569#define TREE_UP_FORMAT "040000 tree %s\t.."
2570
2571static int
2572tree_compare_entry(enum line_type type1, char *name1,
2573 enum line_type type2, char *name2)
2574{
2575 if (type1 != type2) {
2576 if (type1 == LINE_TREE_DIR)
2577 return -1;
2578 return 1;
2579 }
2580
2581 return strcmp(name1, name2);
2582}
2583
2584static bool
2585tree_read(struct view *view, char *text)
2586{
be04d936 2587 size_t textlen = text ? strlen(text) : 0;
e733ee54
JF
2588 char buf[SIZEOF_STR];
2589 unsigned long pos;
2590 enum line_type type;
f88a5319 2591 bool first_read = view->lines == 0;
e733ee54
JF
2592
2593 if (textlen <= SIZEOF_TREE_ATTR)
2594 return FALSE;
2595
2596 type = text[STRING_SIZE("100644 ")] == 't'
2597 ? LINE_TREE_DIR : LINE_TREE_FILE;
2598
f88a5319 2599 if (first_read) {
e733ee54 2600 /* Add path info line */
0a0d8910
JF
2601 if (!string_format(buf, "Directory path /%s", opt_path) ||
2602 !realloc_lines(view, view->line_size + 1) ||
2603 !add_line_text(view, buf, LINE_DEFAULT))
e733ee54
JF
2604 return FALSE;
2605
2606 /* Insert "link" to parent directory. */
0a0d8910
JF
2607 if (*opt_path) {
2608 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2609 !realloc_lines(view, view->line_size + 1) ||
2610 !add_line_text(view, buf, LINE_TREE_DIR))
2611 return FALSE;
2612 }
e733ee54
JF
2613 }
2614
2615 /* Strip the path part ... */
2616 if (*opt_path) {
2617 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2618 size_t striplen = strlen(opt_path);
2619 char *path = text + SIZEOF_TREE_ATTR;
2620
2621 if (pathlen > striplen)
2622 memmove(path, path + striplen,
2623 pathlen - striplen + 1);
2624 }
2625
2626 /* Skip "Directory ..." and ".." line. */
2627 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2628 struct line *line = &view->line[pos];
2629 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2630 char *path2 = text + SIZEOF_TREE_ATTR;
2631 int cmp = tree_compare_entry(line->type, path1, type, path2);
2632
2633 if (cmp <= 0)
2634 continue;
2635
2636 text = strdup(text);
2637 if (!text)
2638 return FALSE;
2639
2640 if (view->lines > pos)
2641 memmove(&view->line[pos + 1], &view->line[pos],
2642 (view->lines - pos) * sizeof(*line));
2643
2644 line = &view->line[pos];
2645 line->data = text;
2646 line->type = type;
2647 view->lines++;
2648 return TRUE;
2649 }
2650
0a0d8910 2651 if (!add_line_text(view, text, type))
e733ee54
JF
2652 return FALSE;
2653
f88a5319
JF
2654 /* Move the current line to the first tree entry. */
2655 if (first_read)
2656 view->lineno++;
2657
e733ee54
JF
2658 return TRUE;
2659}
2660
2661static bool
2662tree_enter(struct view *view, struct line *line)
2663{
aac64c17 2664 enum open_flags flags;
e733ee54
JF
2665 enum request request;
2666
2667 switch (line->type) {
2668 case LINE_TREE_DIR:
2669 /* Depending on whether it is a subdir or parent (updir?) link
2670 * mangle the path buffer. */
2671 if (line == &view->line[1] && *opt_path) {
2672 size_t path_len = strlen(opt_path);
2673 char *dirsep = opt_path + path_len - 1;
2674
2675 while (dirsep > opt_path && dirsep[-1] != '/')
2676 dirsep--;
2677
2678 dirsep[0] = 0;
2679
2680 } else {
d65ced0d 2681 size_t pathlen = strlen(opt_path);
4ea4ce91 2682 size_t origlen = pathlen;
4795d620 2683 char *data = line->data;
e733ee54
JF
2684 char *basename = data + SIZEOF_TREE_ATTR;
2685
4038038f 2686 if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
4ea4ce91
JF
2687 opt_path[origlen] = 0;
2688 return TRUE;
2689 }
e733ee54
JF
2690 }
2691
2692 /* Trees and subtrees share the same ID, so they are not not
2693 * unique like blobs. */
aac64c17 2694 flags = OPEN_RELOAD;
e733ee54
JF
2695 request = REQ_VIEW_TREE;
2696 break;
2697
2698 case LINE_TREE_FILE:
aac64c17 2699 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
e733ee54
JF
2700 request = REQ_VIEW_BLOB;
2701 break;
2702
2703 default:
2704 return TRUE;
2705 }
2706
2707 open_view(view, request, flags);
2708
e733ee54
JF
2709 return TRUE;
2710}
2711
d720de4b
JF
2712static void
2713tree_select(struct view *view, struct line *line)
2714{
035ba11f 2715 char *text = line->data + STRING_SIZE("100644 blob ");
73c76ef5
JF
2716
2717 if (line->type == LINE_TREE_FILE) {
2463b4ea 2718 string_copy_rev(ref_blob, text);
d720de4b 2719
ebbaf4fe
JF
2720 } else if (line->type != LINE_TREE_DIR) {
2721 return;
d720de4b 2722 }
ebbaf4fe 2723
2463b4ea 2724 string_copy_rev(view->ref, text);
d720de4b
JF
2725}
2726
e733ee54
JF
2727static struct view_ops tree_ops = {
2728 "file",
f098944b 2729 NULL,
e733ee54 2730 tree_read,
f098944b 2731 pager_draw,
e733ee54
JF
2732 tree_enter,
2733 pager_grep,
d720de4b 2734 tree_select,
e733ee54
JF
2735};
2736
2737static bool
2738blob_read(struct view *view, char *line)
2739{
0a0d8910 2740 return add_line_text(view, line, LINE_DEFAULT);
e733ee54
JF
2741}
2742
2743static struct view_ops blob_ops = {
2744 "line",
f098944b 2745 NULL,
e733ee54 2746 blob_read,
f098944b 2747 pager_draw,
e733ee54
JF
2748 pager_enter,
2749 pager_grep,
d720de4b 2750 pager_select,
e733ee54
JF
2751};
2752
2753
2754/*
ccc33449 2755 * Revision graph
ff26aa29
JF
2756 */
2757
2758struct commit {
10446330 2759 char id[SIZEOF_REV]; /* SHA1 ID. */
aea510c8 2760 char title[128]; /* First line of the commit message. */
54efb62b
JF
2761 char author[75]; /* Author of the commit. */
2762 struct tm time; /* Date from the author ident. */
2763 struct ref **refs; /* Repository references. */
2764 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
2765 size_t graph_size; /* The width of the graph array. */
ff26aa29 2766};
c34d9c9f 2767
ccc33449
JF
2768/* Size of rev graph with no "padding" columns */
2769#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
2b757533 2770
2ce5c87c
JF
2771struct rev_graph {
2772 struct rev_graph *prev, *next, *parents;
2b757533
JF
2773 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
2774 size_t size;
88757ebd
JF
2775 struct commit *commit;
2776 size_t pos;
2b757533
JF
2777};
2778
2b757533 2779/* Parents of the commit being visualized. */
446a5c36 2780static struct rev_graph graph_parents[4];
c8d60a25 2781
c65a501a 2782/* The current stack of revisions on the graph. */
446a5c36
JF
2783static struct rev_graph graph_stacks[4] = {
2784 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
c65a501a 2785 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
446a5c36
JF
2786 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
2787 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
c65a501a
JF
2788};
2789
9e43b9cd 2790static inline bool
2ce5c87c 2791graph_parent_is_merge(struct rev_graph *graph)
9e43b9cd
JF
2792{
2793 return graph->parents->size > 1;
2794}
2795
88757ebd 2796static inline void
2ce5c87c 2797append_to_rev_graph(struct rev_graph *graph, chtype symbol)
88757ebd 2798{
2c27faac
JF
2799 struct commit *commit = graph->commit;
2800
2801 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
2802 commit->graph[commit->graph_size++] = symbol;
88757ebd
JF
2803}
2804
2b757533 2805static void
2ce5c87c 2806done_rev_graph(struct rev_graph *graph)
987890af
JF
2807{
2808 if (graph_parent_is_merge(graph) &&
2809 graph->pos < graph->size - 1 &&
2810 graph->next->size == graph->size + graph->parents->size - 1) {
2811 size_t i = graph->pos + graph->parents->size - 1;
2812
2813 graph->commit->graph_size = i * 2;
2814 while (i < graph->next->size - 1) {
2815 append_to_rev_graph(graph, ' ');
2816 append_to_rev_graph(graph, '\\');
2817 i++;
2818 }
2819 }
2820
2821 graph->size = graph->pos = 0;
2822 graph->commit = NULL;
2823 memset(graph->parents, 0, sizeof(*graph->parents));
2824}
2825
2826static void
2ce5c87c 2827push_rev_graph(struct rev_graph *graph, char *parent)
2b757533 2828{
2fe894e6
JF
2829 int i;
2830
2831 /* "Collapse" duplicate parents lines.
2832 *
2833 * FIXME: This needs to also update update the drawn graph but
2834 * for now it just serves as a method for pruning graph lines. */
2835 for (i = 0; i < graph->size; i++)
2836 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
2837 return;
2b757533 2838
2ce5c87c
JF
2839 if (graph->size < SIZEOF_REVITEMS) {
2840 string_ncopy(graph->rev[graph->size++], parent, SIZEOF_REV);
2b757533
JF
2841 }
2842}
2843
92507a24
JF
2844static chtype
2845get_rev_graph_symbol(struct rev_graph *graph)
2b757533 2846{
92507a24 2847 chtype symbol;
2b757533 2848
c65a501a 2849 if (graph->parents->size == 0)
c8d60a25 2850 symbol = REVGRAPH_INIT;
18ffaa23 2851 else if (graph_parent_is_merge(graph))
c8d60a25 2852 symbol = REVGRAPH_MERGE;
c65a501a 2853 else if (graph->pos >= graph->size)
c8d60a25 2854 symbol = REVGRAPH_BRANCH;
2b757533 2855 else
c8d60a25 2856 symbol = REVGRAPH_COMMIT;
1dcb3bec 2857
92507a24
JF
2858 return symbol;
2859}
2860
2861static void
2862draw_rev_graph(struct rev_graph *graph)
2863{
e937c2c8
JF
2864 struct rev_filler {
2865 chtype separator, line;
2866 };
2867 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
2868 static struct rev_filler fillers[] = {
2869 { ' ', REVGRAPH_LINE },
2870 { '`', '.' },
2871 { '\'', ' ' },
2872 { '/', ' ' },
e937c2c8 2873 };
92507a24 2874 chtype symbol = get_rev_graph_symbol(graph);
e937c2c8 2875 struct rev_filler *filler;
92507a24
JF
2876 size_t i;
2877
e937c2c8 2878 filler = &fillers[DEFAULT];
110e948e 2879
c65a501a 2880 for (i = 0; i < graph->pos; i++) {
e937c2c8 2881 append_to_rev_graph(graph, filler->line);
9e43b9cd 2882 if (graph_parent_is_merge(graph->prev) &&
e937c2c8
JF
2883 graph->prev->pos == i)
2884 filler = &fillers[RSHARP];
2885
2886 append_to_rev_graph(graph, filler->separator);
110e948e
JF
2887 }
2888
92507a24 2889 /* Place the symbol for this revision. */
c65a501a 2890 append_to_rev_graph(graph, symbol);
2b757533 2891
e937c2c8
JF
2892 if (graph->prev->size > graph->size)
2893 filler = &fillers[RDIAG];
2894 else
2895 filler = &fillers[DEFAULT];
2896
c8d60a25 2897 i++;
2b757533 2898
c65a501a 2899 for (; i < graph->size; i++) {
e937c2c8
JF
2900 append_to_rev_graph(graph, filler->separator);
2901 append_to_rev_graph(graph, filler->line);
2902 if (graph_parent_is_merge(graph->prev) &&
2903 i < graph->prev->pos + graph->parents->size)
2904 filler = &fillers[RSHARP];
2905 if (graph->prev->size > graph->size)
2906 filler = &fillers[LDIAG];
c65a501a
JF
2907 }
2908
2909 if (graph->prev->size > graph->size) {
e937c2c8
JF
2910 append_to_rev_graph(graph, filler->separator);
2911 if (filler->line != ' ')
2912 append_to_rev_graph(graph, filler->line);
2b757533 2913 }
b5d8f208
JF
2914}
2915
61eed810
JF
2916/* Prepare the next rev graph */
2917static void
2918prepare_rev_graph(struct rev_graph *graph)
b5d8f208 2919{
b5d8f208
JF
2920 size_t i;
2921
320df4ea 2922 /* First, traverse all lines of revisions up to the active one. */
c65a501a
JF
2923 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
2924 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
b5d8f208 2925 break;
b5d8f208 2926
2ce5c87c 2927 push_rev_graph(graph->next, graph->rev[graph->pos]);
b5d8f208
JF
2928 }
2929
320df4ea 2930 /* Interleave the new revision parent(s). */
c65a501a 2931 for (i = 0; i < graph->parents->size; i++)
2ce5c87c 2932 push_rev_graph(graph->next, graph->parents->rev[i]);
b5d8f208 2933
320df4ea 2934 /* Lastly, put any remaining revisions. */
c65a501a 2935 for (i = graph->pos + 1; i < graph->size; i++)
2ce5c87c 2936 push_rev_graph(graph->next, graph->rev[i]);
61eed810
JF
2937}
2938
2939static void
2940update_rev_graph(struct rev_graph *graph)
2941{
446a5c36
JF
2942 /* If this is the finalizing update ... */
2943 if (graph->commit)
2944 prepare_rev_graph(graph);
2945
2946 /* Graph visualization needs a one rev look-ahead,
2947 * so the first update doesn't visualize anything. */
2948 if (!graph->prev->commit)
2949 return;
c65a501a 2950
61eed810
JF
2951 draw_rev_graph(graph->prev);
2952 done_rev_graph(graph->prev->prev);
2b757533
JF
2953}
2954
ccc33449
JF
2955
2956/*
2957 * Main view backend
2958 */
2959
2960static bool
2961main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2962{
2963 char buf[DATE_COLS + 1];
2964 struct commit *commit = line->data;
2965 enum line_type type;
2966 int col = 0;
2967 size_t timelen;
2968 size_t authorlen;
2969 int trimmed = 1;
2970
2971 if (!*commit->author)
2972 return FALSE;
2973
2974 wmove(view->win, lineno, col);
2975
2976 if (selected) {
2977 type = LINE_CURSOR;
2978 wattrset(view->win, get_line_attr(type));
2979 wchgat(view->win, -1, 0, type, NULL);
2980
2981 } else {
2982 type = LINE_MAIN_COMMIT;
2983 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2984 }
2985
2986 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2987 waddnstr(view->win, buf, timelen);
2988 waddstr(view->win, " ");
2989
2990 col += DATE_COLS;
2991 wmove(view->win, lineno, col);
2992 if (type != LINE_CURSOR)
2993 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2994
2995 if (opt_utf8) {
2996 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2997 } else {
2998 authorlen = strlen(commit->author);
2999 if (authorlen > AUTHOR_COLS - 2) {
3000 authorlen = AUTHOR_COLS - 2;
3001 trimmed = 1;
3002 }
3003 }
3004
3005 if (trimmed) {
3006 waddnstr(view->win, commit->author, authorlen);
3007 if (type != LINE_CURSOR)
3008 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3009 waddch(view->win, '~');
3010 } else {
3011 waddstr(view->win, commit->author);
3012 }
3013
3014 col += AUTHOR_COLS;
3015 if (type != LINE_CURSOR)
3016 wattrset(view->win, A_NORMAL);
3017
3018 if (opt_rev_graph && commit->graph_size) {
3019 size_t i;
3020
3021 wmove(view->win, lineno, col);
3022 /* Using waddch() instead of waddnstr() ensures that
3023 * they'll be rendered correctly for the cursor line. */
3024 for (i = 0; i < commit->graph_size; i++)
3025 waddch(view->win, commit->graph[i]);
3026
8716b9ed 3027 waddch(view->win, ' ');
ccc33449
JF
3028 col += commit->graph_size + 1;
3029 }
3030
3031 wmove(view->win, lineno, col);
3032
3033 if (commit->refs) {
3034 size_t i = 0;
3035
3036 do {
3037 if (type == LINE_CURSOR)
3038 ;
3039 else if (commit->refs[i]->tag)
3040 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
e15ec88e
JF
3041 else if (commit->refs[i]->remote)
3042 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
ccc33449
JF
3043 else
3044 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3045 waddstr(view->win, "[");
3046 waddstr(view->win, commit->refs[i]->name);
3047 waddstr(view->win, "]");
3048 if (type != LINE_CURSOR)
3049 wattrset(view->win, A_NORMAL);
3050 waddstr(view->win, " ");
3051 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3052 } while (commit->refs[i++]->next);
3053 }
3054
3055 if (type != LINE_CURSOR)
3056 wattrset(view->win, get_line_attr(type));
3057
3058 {
3059 int titlelen = strlen(commit->title);
3060
3061 if (col + titlelen > view->width)
3062 titlelen = view->width - col;
3063
3064 waddnstr(view->win, commit->title, titlelen);
3065 }
3066
3067 return TRUE;
3068}
3069
4c6fabc2 3070/* Reads git log --pretty=raw output and parses it into the commit struct. */
6b161b31 3071static bool
701e4f5d 3072main_read(struct view *view, char *line)
22f66b0a 3073{
2ce5c87c 3074 static struct rev_graph *graph = graph_stacks;
be04d936 3075 enum line_type type;
0ff3b97c 3076 struct commit *commit;
22f66b0a 3077
be04d936 3078 if (!line) {
446a5c36 3079 update_rev_graph(graph);
be04d936
JF
3080 return TRUE;
3081 }
3082
3083 type = get_line_type(line);
0ff3b97c 3084 if (type == LINE_COMMIT) {
22f66b0a
JF
3085 commit = calloc(1, sizeof(struct commit));
3086 if (!commit)
3087 return FALSE;
3088
0ff3b97c 3089 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
c34d9c9f 3090 commit->refs = get_refs(commit->id);
c65a501a 3091 graph->commit = commit;
e314c36d 3092 add_line_data(view, commit, LINE_MAIN_COMMIT);
0ff3b97c
JF
3093 return TRUE;
3094 }
2b757533 3095
0ff3b97c
JF
3096 if (!view->lines)
3097 return TRUE;
3098 commit = view->line[view->lines - 1].data;
3099
3100 switch (type) {
2b757533 3101 case LINE_PARENT:
0ff3b97c 3102 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
78c70acd 3103 break;
22f66b0a 3104
8855ada4 3105 case LINE_AUTHOR:
b76c2afc 3106 {
19c3ac60
JF
3107 /* Parse author lines where the name may be empty:
3108 * author <email@address.tld> 1138474660 +0100
3109 */
4c6fabc2 3110 char *ident = line + STRING_SIZE("author ");
19c3ac60
JF
3111 char *nameend = strchr(ident, '<');
3112 char *emailend = strchr(ident, '>');
b76c2afc 3113
0ff3b97c 3114 if (!nameend || !emailend)
fe7233c3
JF
3115 break;
3116
c65a501a
JF
3117 update_rev_graph(graph);
3118 graph = graph->next;
2b757533 3119
19c3ac60
JF
3120 *nameend = *emailend = 0;
3121 ident = chomp_string(ident);
3122 if (!*ident) {
3123 ident = chomp_string(nameend + 1);
3124 if (!*ident)
3125 ident = "Unknown";
b76c2afc
JF
3126 }
3127
82e78006 3128 string_copy(commit->author, ident);
b76c2afc 3129
4c6fabc2 3130 /* Parse epoch and timezone */
19c3ac60
JF
3131 if (emailend[1] == ' ') {
3132 char *secs = emailend + 2;
3133 char *zone = strchr(secs, ' ');
3134 time_t time = (time_t) atol(secs);
b76c2afc 3135
4c6fabc2 3136 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
b76c2afc
JF
3137 long tz;
3138
3139 zone++;
3140 tz = ('0' - zone[1]) * 60 * 60 * 10;
3141 tz += ('0' - zone[2]) * 60 * 60;
3142 tz += ('0' - zone[3]) * 60;
3143 tz += ('0' - zone[4]) * 60;
3144
3145 if (zone[0] == '-')
3146 tz = -tz;
3147
3148 time -= tz;
3149 }
19c3ac60 3150
b76c2afc
JF
3151 gmtime_r(&time, &commit->time);
3152 }
3153 break;
3154 }
78c70acd 3155 default:
2e8488b4 3156 /* Fill in the commit title if it has not already been set. */
2e8488b4
JF
3157 if (commit->title[0])
3158 break;
3159
3160 /* Require titles to start with a non-space character at the
3161 * offset used by git log. */
9073c64a
JF
3162 if (strncmp(line, " ", 4))
3163 break;
3164 line += 4;
3165 /* Well, if the title starts with a whitespace character,
3166 * try to be forgiving. Otherwise we end up with no title. */
3167 while (isspace(*line))
3168 line++;
3169 if (*line == '\0')
82e78006 3170 break;
9073c64a
JF
3171 /* FIXME: More graceful handling of titles; append "..." to
3172 * shortened titles, etc. */
82e78006 3173
9073c64a 3174 string_copy(commit->title, line);
22f66b0a
JF
3175 }
3176
3177 return TRUE;
3178}
3179
6b161b31 3180static bool
fe7233c3 3181main_enter(struct view *view, struct line *line)
b801d8b2 3182{
b3a54cba
JF
3183 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3184
3185 open_view(view, REQ_VIEW_DIFF, flags);
6b161b31 3186 return TRUE;
b801d8b2
JF
3187}
3188
4af34daa
JF
3189static bool
3190main_grep(struct view *view, struct line *line)
3191{
3192 struct commit *commit = line->data;
3193 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3194 char buf[DATE_COLS + 1];
3195 regmatch_t pmatch;
3196
3197 for (state = S_TITLE; state < S_END; state++) {
3198 char *text;
3199
3200 switch (state) {
3201 case S_TITLE: text = commit->title; break;
3202 case S_AUTHOR: text = commit->author; break;
3203 case S_DATE:
3204 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3205 continue;
3206 text = buf;
3207 break;
3208
3209 default:
3210 return FALSE;
3211 }
3212
b77b2cb8 3213 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4af34daa
JF
3214 return TRUE;
3215 }
3216
3217 return FALSE;
3218}
3219
d720de4b
JF
3220static void
3221main_select(struct view *view, struct line *line)
3222{
3223 struct commit *commit = line->data;
3224
2463b4ea
JF
3225 string_copy_rev(view->ref, commit->id);
3226 string_copy_rev(ref_commit, view->ref);
d720de4b
JF
3227}
3228
6b161b31 3229static struct view_ops main_ops = {
6734f6b9 3230 "commit",
f098944b 3231 NULL,
6b161b31 3232 main_read,
f098944b 3233 main_draw,
6b161b31 3234 main_enter,
4af34daa 3235 main_grep,
d720de4b 3236 main_select,
6b161b31 3237};
2e8488b4 3238
c34d9c9f 3239
6b161b31 3240/*
10e290ee
JF
3241 * Unicode / UTF-8 handling
3242 *
3243 * NOTE: Much of the following code for dealing with unicode is derived from
3244 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3245 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3246 */
3247
3248/* I've (over)annotated a lot of code snippets because I am not entirely
3249 * confident that the approach taken by this small UTF-8 interface is correct.
3250 * --jonas */
3251
3252static inline int
3253unicode_width(unsigned long c)
3254{
3255 if (c >= 0x1100 &&
3256 (c <= 0x115f /* Hangul Jamo */
3257 || c == 0x2329
3258 || c == 0x232a
3259 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
f97f4012 3260 /* CJK ... Yi */
10e290ee
JF
3261 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
3262 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
3263 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
3264 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
3265 || (c >= 0xffe0 && c <= 0xffe6)
3266 || (c >= 0x20000 && c <= 0x2fffd)
3267 || (c >= 0x30000 && c <= 0x3fffd)))
3268 return 2;
3269
3270 return 1;
3271}
3272
3273/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3274 * Illegal bytes are set one. */
3275static const unsigned char utf8_bytes[256] = {
3276 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,
3277 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,
3278 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,
3279 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,
3280 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,
3281 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,
3282 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,
3283 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,
3284};
3285
3286/* Decode UTF-8 multi-byte representation into a unicode character. */
3287static inline unsigned long
3288utf8_to_unicode(const char *string, size_t length)
3289{
3290 unsigned long unicode;
3291
3292 switch (length) {
3293 case 1:
3294 unicode = string[0];
3295 break;
3296 case 2:
3297 unicode = (string[0] & 0x1f) << 6;
3298 unicode += (string[1] & 0x3f);
3299 break;
3300 case 3:
3301 unicode = (string[0] & 0x0f) << 12;
3302 unicode += ((string[1] & 0x3f) << 6);
3303 unicode += (string[2] & 0x3f);
3304 break;
3305 case 4:
3306 unicode = (string[0] & 0x0f) << 18;
3307 unicode += ((string[1] & 0x3f) << 12);
3308 unicode += ((string[2] & 0x3f) << 6);
3309 unicode += (string[3] & 0x3f);
3310 break;
3311 case 5:
3312 unicode = (string[0] & 0x0f) << 24;
3313 unicode += ((string[1] & 0x3f) << 18);
3314 unicode += ((string[2] & 0x3f) << 12);
3315 unicode += ((string[3] & 0x3f) << 6);
3316 unicode += (string[4] & 0x3f);
3317 break;
68b6e0eb 3318 case 6:
10e290ee
JF
3319 unicode = (string[0] & 0x01) << 30;
3320 unicode += ((string[1] & 0x3f) << 24);
3321 unicode += ((string[2] & 0x3f) << 18);
3322 unicode += ((string[3] & 0x3f) << 12);
3323 unicode += ((string[4] & 0x3f) << 6);
3324 unicode += (string[5] & 0x3f);
3325 break;
3326 default:
3327 die("Invalid unicode length");
3328 }
3329
3330 /* Invalid characters could return the special 0xfffd value but NUL
3331 * should be just as good. */
3332 return unicode > 0xffff ? 0 : unicode;
3333}
3334
3335/* Calculates how much of string can be shown within the given maximum width
3336 * and sets trimmed parameter to non-zero value if all of string could not be
3337 * shown.
3338 *
3339 * Additionally, adds to coloffset how many many columns to move to align with
3340 * the expected position. Takes into account how multi-byte and double-width
3341 * characters will effect the cursor position.
3342 *
3343 * Returns the number of bytes to output from string to satisfy max_width. */
3344static size_t
3345utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3346{
3347 const char *start = string;
3348 const char *end = strchr(string, '\0');
3349 size_t mbwidth = 0;
3350 size_t width = 0;
3351
3352 *trimmed = 0;
3353
3354 while (string < end) {
3355 int c = *(unsigned char *) string;
3356 unsigned char bytes = utf8_bytes[c];
3357 size_t ucwidth;
3358 unsigned long unicode;
3359
3360 if (string + bytes > end)
3361 break;
3362
3363 /* Change representation to figure out whether
3364 * it is a single- or double-width character. */
3365
3366 unicode = utf8_to_unicode(string, bytes);
3367 /* FIXME: Graceful handling of invalid unicode character. */
3368 if (!unicode)
3369 break;
3370
3371 ucwidth = unicode_width(unicode);
3372 width += ucwidth;
3373 if (width > max_width) {
3374 *trimmed = 1;
3375 break;
3376 }
3377
3378 /* The column offset collects the differences between the
3379 * number of bytes encoding a character and the number of
3380 * columns will be used for rendering said character.
3381 *
3382 * So if some character A is encoded in 2 bytes, but will be
3383 * represented on the screen using only 1 byte this will and up
3384 * adding 1 to the multi-byte column offset.
3385 *
3386 * Assumes that no double-width character can be encoding in
3387 * less than two bytes. */
3388 if (bytes > ucwidth)
3389 mbwidth += bytes - ucwidth;
3390
3391 string += bytes;
3392 }
3393
3394 *coloffset += mbwidth;
3395
3396 return string - start;
3397}
3398
3399
3400/*
6b161b31
JF
3401 * Status management
3402 */
2e8488b4 3403
8855ada4 3404/* Whether or not the curses interface has been initialized. */
68b6e0eb 3405static bool cursed = FALSE;
8855ada4 3406
6b161b31
JF
3407/* The status window is used for polling keystrokes. */
3408static WINDOW *status_win;
4a2909a7 3409
21be28fb
JF
3410static bool status_empty = TRUE;
3411
2e8488b4 3412/* Update status and title window. */
4a2909a7
JF
3413static void
3414report(const char *msg, ...)
3415{
6706b2ba 3416 struct view *view = display[current_view];
b76c2afc 3417
ab4af23e
JF
3418 if (input_mode)
3419 return;
3420
21be28fb 3421 if (!status_empty || *msg) {
6706b2ba 3422 va_list args;
4a2909a7 3423
6706b2ba 3424 va_start(args, msg);
4b76734f 3425
6706b2ba
JF
3426 wmove(status_win, 0, 0);
3427 if (*msg) {
3428 vwprintw(status_win, msg, args);
21be28fb 3429 status_empty = FALSE;
6706b2ba 3430 } else {
21be28fb 3431 status_empty = TRUE;
6706b2ba 3432 }
390a8262 3433 wclrtoeol(status_win);
6706b2ba 3434 wrefresh(status_win);
b801d8b2 3435
6706b2ba
JF
3436 va_end(args);
3437 }
3438
3439 update_view_title(view);
2bee3bde 3440 update_display_cursor(view);
b801d8b2
JF
3441}
3442
6b161b31
JF
3443/* Controls when nodelay should be in effect when polling user input. */
3444static void
1ba2ae4b 3445set_nonblocking_input(bool loading)
b801d8b2 3446{
6706b2ba 3447 static unsigned int loading_views;
b801d8b2 3448
6706b2ba
JF
3449 if ((loading == FALSE && loading_views-- == 1) ||
3450 (loading == TRUE && loading_views++ == 0))
1ba2ae4b 3451 nodelay(status_win, loading);
6b161b31
JF
3452}
3453
3454static void
3455init_display(void)
3456{
3457 int x, y;
b76c2afc 3458
6908bdbd
JF
3459 /* Initialize the curses library */
3460 if (isatty(STDIN_FILENO)) {
8855ada4 3461 cursed = !!initscr();
6908bdbd
JF
3462 } else {
3463 /* Leave stdin and stdout alone when acting as a pager. */
3464 FILE *io = fopen("/dev/tty", "r+");
3465
e6f60674
JF
3466 if (!io)
3467 die("Failed to open /dev/tty");
8855ada4 3468 cursed = !!newterm(NULL, io, io);
6908bdbd
JF
3469 }
3470
8855ada4
JF
3471 if (!cursed)
3472 die("Failed to initialize curses");
3473
2e8488b4
JF
3474 nonl(); /* Tell curses not to do NL->CR/NL on output */
3475 cbreak(); /* Take input chars one at a time, no wait for \n */
3476 noecho(); /* Don't echo input */
b801d8b2 3477 leaveok(stdscr, TRUE);
b801d8b2
JF
3478
3479 if (has_colors())
3480 init_colors();
3481
3482 getmaxyx(stdscr, y, x);
3483 status_win = newwin(1, 0, y - 1, 0);
3484 if (!status_win)
3485 die("Failed to create status window");
3486
3487 /* Enable keyboard mapping */
3488 keypad(status_win, TRUE);
78c70acd 3489 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6b161b31
JF
3490}
3491
4af34daa 3492static char *
cb9e48c1 3493read_prompt(const char *prompt)
ef5404a4
JF
3494{
3495 enum { READING, STOP, CANCEL } status = READING;
9e21ce5c 3496 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
ef5404a4
JF
3497 int pos = 0;
3498
3499 while (status == READING) {
3500 struct view *view;
3501 int i, key;
3502
ab4af23e
JF
3503 input_mode = TRUE;
3504
699ae55b 3505 foreach_view (view, i)
ef5404a4
JF
3506 update_view(view);
3507
ab4af23e
JF
3508 input_mode = FALSE;
3509
3510 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
3511 wclrtoeol(status_win);
3512
ef5404a4
JF
3513 /* Refresh, accept single keystroke of input */
3514 key = wgetch(status_win);
3515 switch (key) {
3516 case KEY_RETURN:
3517 case KEY_ENTER:
3518 case '\n':
3519 status = pos ? STOP : CANCEL;
3520 break;
3521
3522 case KEY_BACKSPACE:
3523 if (pos > 0)
3524 pos--;
3525 else
3526 status = CANCEL;
3527 break;
3528
3529 case KEY_ESC:
3530 status = CANCEL;
3531 break;
3532
3533 case ERR:
3534 break;
3535
3536 default:
3537 if (pos >= sizeof(buf)) {
3538 report("Input string too long");
9e21ce5c 3539 return NULL;
ef5404a4
JF
3540 }
3541
3542 if (isprint(key))
3543 buf[pos++] = (char) key;
3544 }
3545 }
3546
7a06ebdf
JF
3547 /* Clear the status window */
3548 status_empty = FALSE;
3549 report("");
3550
3551 if (status == CANCEL)
9e21ce5c 3552 return NULL;
ef5404a4
JF
3553
3554 buf[pos++] = 0;
ef5404a4 3555
9e21ce5c 3556 return buf;
ef5404a4 3557}
c34d9c9f
JF
3558
3559/*
3560 * Repository references
3561 */
3562
3563static struct ref *refs;
3a91b75e 3564static size_t refs_size;
c34d9c9f 3565
1307df1a
JF
3566/* Id <-> ref store */
3567static struct ref ***id_refs;
3568static size_t id_refs_size;
3569
c34d9c9f
JF
3570static struct ref **
3571get_refs(char *id)
3572{
1307df1a
JF
3573 struct ref ***tmp_id_refs;
3574 struct ref **ref_list = NULL;
3575 size_t ref_list_size = 0;
c34d9c9f
JF
3576 size_t i;
3577
1307df1a
JF
3578 for (i = 0; i < id_refs_size; i++)
3579 if (!strcmp(id, id_refs[i][0]->id))
3580 return id_refs[i];
3581
3582 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3583 if (!tmp_id_refs)
3584 return NULL;
3585
3586 id_refs = tmp_id_refs;
3587
c34d9c9f
JF
3588 for (i = 0; i < refs_size; i++) {
3589 struct ref **tmp;
3590
3591 if (strcmp(id, refs[i].id))
3592 continue;
3593
1307df1a 3594 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
c34d9c9f 3595 if (!tmp) {
1307df1a
JF
3596 if (ref_list)
3597 free(ref_list);
c34d9c9f
JF
3598 return NULL;
3599 }
3600
1307df1a
JF
3601 ref_list = tmp;
3602 if (ref_list_size > 0)
3603 ref_list[ref_list_size - 1]->next = 1;
3604 ref_list[ref_list_size] = &refs[i];
3af8774e
JF
3605
3606 /* XXX: The properties of the commit chains ensures that we can
3607 * safely modify the shared ref. The repo references will
3608 * always be similar for the same id. */
1307df1a
JF
3609 ref_list[ref_list_size]->next = 0;
3610 ref_list_size++;
c34d9c9f
JF
3611 }
3612
1307df1a
JF
3613 if (ref_list)
3614 id_refs[id_refs_size++] = ref_list;
3615
3616 return ref_list;
c34d9c9f
JF
3617}
3618
3619static int
d0cea5f9 3620read_ref(char *id, int idlen, char *name, int namelen)
c34d9c9f 3621{
d0cea5f9
JF
3622 struct ref *ref;
3623 bool tag = FALSE;
e15ec88e 3624 bool remote = FALSE;
d0cea5f9 3625
8b0297ae
JF
3626 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3627 /* Commits referenced by tags has "^{}" appended. */
3628 if (name[namelen - 1] != '}')
3629 return OK;
3630
d0cea5f9
JF
3631 while (namelen > 0 && name[namelen] != '^')
3632 namelen--;
c34d9c9f 3633
d0cea5f9 3634 tag = TRUE;
8b0297ae
JF
3635 namelen -= STRING_SIZE("refs/tags/");
3636 name += STRING_SIZE("refs/tags/");
c34d9c9f 3637
e15ec88e
JF
3638 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
3639 remote = TRUE;
3640 namelen -= STRING_SIZE("refs/remotes/");
3641 name += STRING_SIZE("refs/remotes/");
3642
d0cea5f9 3643 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
8b0297ae
JF
3644 namelen -= STRING_SIZE("refs/heads/");
3645 name += STRING_SIZE("refs/heads/");
c34d9c9f 3646
d0cea5f9
JF
3647 } else if (!strcmp(name, "HEAD")) {
3648 return OK;
3649 }
6706b2ba 3650
d0cea5f9
JF
3651 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3652 if (!refs)
3653 return ERR;
c34d9c9f 3654
d0cea5f9 3655 ref = &refs[refs_size++];
8b0297ae 3656 ref->name = malloc(namelen + 1);
d0cea5f9
JF
3657 if (!ref->name)
3658 return ERR;
3af8774e 3659
8b0297ae
JF
3660 strncpy(ref->name, name, namelen);
3661 ref->name[namelen] = 0;
d0cea5f9 3662 ref->tag = tag;
e15ec88e 3663 ref->remote = remote;
2463b4ea 3664 string_copy_rev(ref->id, id);
3af8774e 3665
d0cea5f9
JF
3666 return OK;
3667}
c34d9c9f 3668
d0cea5f9
JF
3669static int
3670load_refs(void)
3671{
3672 const char *cmd_env = getenv("TIG_LS_REMOTE");
3673 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
c34d9c9f 3674
4a63c884 3675 return read_properties(popen(cmd, "r"), "\t", read_ref);
d0cea5f9 3676}
c34d9c9f 3677
d0cea5f9 3678static int
14c778a6 3679read_repo_config_option(char *name, int namelen, char *value, int valuelen)
d0cea5f9 3680{
22913179 3681 if (!strcmp(name, "i18n.commitencoding"))
d0cea5f9 3682 string_copy(opt_encoding, value);
c34d9c9f 3683
c34d9c9f
JF
3684 return OK;
3685}
3686
4670cf89 3687static int
14c778a6 3688load_repo_config(void)
4670cf89 3689{
66749723 3690 return read_properties(popen("git repo-config --list", "r"),
14c778a6 3691 "=", read_repo_config_option);
d0cea5f9
JF
3692}
3693
3694static int
4a63c884 3695read_properties(FILE *pipe, const char *separators,
d0cea5f9
JF
3696 int (*read_property)(char *, int, char *, int))
3697{
4670cf89
JF
3698 char buffer[BUFSIZ];
3699 char *name;
d0cea5f9 3700 int state = OK;
4670cf89
JF
3701
3702 if (!pipe)
3703 return ERR;
3704
d0cea5f9 3705 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4a63c884
JF
3706 char *value;
3707 size_t namelen;
3708 size_t valuelen;
4670cf89 3709
4a63c884
JF
3710 name = chomp_string(name);
3711 namelen = strcspn(name, separators);
3712
3713 if (name[namelen]) {
3714 name[namelen] = 0;
3715 value = chomp_string(name + namelen + 1);
d0cea5f9 3716 valuelen = strlen(value);
4670cf89 3717
d0cea5f9 3718 } else {
d0cea5f9
JF
3719 value = "";
3720 valuelen = 0;
4670cf89 3721 }
d0cea5f9 3722
3c3801c2 3723 state = read_property(name, namelen, value, valuelen);
4670cf89
JF
3724 }
3725
d0cea5f9
JF
3726 if (state != ERR && ferror(pipe))
3727 state = ERR;
4670cf89
JF
3728
3729 pclose(pipe);
3730
d0cea5f9 3731 return state;
4670cf89
JF
3732}
3733
d0cea5f9 3734
6b161b31
JF
3735/*
3736 * Main
3737 */
3738
b5c9e67f 3739static void __NORETURN
6b161b31
JF
3740quit(int sig)
3741{
8855ada4
JF
3742 /* XXX: Restore tty modes and let the OS cleanup the rest! */
3743 if (cursed)
3744 endwin();
6b161b31
JF
3745 exit(0);
3746}
3747
c6704a4e
JF
3748static void __NORETURN
3749die(const char *err, ...)
6b161b31
JF
3750{
3751 va_list args;
3752
3753 endwin();
3754
3755 va_start(args, err);
3756 fputs("tig: ", stderr);
3757 vfprintf(stderr, err, args);
3758 fputs("\n", stderr);
3759 va_end(args);
3760
3761 exit(1);
3762}
3763
3764int
3765main(int argc, char *argv[])
3766{
1ba2ae4b 3767 struct view *view;
6b161b31 3768 enum request request;
1ba2ae4b 3769 size_t i;
6b161b31
JF
3770
3771 signal(SIGINT, quit);
3772
6b68fd24
JF
3773 if (setlocale(LC_ALL, "")) {
3774 string_copy(opt_codeset, nl_langinfo(CODESET));
3775 }
3776
660e09ad
JF
3777 if (load_options() == ERR)
3778 die("Failed to load user config.");
3779
3780 /* Load the repo config file so options can be overwritten from
afdc35b3 3781 * the command line. */
14c778a6 3782 if (load_repo_config() == ERR)
afdc35b3
JF
3783 die("Failed to load repo config.");
3784
8855ada4 3785 if (!parse_options(argc, argv))
6b161b31
JF
3786 return 0;
3787
6b68fd24
JF
3788 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3789 opt_iconv = iconv_open(opt_codeset, opt_encoding);
20f4b4a3 3790 if (opt_iconv == ICONV_NONE)
6b68fd24
JF
3791 die("Failed to initialize character set conversion");
3792 }
3793
c34d9c9f
JF
3794 if (load_refs() == ERR)
3795 die("Failed to load refs.");
3796
7bb55251
JF
3797 /* Require a git repository unless when running in pager mode. */
3798 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3799 die("Not a git repository");
3800
1ba2ae4b
JF
3801 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3802 view->cmd_env = getenv(view->cmd_env);
3803
6b161b31
JF
3804 request = opt_request;
3805
3806 init_display();
b801d8b2
JF
3807
3808 while (view_driver(display[current_view], request)) {
6b161b31 3809 int key;
b801d8b2
JF
3810 int i;
3811
699ae55b 3812 foreach_view (view, i)
6b161b31 3813 update_view(view);
b801d8b2
JF
3814
3815 /* Refresh, accept single keystroke of input */
6b161b31 3816 key = wgetch(status_win);
04e2b7b2 3817
cf4d82e6
JF
3818 /* wgetch() with nodelay() enabled returns ERR when there's no
3819 * input. */
3820 if (key == ERR) {
3821 request = REQ_NONE;
8b534a13 3822 continue;
cf4d82e6 3823 }
04e2b7b2
JF
3824
3825 request = get_keybinding(display[current_view]->keymap, key);
03a93dbb 3826
6706b2ba 3827 /* Some low-level request handling. This keeps access to
fac7db6c
JF
3828 * status_win restricted. */
3829 switch (request) {
3830 case REQ_PROMPT:
9e21ce5c
JF
3831 {
3832 char *cmd = read_prompt(":");
3833
3834 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3835 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3836 opt_request = REQ_VIEW_DIFF;
3837 } else {
3838 opt_request = REQ_VIEW_PAGER;
3839 }
3840 break;
3841 }
fac7db6c 3842
1d754561 3843 request = REQ_NONE;
9e21ce5c
JF
3844 break;
3845 }
4af34daa
JF
3846 case REQ_SEARCH:
3847 case REQ_SEARCH_BACK:
3848 {
3849 const char *prompt = request == REQ_SEARCH
3850 ? "/" : "?";
3851 char *search = read_prompt(prompt);
3852
3853 if (search)
3854 string_copy(opt_search, search);
3855 else
3856 request = REQ_NONE;
3857 break;
3858 }
fac7db6c
JF
3859 case REQ_SCREEN_RESIZE:
3860 {
3861 int height, width;
3862
3863 getmaxyx(stdscr, height, width);
3864
3865 /* Resize the status view and let the view driver take
3866 * care of resizing the displayed views. */
3867 wresize(status_win, 1, width);
3868 mvwin(status_win, height - 1, 0);
3869 wrefresh(status_win);
3870 break;
3871 }
3872 default:
3873 break;
03a93dbb 3874 }
b801d8b2
JF
3875 }
3876
3877 quit(0);
3878
3879 return 0;
3880}