Add notice about empty pager view
[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;
1237 /* Draw one line; @lineno must be < view->height. */
5dcf8064 1238 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
fe7233c3 1239 /* Read one line; updates view->line. */
701e4f5d 1240 bool (*read)(struct view *view, char *data);
fe7233c3
JF
1241 /* Depending on view, change display based on current line. */
1242 bool (*enter)(struct view *view, struct line *line);
4af34daa
JF
1243 /* Search for regex in a line. */
1244 bool (*grep)(struct view *view, struct line *line);
d720de4b
JF
1245 /* Select line */
1246 void (*select)(struct view *view, struct line *line);
fe7233c3
JF
1247};
1248
6b161b31
JF
1249static struct view_ops pager_ops;
1250static struct view_ops main_ops;
e733ee54
JF
1251static struct view_ops tree_ops;
1252static struct view_ops blob_ops;
a28bcc22 1253
04e2b7b2
JF
1254#define VIEW_STR(name, cmd, env, ref, ops, map) \
1255 { name, cmd, #env, ref, ops, map}
1ba2ae4b 1256
95d7ddcd 1257#define VIEW_(id, name, ops, ref) \
04e2b7b2 1258 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1ba2ae4b 1259
c2124ccd 1260
b801d8b2 1261static struct view views[] = {
95d7ddcd
JF
1262 VIEW_(MAIN, "main", &main_ops, ref_head),
1263 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1264 VIEW_(LOG, "log", &pager_ops, ref_head),
e733ee54
JF
1265 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1266 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
035ba11f
JF
1267 VIEW_(HELP, "help", &pager_ops, ""),
1268 VIEW_(PAGER, "pager", &pager_ops, ""),
b801d8b2
JF
1269};
1270
a28bcc22
JF
1271#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1272
699ae55b
JF
1273#define foreach_view(view, i) \
1274 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1275
1276#define view_is_displayed(view) \
1277 (view == display[0] || view == display[1])
4c6fabc2 1278
fe7233c3
JF
1279static bool
1280draw_view_line(struct view *view, unsigned int lineno)
1281{
d720de4b 1282 struct line *line;
5dcf8064 1283 bool selected = (view->offset + lineno == view->lineno);
4887d44e 1284 bool draw_ok;
d720de4b 1285
699ae55b
JF
1286 assert(view_is_displayed(view));
1287
fe7233c3
JF
1288 if (view->offset + lineno >= view->lines)
1289 return FALSE;
1290
d720de4b
JF
1291 line = &view->line[view->offset + lineno];
1292
3c571d67
JF
1293 if (selected) {
1294 line->selected = TRUE;
d720de4b 1295 view->ops->select(view, line);
3c571d67
JF
1296 } else if (line->selected) {
1297 line->selected = FALSE;
1298 wmove(view->win, lineno, 0);
1299 wclrtoeol(view->win);
1300 }
d720de4b 1301
4887d44e
JF
1302 scrollok(view->win, FALSE);
1303 draw_ok = view->ops->draw(view, line, lineno, selected);
1304 scrollok(view->win, TRUE);
1305
1306 return draw_ok;
fe7233c3
JF
1307}
1308
b801d8b2 1309static void
82e78006 1310redraw_view_from(struct view *view, int lineno)
b801d8b2 1311{
82e78006 1312 assert(0 <= lineno && lineno < view->height);
b801d8b2 1313
82e78006 1314 for (; lineno < view->height; lineno++) {
fe7233c3 1315 if (!draw_view_line(view, lineno))
fd85fef1 1316 break;
b801d8b2
JF
1317 }
1318
1319 redrawwin(view->win);
ab4af23e
JF
1320 if (input_mode)
1321 wnoutrefresh(view->win);
1322 else
1323 wrefresh(view->win);
b801d8b2
JF
1324}
1325
b76c2afc 1326static void
82e78006
JF
1327redraw_view(struct view *view)
1328{
1329 wclear(view->win);
1330 redraw_view_from(view, 0);
1331}
1332
c2124ccd 1333
6b161b31 1334static void
81030ec8
JF
1335update_view_title(struct view *view)
1336{
3c112a88 1337 char buf[SIZEOF_STR];
71d1c7db
JF
1338 char state[SIZEOF_STR];
1339 size_t bufpos = 0, statelen = 0;
81030ec8 1340
3c112a88 1341 assert(view_is_displayed(view));
81030ec8 1342
c19f8017 1343 if (view->lines || view->pipe) {
6d9c07af 1344 unsigned int view_lines = view->offset + view->height;
c19f8017 1345 unsigned int lines = view->lines
6d9c07af 1346 ? MIN(view_lines, view->lines) * 100 / view->lines
c19f8017
JF
1347 : 0;
1348
71d1c7db 1349 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
3c112a88
JF
1350 view->ops->type,
1351 view->lineno + 1,
1352 view->lines,
1353 lines);
81030ec8 1354
5becf244
JF
1355 if (view->pipe) {
1356 time_t secs = time(NULL) - view->start_time;
f97f4012 1357
5becf244
JF
1358 /* Three git seconds are a long time ... */
1359 if (secs > 2)
71d1c7db 1360 string_format_from(state, &statelen, " %lds", secs);
5becf244 1361 }
81030ec8
JF
1362 }
1363
71d1c7db
JF
1364 string_format_from(buf, &bufpos, "[%s]", view->name);
1365 if (*view->ref && bufpos < view->width) {
1366 size_t refsize = strlen(view->ref);
1367 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1368
1369 if (minsize < view->width)
1370 refsize = view->width - minsize + 7;
1371 string_format_from(buf, &bufpos, " %.*s", refsize, view->ref);
1372 }
f97f4012 1373
71d1c7db
JF
1374 if (statelen && bufpos < view->width) {
1375 string_format_from(buf, &bufpos, " %s", state);
f97f4012
JF
1376 }
1377
3c112a88
JF
1378 if (view == display[current_view])
1379 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1380 else
1381 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1382
3c112a88 1383 mvwaddnstr(view->title, 0, 0, buf, bufpos);
390a8262 1384 wclrtoeol(view->title);
976447f8 1385 wmove(view->title, 0, view->width - 1);
ab4af23e
JF
1386
1387 if (input_mode)
1388 wnoutrefresh(view->title);
1389 else
1390 wrefresh(view->title);
81030ec8
JF
1391}
1392
1393static void
6b161b31 1394resize_display(void)
b76c2afc 1395{
03a93dbb 1396 int offset, i;
6b161b31
JF
1397 struct view *base = display[0];
1398 struct view *view = display[1] ? display[1] : display[0];
b76c2afc 1399
6b161b31 1400 /* Setup window dimensions */
b76c2afc 1401
03a93dbb 1402 getmaxyx(stdscr, base->height, base->width);
b76c2afc 1403
6b161b31 1404 /* Make room for the status window. */
03a93dbb 1405 base->height -= 1;
6b161b31
JF
1406
1407 if (view != base) {
03a93dbb
JF
1408 /* Horizontal split. */
1409 view->width = base->width;
6b161b31
JF
1410 view->height = SCALE_SPLIT_VIEW(base->height);
1411 base->height -= view->height;
1412
1413 /* Make room for the title bar. */
1414 view->height -= 1;
1415 }
1416
1417 /* Make room for the title bar. */
1418 base->height -= 1;
1419
1420 offset = 0;
1421
33c4f9ea 1422 foreach_displayed_view (view, i) {
b76c2afc 1423 if (!view->win) {
c19f8017 1424 view->win = newwin(view->height, 0, offset, 0);
6b161b31
JF
1425 if (!view->win)
1426 die("Failed to create %s view", view->name);
1427
1428 scrollok(view->win, TRUE);
1429
1430 view->title = newwin(1, 0, offset + view->height, 0);
1431 if (!view->title)
1432 die("Failed to create title window");
1433
1434 } else {
c19f8017 1435 wresize(view->win, view->height, view->width);
6b161b31
JF
1436 mvwin(view->win, offset, 0);
1437 mvwin(view->title, offset + view->height, 0);
a28bcc22 1438 }
a28bcc22 1439
6b161b31 1440 offset += view->height + 1;
b76c2afc 1441 }
6b161b31 1442}
b76c2afc 1443
6b161b31 1444static void
20bb5e18
JF
1445redraw_display(void)
1446{
1447 struct view *view;
1448 int i;
1449
33c4f9ea 1450 foreach_displayed_view (view, i) {
20bb5e18
JF
1451 redraw_view(view);
1452 update_view_title(view);
1453 }
1454}
1455
85af6284 1456static void
2bee3bde 1457update_display_cursor(struct view *view)
85af6284 1458{
85af6284
JF
1459 /* Move the cursor to the right-most column of the cursor line.
1460 *
1461 * XXX: This could turn out to be a bit expensive, but it ensures that
1462 * the cursor does not jump around. */
1463 if (view->lines) {
1464 wmove(view->win, view->lineno - view->offset, view->width - 1);
1465 wrefresh(view->win);
1466 }
1467}
20bb5e18 1468
2e8488b4
JF
1469/*
1470 * Navigation
1471 */
1472
4a2909a7 1473/* Scrolling backend */
b801d8b2 1474static void
8c317212 1475do_scroll_view(struct view *view, int lines)
b801d8b2 1476{
a0087dd5
JF
1477 bool redraw_current_line = FALSE;
1478
fd85fef1
JF
1479 /* The rendering expects the new offset. */
1480 view->offset += lines;
1481
1482 assert(0 <= view->offset && view->offset < view->lines);
1483 assert(lines);
b801d8b2 1484
a0087dd5
JF
1485 /* Move current line into the view. */
1486 if (view->lineno < view->offset) {
1487 view->lineno = view->offset;
1488 redraw_current_line = TRUE;
1489 } else if (view->lineno >= view->offset + view->height) {
1490 view->lineno = view->offset + view->height - 1;
1491 redraw_current_line = TRUE;
1492 }
1493
1494 assert(view->offset <= view->lineno && view->lineno < view->lines);
1495
82e78006 1496 /* Redraw the whole screen if scrolling is pointless. */
4c6fabc2 1497 if (view->height < ABS(lines)) {
b76c2afc
JF
1498 redraw_view(view);
1499
1500 } else {
22f66b0a 1501 int line = lines > 0 ? view->height - lines : 0;
82e78006 1502 int end = line + ABS(lines);
fd85fef1
JF
1503
1504 wscrl(view->win, lines);
1505
22f66b0a 1506 for (; line < end; line++) {
fe7233c3 1507 if (!draw_view_line(view, line))
fd85fef1
JF
1508 break;
1509 }
fd85fef1 1510
a0087dd5
JF
1511 if (redraw_current_line)
1512 draw_view_line(view, view->lineno - view->offset);
fd85fef1
JF
1513 }
1514
fd85fef1
JF
1515 redrawwin(view->win);
1516 wrefresh(view->win);
9d3f5834 1517 report("");
fd85fef1 1518}
78c70acd 1519
4a2909a7 1520/* Scroll frontend */
fd85fef1 1521static void
6b161b31 1522scroll_view(struct view *view, enum request request)
fd85fef1
JF
1523{
1524 int lines = 1;
b801d8b2 1525
8c317212
JF
1526 assert(view_is_displayed(view));
1527
b801d8b2 1528 switch (request) {
4a2909a7 1529 case REQ_SCROLL_PAGE_DOWN:
fd85fef1 1530 lines = view->height;
4a2909a7 1531 case REQ_SCROLL_LINE_DOWN:
b801d8b2 1532 if (view->offset + lines > view->lines)
bde3653a 1533 lines = view->lines - view->offset;
b801d8b2 1534
fd85fef1 1535 if (lines == 0 || view->offset + view->height >= view->lines) {
eb98559e 1536 report("Cannot scroll beyond the last line");
b801d8b2
JF
1537 return;
1538 }
1539 break;
1540
4a2909a7 1541 case REQ_SCROLL_PAGE_UP:
fd85fef1 1542 lines = view->height;
4a2909a7 1543 case REQ_SCROLL_LINE_UP:
b801d8b2
JF
1544 if (lines > view->offset)
1545 lines = view->offset;
1546
1547 if (lines == 0) {
eb98559e 1548 report("Cannot scroll beyond the first line");
b801d8b2
JF
1549 return;
1550 }
1551
fd85fef1 1552 lines = -lines;
b801d8b2 1553 break;
03a93dbb 1554
6b161b31
JF
1555 default:
1556 die("request %d not handled in switch", request);
b801d8b2
JF
1557 }
1558
8c317212 1559 do_scroll_view(view, lines);
fd85fef1 1560}
b801d8b2 1561
4a2909a7 1562/* Cursor moving */
fd85fef1 1563static void
8522ecc7 1564move_view(struct view *view, enum request request)
fd85fef1 1565{
dfaa6c81 1566 int scroll_steps = 0;
fd85fef1 1567 int steps;
b801d8b2 1568
fd85fef1 1569 switch (request) {
4a2909a7 1570 case REQ_MOVE_FIRST_LINE:
78c70acd
JF
1571 steps = -view->lineno;
1572 break;
1573
4a2909a7 1574 case REQ_MOVE_LAST_LINE:
78c70acd
JF
1575 steps = view->lines - view->lineno - 1;
1576 break;
1577
4a2909a7 1578 case REQ_MOVE_PAGE_UP:
78c70acd
JF
1579 steps = view->height > view->lineno
1580 ? -view->lineno : -view->height;
1581 break;
1582
4a2909a7 1583 case REQ_MOVE_PAGE_DOWN:
78c70acd
JF
1584 steps = view->lineno + view->height >= view->lines
1585 ? view->lines - view->lineno - 1 : view->height;
1586 break;
1587
4a2909a7 1588 case REQ_MOVE_UP:
fd85fef1
JF
1589 steps = -1;
1590 break;
b801d8b2 1591
4a2909a7 1592 case REQ_MOVE_DOWN:
fd85fef1
JF
1593 steps = 1;
1594 break;
6b161b31
JF
1595
1596 default:
1597 die("request %d not handled in switch", request);
78c70acd 1598 }
b801d8b2 1599
4c6fabc2 1600 if (steps <= 0 && view->lineno == 0) {
eb98559e 1601 report("Cannot move beyond the first line");
78c70acd 1602 return;
b801d8b2 1603
6908bdbd 1604 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
eb98559e 1605 report("Cannot move beyond the last line");
78c70acd 1606 return;
fd85fef1
JF
1607 }
1608
4c6fabc2 1609 /* Move the current line */
fd85fef1 1610 view->lineno += steps;
4c6fabc2
JF
1611 assert(0 <= view->lineno && view->lineno < view->lines);
1612
4c6fabc2 1613 /* Check whether the view needs to be scrolled */
fd85fef1
JF
1614 if (view->lineno < view->offset ||
1615 view->lineno >= view->offset + view->height) {
dfaa6c81 1616 scroll_steps = steps;
fd85fef1 1617 if (steps < 0 && -steps > view->offset) {
dfaa6c81 1618 scroll_steps = -view->offset;
b76c2afc
JF
1619
1620 } else if (steps > 0) {
1621 if (view->lineno == view->lines - 1 &&
1622 view->lines > view->height) {
dfaa6c81
JF
1623 scroll_steps = view->lines - view->offset - 1;
1624 if (scroll_steps >= view->height)
1625 scroll_steps -= view->height - 1;
b76c2afc 1626 }
b801d8b2 1627 }
8522ecc7
JF
1628 }
1629
1630 if (!view_is_displayed(view)) {
a3965365
JF
1631 view->offset += scroll_steps;
1632 assert(0 <= view->offset && view->offset < view->lines);
8522ecc7
JF
1633 view->ops->select(view, &view->line[view->lineno]);
1634 return;
1635 }
1636
1637 /* Repaint the old "current" line if we be scrolling */
1638 if (ABS(steps) < view->height)
1639 draw_view_line(view, view->lineno - steps - view->offset);
1640
dfaa6c81
JF
1641 if (scroll_steps) {
1642 do_scroll_view(view, scroll_steps);
fd85fef1 1643 return;
b801d8b2
JF
1644 }
1645
4c6fabc2 1646 /* Draw the current line */
fe7233c3 1647 draw_view_line(view, view->lineno - view->offset);
fd85fef1 1648
b801d8b2
JF
1649 redrawwin(view->win);
1650 wrefresh(view->win);
9d3f5834 1651 report("");
b801d8b2
JF
1652}
1653
b801d8b2 1654
2e8488b4 1655/*
4af34daa
JF
1656 * Searching
1657 */
1658
c02d8fce 1659static void search_view(struct view *view, enum request request);
4af34daa
JF
1660
1661static bool
1662find_next_line(struct view *view, unsigned long lineno, struct line *line)
1663{
699ae55b
JF
1664 assert(view_is_displayed(view));
1665
4af34daa
JF
1666 if (!view->ops->grep(view, line))
1667 return FALSE;
1668
1669 if (lineno - view->offset >= view->height) {
1670 view->offset = lineno;
1671 view->lineno = lineno;
1672 redraw_view(view);
1673
1674 } else {
1675 unsigned long old_lineno = view->lineno - view->offset;
1676
1677 view->lineno = lineno;
4af34daa
JF
1678 draw_view_line(view, old_lineno);
1679
1680 draw_view_line(view, view->lineno - view->offset);
1681 redrawwin(view->win);
1682 wrefresh(view->win);
1683 }
1684
1685 report("Line %ld matches '%s'", lineno + 1, view->grep);
1686 return TRUE;
1687}
1688
1689static void
1690find_next(struct view *view, enum request request)
1691{
1692 unsigned long lineno = view->lineno;
1693 int direction;
1694
1695 if (!*view->grep) {
1696 if (!*opt_search)
1697 report("No previous search");
1698 else
c02d8fce 1699 search_view(view, request);
4af34daa
JF
1700 return;
1701 }
1702
1703 switch (request) {
1704 case REQ_SEARCH:
1705 case REQ_FIND_NEXT:
1706 direction = 1;
1707 break;
1708
1709 case REQ_SEARCH_BACK:
1710 case REQ_FIND_PREV:
1711 direction = -1;
1712 break;
1713
1714 default:
1715 return;
1716 }
1717
1718 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1719 lineno += direction;
1720
1721 /* Note, lineno is unsigned long so will wrap around in which case it
1722 * will become bigger than view->lines. */
1723 for (; lineno < view->lines; lineno += direction) {
1724 struct line *line = &view->line[lineno];
1725
1726 if (find_next_line(view, lineno, line))
1727 return;
1728 }
1729
1730 report("No match found for '%s'", view->grep);
1731}
1732
1733static void
c02d8fce 1734search_view(struct view *view, enum request request)
4af34daa
JF
1735{
1736 int regex_err;
1737
b77b2cb8
JF
1738 if (view->regex) {
1739 regfree(view->regex);
4af34daa 1740 *view->grep = 0;
b77b2cb8
JF
1741 } else {
1742 view->regex = calloc(1, sizeof(*view->regex));
1743 if (!view->regex)
1744 return;
4af34daa
JF
1745 }
1746
c02d8fce 1747 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
4af34daa
JF
1748 if (regex_err != 0) {
1749 char buf[SIZEOF_STR] = "unknown error";
1750
b77b2cb8 1751 regerror(regex_err, view->regex, buf, sizeof(buf));
e9cacd58 1752 report("Search failed: %s", buf);
4af34daa
JF
1753 return;
1754 }
1755
c02d8fce 1756 string_copy(view->grep, opt_search);
4af34daa
JF
1757
1758 find_next(view, request);
1759}
1760
1761/*
2e8488b4
JF
1762 * Incremental updating
1763 */
b801d8b2 1764
199d1288
JF
1765static void
1766end_update(struct view *view)
1767{
1768 if (!view->pipe)
1769 return;
1770 set_nonblocking_input(FALSE);
1771 if (view->pipe == stdin)
1772 fclose(view->pipe);
1773 else
1774 pclose(view->pipe);
1775 view->pipe = NULL;
1776}
1777
03a93dbb 1778static bool
b801d8b2
JF
1779begin_update(struct view *view)
1780{
4685845e 1781 const char *id = view->id;
fd85fef1 1782
199d1288
JF
1783 if (view->pipe)
1784 end_update(view);
1785
03a93dbb
JF
1786 if (opt_cmd[0]) {
1787 string_copy(view->cmd, opt_cmd);
1788 opt_cmd[0] = 0;
035ba11f
JF
1789 /* When running random commands, initially show the
1790 * command in the title. However, it maybe later be
1791 * overwritten if a commit line is selected. */
1792 string_copy(view->ref, view->cmd);
e733ee54
JF
1793
1794 } else if (view == VIEW(REQ_VIEW_TREE)) {
1795 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
f0f114ac 1796 char path[SIZEOF_STR];
e733ee54
JF
1797
1798 if (strcmp(view->vid, view->id))
f0f114ac
JF
1799 opt_path[0] = path[0] = 0;
1800 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1801 return FALSE;
e733ee54 1802
f0f114ac 1803 if (!string_format(view->cmd, format, id, path))
e733ee54
JF
1804 return FALSE;
1805
03a93dbb 1806 } else {
4685845e 1807 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1ba2ae4b 1808
cc2d1364 1809 if (!string_format(view->cmd, format, id, id, id, id, id))
03a93dbb 1810 return FALSE;
035ba11f
JF
1811
1812 /* Put the current ref_* value to the view title ref
1813 * member. This is needed by the blob view. Most other
1814 * views sets it automatically after loading because the
1815 * first line is a commit line. */
1816 string_copy(view->ref, id);
03a93dbb 1817 }
b801d8b2 1818
6908bdbd
JF
1819 /* Special case for the pager view. */
1820 if (opt_pipe) {
1821 view->pipe = opt_pipe;
1822 opt_pipe = NULL;
1823 } else {
1824 view->pipe = popen(view->cmd, "r");
1825 }
1826
2e8488b4
JF
1827 if (!view->pipe)
1828 return FALSE;
b801d8b2 1829
6b161b31 1830 set_nonblocking_input(TRUE);
b801d8b2
JF
1831
1832 view->offset = 0;
1833 view->lines = 0;
1834 view->lineno = 0;
2463b4ea 1835 string_copy_rev(view->vid, id);
b801d8b2 1836
2e8488b4
JF
1837 if (view->line) {
1838 int i;
1839
1840 for (i = 0; i < view->lines; i++)
fe7233c3
JF
1841 if (view->line[i].data)
1842 free(view->line[i].data);
2e8488b4
JF
1843
1844 free(view->line);
1845 view->line = NULL;
1846 }
1847
1848 view->start_time = time(NULL);
1849
b801d8b2
JF
1850 return TRUE;
1851}
1852
e2c01617
JF
1853static struct line *
1854realloc_lines(struct view *view, size_t line_size)
1855{
1856 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1857
1858 if (!tmp)
1859 return NULL;
1860
1861 view->line = tmp;
1862 view->line_size = line_size;
1863 return view->line;
1864}
1865
03a93dbb 1866static bool
b801d8b2
JF
1867update_view(struct view *view)
1868{
6b68fd24
JF
1869 char in_buffer[BUFSIZ];
1870 char out_buffer[BUFSIZ * 2];
b801d8b2 1871 char *line;
82e78006
JF
1872 /* The number of lines to read. If too low it will cause too much
1873 * redrawing (and possible flickering), if too high responsiveness
1874 * will suffer. */
8855ada4 1875 unsigned long lines = view->height;
82e78006 1876 int redraw_from = -1;
b801d8b2
JF
1877
1878 if (!view->pipe)
1879 return TRUE;
1880
82e78006
JF
1881 /* Only redraw if lines are visible. */
1882 if (view->offset + view->height >= view->lines)
1883 redraw_from = view->lines - view->offset;
b801d8b2 1884
699ae55b 1885 /* FIXME: This is probably not perfect for backgrounded views. */
e2c01617 1886 if (!realloc_lines(view, view->lines + lines))
b801d8b2
JF
1887 goto alloc_error;
1888
6b68fd24
JF
1889 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1890 size_t linelen = strlen(line);
b801d8b2 1891
b801d8b2
JF
1892 if (linelen)
1893 line[linelen - 1] = 0;
1894
6b68fd24
JF
1895 if (opt_iconv != ICONV_NONE) {
1896 char *inbuf = line;
1897 size_t inlen = linelen;
1898
1899 char *outbuf = out_buffer;
1900 size_t outlen = sizeof(out_buffer);
1901
1902 size_t ret;
1903
7361622d 1904 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
6b68fd24
JF
1905 if (ret != (size_t) -1) {
1906 line = out_buffer;
1907 linelen = strlen(out_buffer);
1908 }
1909 }
1910
701e4f5d 1911 if (!view->ops->read(view, line))
b801d8b2 1912 goto alloc_error;
fd85fef1
JF
1913
1914 if (lines-- == 1)
1915 break;
b801d8b2
JF
1916 }
1917
8855ada4
JF
1918 {
1919 int digits;
1920
1921 lines = view->lines;
1922 for (digits = 0; lines; digits++)
1923 lines /= 10;
1924
1925 /* Keep the displayed view in sync with line number scaling. */
1926 if (digits != view->digits) {
1927 view->digits = digits;
1928 redraw_from = 0;
1929 }
1930 }
1931
699ae55b
JF
1932 if (!view_is_displayed(view))
1933 goto check_pipe;
1934
e733ee54
JF
1935 if (view == VIEW(REQ_VIEW_TREE)) {
1936 /* Clear the view and redraw everything since the tree sorting
1937 * might have rearranged things. */
1938 redraw_view(view);
1939
1940 } else if (redraw_from >= 0) {
82e78006 1941 /* If this is an incremental update, redraw the previous line
a28bcc22
JF
1942 * since for commits some members could have changed when
1943 * loading the main view. */
82e78006
JF
1944 if (redraw_from > 0)
1945 redraw_from--;
1946
9eded379
JF
1947 /* Since revision graph visualization requires knowledge
1948 * about the parent commit, it causes a further one-off
1949 * needed to be redrawn for incremental updates. */
1950 if (redraw_from > 0 && opt_rev_graph)
1951 redraw_from--;
1952
82e78006
JF
1953 /* Incrementally draw avoids flickering. */
1954 redraw_view_from(view, redraw_from);
4c6fabc2 1955 }
b801d8b2 1956
eb98559e
JF
1957 /* Update the title _after_ the redraw so that if the redraw picks up a
1958 * commit reference in view->ref it'll be available here. */
1959 update_view_title(view);
1960
699ae55b 1961check_pipe:
b801d8b2 1962 if (ferror(view->pipe)) {
03a93dbb 1963 report("Failed to read: %s", strerror(errno));
b801d8b2
JF
1964 goto end;
1965
1966 } else if (feof(view->pipe)) {
f97f4012 1967 report("");
b801d8b2
JF
1968 goto end;
1969 }
1970
1971 return TRUE;
1972
1973alloc_error:
2e8488b4 1974 report("Allocation failure");
b801d8b2
JF
1975
1976end:
be04d936 1977 view->ops->read(view, NULL);
b801d8b2
JF
1978 end_update(view);
1979 return FALSE;
1980}
1981
0a0d8910
JF
1982static struct line *
1983add_line_text(struct view *view, char *data, enum line_type type)
1984{
1985 struct line *line = &view->line[view->lines];
1986
1987 if (!data)
1988 return NULL;
1989
1990 line->data = strdup(data);
1991 if (!line->data)
1992 return NULL;
1993
1994 line->type = type;
1995 view->lines++;
1996
1997 return line;
1998}
1999
79d445ca 2000
e10154d5
JF
2001/*
2002 * View opening
2003 */
2004
79d445ca
JF
2005static void open_help_view(struct view *view)
2006{
2007 char buf[BUFSIZ];
2008 int lines = ARRAY_SIZE(req_info) + 2;
2009 int i;
2010
2011 if (view->lines > 0)
2012 return;
2013
2014 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2015 if (!req_info[i].request)
2016 lines++;
2017
2018 view->line = calloc(lines, sizeof(*view->line));
2019 if (!view->line) {
2020 report("Allocation failure");
2021 return;
2022 }
2023
0a0d8910 2024 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
79d445ca
JF
2025
2026 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2027 char *key;
2028
2029 if (!req_info[i].request) {
0a0d8910
JF
2030 add_line_text(view, "", LINE_DEFAULT);
2031 add_line_text(view, req_info[i].help, LINE_DEFAULT);
79d445ca
JF
2032 continue;
2033 }
2034
2035 key = get_key(req_info[i].request);
520094b4 2036 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
79d445ca
JF
2037 continue;
2038
0a0d8910 2039 add_line_text(view, buf, LINE_DEFAULT);
79d445ca
JF
2040 }
2041}
2042
49f2b43f
JF
2043enum open_flags {
2044 OPEN_DEFAULT = 0, /* Use default view switching. */
2045 OPEN_SPLIT = 1, /* Split current view. */
2046 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2047 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2048};
2049
6b161b31 2050static void
49f2b43f 2051open_view(struct view *prev, enum request request, enum open_flags flags)
b801d8b2 2052{
49f2b43f
JF
2053 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2054 bool split = !!(flags & OPEN_SPLIT);
2055 bool reload = !!(flags & OPEN_RELOAD);
a28bcc22 2056 struct view *view = VIEW(request);
9f41488f 2057 int nviews = displayed_views();
6e950a52 2058 struct view *base_view = display[0];
b801d8b2 2059
49f2b43f 2060 if (view == prev && nviews == 1 && !reload) {
6b161b31
JF
2061 report("Already in %s view", view->name);
2062 return;
2063 }
b801d8b2 2064
2509b112 2065 if (view == VIEW(REQ_VIEW_HELP)) {
79d445ca 2066 open_help_view(view);
2509b112
JF
2067
2068 } else if ((reload || strcmp(view->vid, view->id)) &&
2069 !begin_update(view)) {
6b161b31
JF
2070 report("Failed to load %s view", view->name);
2071 return;
2072 }
a28bcc22 2073
6b161b31 2074 if (split) {
8d741c06 2075 display[1] = view;
6b161b31 2076 if (!backgrounded)
8d741c06 2077 current_view = 1;
6b161b31
JF
2078 } else {
2079 /* Maximize the current view. */
2080 memset(display, 0, sizeof(display));
2081 current_view = 0;
2082 display[current_view] = view;
a28bcc22 2083 }
b801d8b2 2084
6e950a52
JF
2085 /* Resize the view when switching between split- and full-screen,
2086 * or when switching between two different full-screen views. */
2087 if (nviews != displayed_views() ||
2088 (nviews == 1 && base_view != display[0]))
a006db63 2089 resize_display();
b801d8b2 2090
a8891802 2091 if (split && prev->lineno - prev->offset >= prev->height) {
03a93dbb 2092 /* Take the title line into account. */
eb98559e 2093 int lines = prev->lineno - prev->offset - prev->height + 1;
03a93dbb
JF
2094
2095 /* Scroll the view that was split if the current line is
2096 * outside the new limited view. */
8c317212 2097 do_scroll_view(prev, lines);
03a93dbb
JF
2098 }
2099
6b161b31 2100 if (prev && view != prev) {
9b995f0c 2101 if (split && !backgrounded) {
f0b3ab80
JF
2102 /* "Blur" the previous view. */
2103 update_view_title(prev);
9f396969 2104 }
f0b3ab80 2105
f6da0b66 2106 view->parent = prev;
b801d8b2
JF
2107 }
2108
9f396969 2109 if (view->pipe && view->lines == 0) {
03a93dbb
JF
2110 /* Clear the old view and let the incremental updating refill
2111 * the screen. */
2112 wclear(view->win);
f97f4012 2113 report("");
03a93dbb
JF
2114 } else {
2115 redraw_view(view);
24b5b3e0 2116 report("");
03a93dbb 2117 }
6706b2ba
JF
2118
2119 /* If the view is backgrounded the above calls to report()
2120 * won't redraw the view title. */
2121 if (backgrounded)
2122 update_view_title(view);
b801d8b2
JF
2123}
2124
2125
6b161b31
JF
2126/*
2127 * User request switch noodle
2128 */
2129
b801d8b2 2130static int
6b161b31 2131view_driver(struct view *view, enum request request)
b801d8b2 2132{
b801d8b2
JF
2133 int i;
2134
2135 switch (request) {
4a2909a7
JF
2136 case REQ_MOVE_UP:
2137 case REQ_MOVE_DOWN:
2138 case REQ_MOVE_PAGE_UP:
2139 case REQ_MOVE_PAGE_DOWN:
2140 case REQ_MOVE_FIRST_LINE:
2141 case REQ_MOVE_LAST_LINE:
8522ecc7 2142 move_view(view, request);
fd85fef1
JF
2143 break;
2144
4a2909a7
JF
2145 case REQ_SCROLL_LINE_DOWN:
2146 case REQ_SCROLL_LINE_UP:
2147 case REQ_SCROLL_PAGE_DOWN:
2148 case REQ_SCROLL_PAGE_UP:
a28bcc22 2149 scroll_view(view, request);
b801d8b2
JF
2150 break;
2151
e733ee54
JF
2152 case REQ_VIEW_BLOB:
2153 if (!ref_blob[0]) {
550cd4b5
JF
2154 report("No file chosen, press %s to open tree view",
2155 get_key(REQ_VIEW_TREE));
e733ee54
JF
2156 break;
2157 }
5c4358d1
JF
2158 open_view(view, request, OPEN_DEFAULT);
2159 break;
2160
2161 case REQ_VIEW_PAGER:
2162 if (!VIEW(REQ_VIEW_PAGER)->lines) {
2163 report("No pager content, press %s to run command from prompt",
2164 get_key(REQ_PROMPT));
2165 break;
2166 }
2167 open_view(view, request, OPEN_DEFAULT);
2168 break;
2169
4a2909a7 2170 case REQ_VIEW_MAIN:
4a2909a7 2171 case REQ_VIEW_DIFF:
2e8488b4 2172 case REQ_VIEW_LOG:
e733ee54 2173 case REQ_VIEW_TREE:
2e8488b4 2174 case REQ_VIEW_HELP:
49f2b43f 2175 open_view(view, request, OPEN_DEFAULT);
b801d8b2
JF
2176 break;
2177
b3a54cba
JF
2178 case REQ_NEXT:
2179 case REQ_PREVIOUS:
2180 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2181
e733ee54
JF
2182 if ((view == VIEW(REQ_VIEW_DIFF) &&
2183 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2184 (view == VIEW(REQ_VIEW_BLOB) &&
2185 view->parent == VIEW(REQ_VIEW_TREE))) {
b3a54cba 2186 view = view->parent;
8522ecc7
JF
2187 move_view(view, request);
2188 if (view_is_displayed(view))
f0b3ab80 2189 update_view_title(view);
b3a54cba 2190 } else {
8522ecc7 2191 move_view(view, request);
b3a54cba
JF
2192 break;
2193 }
6706b2ba
JF
2194 /* Fall-through */
2195
6b161b31 2196 case REQ_ENTER:
6908bdbd
JF
2197 if (!view->lines) {
2198 report("Nothing to enter");
2199 break;
2200 }
fe7233c3 2201 return view->ops->enter(view, &view->line[view->lineno]);
6b161b31 2202
03a93dbb
JF
2203 case REQ_VIEW_NEXT:
2204 {
9f41488f 2205 int nviews = displayed_views();
03a93dbb
JF
2206 int next_view = (current_view + 1) % nviews;
2207
2208 if (next_view == current_view) {
2209 report("Only one view is displayed");
2210 break;
2211 }
2212
2213 current_view = next_view;
2214 /* Blur out the title of the previous view. */
2215 update_view_title(view);
6734f6b9 2216 report("");
03a93dbb
JF
2217 break;
2218 }
24b5b3e0 2219 case REQ_TOGGLE_LINENO:
b76c2afc 2220 opt_line_number = !opt_line_number;
20bb5e18 2221 redraw_display();
b801d8b2
JF
2222 break;
2223
54efb62b
JF
2224 case REQ_TOGGLE_REV_GRAPH:
2225 opt_rev_graph = !opt_rev_graph;
2226 redraw_display();
2227 break;
2228
03a93dbb 2229 case REQ_PROMPT:
8855ada4 2230 /* Always reload^Wrerun commands from the prompt. */
49f2b43f 2231 open_view(view, opt_request, OPEN_RELOAD);
03a93dbb
JF
2232 break;
2233
4af34daa
JF
2234 case REQ_SEARCH:
2235 case REQ_SEARCH_BACK:
c02d8fce 2236 search_view(view, request);
4af34daa
JF
2237 break;
2238
2239 case REQ_FIND_NEXT:
2240 case REQ_FIND_PREV:
2241 find_next(view, request);
2242 break;
2243
4a2909a7 2244 case REQ_STOP_LOADING:
59a45d3a
JF
2245 for (i = 0; i < ARRAY_SIZE(views); i++) {
2246 view = &views[i];
2e8488b4 2247 if (view->pipe)
6a7bb912 2248 report("Stopped loading the %s view", view->name),
03a93dbb
JF
2249 end_update(view);
2250 }
b801d8b2
JF
2251 break;
2252
4a2909a7 2253 case REQ_SHOW_VERSION:
6cb291b7 2254 report("%s (built %s)", VERSION, __DATE__);
b801d8b2
JF
2255 return TRUE;
2256
fac7db6c
JF
2257 case REQ_SCREEN_RESIZE:
2258 resize_display();
2259 /* Fall-through */
4a2909a7 2260 case REQ_SCREEN_REDRAW:
20bb5e18 2261 redraw_display();
4a2909a7
JF
2262 break;
2263
1d754561 2264 case REQ_NONE:
b801d8b2
JF
2265 doupdate();
2266 return TRUE;
2267
4f9b667a 2268 case REQ_VIEW_CLOSE:
2fcf5401
JF
2269 /* XXX: Mark closed views by letting view->parent point to the
2270 * view itself. Parents to closed view should never be
2271 * followed. */
2272 if (view->parent &&
2273 view->parent->parent != view->parent) {
4f9b667a
JF
2274 memset(display, 0, sizeof(display));
2275 current_view = 0;
f6da0b66 2276 display[current_view] = view->parent;
2fcf5401 2277 view->parent = view;
4f9b667a
JF
2278 resize_display();
2279 redraw_display();
2280 break;
2281 }
2282 /* Fall-through */
b801d8b2
JF
2283 case REQ_QUIT:
2284 return FALSE;
2285
2286 default:
2e8488b4 2287 /* An unknown key will show most commonly used commands. */
468876c9 2288 report("Unknown key, press 'h' for help");
b801d8b2
JF
2289 return TRUE;
2290 }
2291
2292 return TRUE;
2293}
2294
2295
2296/*
ff26aa29 2297 * Pager backend
b801d8b2
JF
2298 */
2299
6b161b31 2300static bool
5dcf8064 2301pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
b801d8b2 2302{
fe7233c3
JF
2303 char *text = line->data;
2304 enum line_type type = line->type;
2305 int textlen = strlen(text);
78c70acd 2306 int attr;
b801d8b2 2307
6706b2ba
JF
2308 wmove(view->win, lineno, 0);
2309
5dcf8064 2310 if (selected) {
78c70acd 2311 type = LINE_CURSOR;
6706b2ba 2312 wchgat(view->win, -1, 0, type, NULL);
fd85fef1
JF
2313 }
2314
78c70acd 2315 attr = get_line_attr(type);
b801d8b2 2316 wattrset(view->win, attr);
b76c2afc 2317
6706b2ba
JF
2318 if (opt_line_number || opt_tab_size < TABSIZE) {
2319 static char spaces[] = " ";
2320 int col_offset = 0, col = 0;
2321
2322 if (opt_line_number) {
2323 unsigned long real_lineno = view->offset + lineno + 1;
82e78006 2324
6706b2ba
JF
2325 if (real_lineno == 1 ||
2326 (real_lineno % opt_num_interval) == 0) {
2327 wprintw(view->win, "%.*d", view->digits, real_lineno);
8855ada4 2328
6706b2ba
JF
2329 } else {
2330 waddnstr(view->win, spaces,
2331 MIN(view->digits, STRING_SIZE(spaces)));
2332 }
2333 waddstr(view->win, ": ");
2334 col_offset = view->digits + 2;
2335 }
8855ada4 2336
fe7233c3 2337 while (text && col_offset + col < view->width) {
6706b2ba 2338 int cols_max = view->width - col_offset - col;
fe7233c3 2339 char *pos = text;
6706b2ba 2340 int cols;
4c6fabc2 2341
fe7233c3
JF
2342 if (*text == '\t') {
2343 text++;
6706b2ba 2344 assert(sizeof(spaces) > TABSIZE);
fe7233c3 2345 pos = spaces;
6706b2ba 2346 cols = opt_tab_size - (col % opt_tab_size);
82e78006 2347
b76c2afc 2348 } else {
fe7233c3
JF
2349 text = strchr(text, '\t');
2350 cols = line ? text - pos : strlen(pos);
b76c2afc 2351 }
6706b2ba 2352
fe7233c3 2353 waddnstr(view->win, pos, MIN(cols, cols_max));
6706b2ba 2354 col += cols;
b76c2afc 2355 }
b76c2afc
JF
2356
2357 } else {
6706b2ba 2358 int col = 0, pos = 0;
b801d8b2 2359
fe7233c3
JF
2360 for (; pos < textlen && col < view->width; pos++, col++)
2361 if (text[pos] == '\t')
6706b2ba
JF
2362 col += TABSIZE - (col % TABSIZE) - 1;
2363
fe7233c3 2364 waddnstr(view->win, text, pos);
6706b2ba 2365 }
2e8488b4 2366
b801d8b2
JF
2367 return TRUE;
2368}
2369
dc23c0e3 2370static bool
d65ced0d 2371add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
dc23c0e3 2372{
17482b11 2373 char refbuf[SIZEOF_STR];
dc23c0e3
JF
2374 char *ref = NULL;
2375 FILE *pipe;
2376
d3c345f7 2377 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
dc23c0e3
JF
2378 return TRUE;
2379
2380 pipe = popen(refbuf, "r");
2381 if (!pipe)
2382 return TRUE;
2383
2384 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2385 ref = chomp_string(ref);
2386 pclose(pipe);
2387
2388 if (!ref || !*ref)
2389 return TRUE;
2390
2391 /* This is the only fatal call, since it can "corrupt" the buffer. */
17482b11 2392 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
dc23c0e3
JF
2393 return FALSE;
2394
2395 return TRUE;
2396}
2397
7b99a34c
JF
2398static void
2399add_pager_refs(struct view *view, struct line *line)
2400{
17482b11 2401 char buf[SIZEOF_STR];
c9ca1ec3 2402 char *commit_id = line->data + STRING_SIZE("commit ");
7b99a34c 2403 struct ref **refs;
d65ced0d 2404 size_t bufpos = 0, refpos = 0;
7b99a34c 2405 const char *sep = "Refs: ";
dc23c0e3 2406 bool is_tag = FALSE;
7b99a34c
JF
2407
2408 assert(line->type == LINE_COMMIT);
2409
c9ca1ec3 2410 refs = get_refs(commit_id);
dc23c0e3
JF
2411 if (!refs) {
2412 if (view == VIEW(REQ_VIEW_DIFF))
2413 goto try_add_describe_ref;
7b99a34c 2414 return;
dc23c0e3 2415 }
7b99a34c
JF
2416
2417 do {
cc2d1364 2418 struct ref *ref = refs[refpos];
e15ec88e
JF
2419 char *fmt = ref->tag ? "%s[%s]" :
2420 ref->remote ? "%s<%s>" : "%s%s";
7b99a34c 2421
cc2d1364
JF
2422 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2423 return;
7b99a34c 2424 sep = ", ";
dc23c0e3
JF
2425 if (ref->tag)
2426 is_tag = TRUE;
7b99a34c
JF
2427 } while (refs[refpos++]->next);
2428
dc23c0e3
JF
2429 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2430try_add_describe_ref:
d42c8a35 2431 /* Add <tag>-g<commit_id> "fake" reference. */
dc23c0e3
JF
2432 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2433 return;
2434 }
2435
d42c8a35
JF
2436 if (bufpos == 0)
2437 return;
2438
cc2d1364 2439 if (!realloc_lines(view, view->line_size + 1))
7b99a34c
JF
2440 return;
2441
0a0d8910 2442 add_line_text(view, buf, LINE_PP_REFS);
7b99a34c
JF
2443}
2444
6b161b31 2445static bool
701e4f5d 2446pager_read(struct view *view, char *data)
22f66b0a 2447{
0a0d8910 2448 struct line *line;
22f66b0a 2449
be04d936
JF
2450 if (!data)
2451 return TRUE;
2452
0a0d8910
JF
2453 line = add_line_text(view, data, get_line_type(data));
2454 if (!line)
7b99a34c 2455 return FALSE;
fe7233c3 2456
7b99a34c
JF
2457 if (line->type == LINE_COMMIT &&
2458 (view == VIEW(REQ_VIEW_DIFF) ||
2459 view == VIEW(REQ_VIEW_LOG)))
2460 add_pager_refs(view, line);
2461
22f66b0a
JF
2462 return TRUE;
2463}
2464
6b161b31 2465static bool
fe7233c3 2466pager_enter(struct view *view, struct line *line)
6b161b31 2467{
91e8e277 2468 int split = 0;
6b161b31 2469
9fbbd28f
JF
2470 if (line->type == LINE_COMMIT &&
2471 (view == VIEW(REQ_VIEW_LOG) ||
2472 view == VIEW(REQ_VIEW_PAGER))) {
91e8e277
JF
2473 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2474 split = 1;
67e48ac5
JF
2475 }
2476
91e8e277
JF
2477 /* Always scroll the view even if it was split. That way
2478 * you can use Enter to scroll through the log view and
2479 * split open each commit diff. */
2480 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2481
2482 /* FIXME: A minor workaround. Scrolling the view will call report("")
9d82d824
JF
2483 * but if we are scrolling a non-current view this won't properly
2484 * update the view title. */
91e8e277
JF
2485 if (split)
2486 update_view_title(view);
6b161b31
JF
2487
2488 return TRUE;
2489}
2490
4af34daa
JF
2491static bool
2492pager_grep(struct view *view, struct line *line)
2493{
2494 regmatch_t pmatch;
2495 char *text = line->data;
2496
2497 if (!*text)
2498 return FALSE;
2499
b77b2cb8 2500 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
4af34daa
JF
2501 return FALSE;
2502
2503 return TRUE;
2504}
2505
d720de4b
JF
2506static void
2507pager_select(struct view *view, struct line *line)
2508{
2509 if (line->type == LINE_COMMIT) {
035ba11f 2510 char *text = line->data + STRING_SIZE("commit ");
d720de4b 2511
035ba11f 2512 if (view != VIEW(REQ_VIEW_PAGER))
2463b4ea
JF
2513 string_copy_rev(view->ref, text);
2514 string_copy_rev(ref_commit, text);
d720de4b
JF
2515 }
2516}
2517
6b161b31 2518static struct view_ops pager_ops = {
6734f6b9 2519 "line",
6b161b31
JF
2520 pager_draw,
2521 pager_read,
2522 pager_enter,
4af34daa 2523 pager_grep,
d720de4b 2524 pager_select,
6b161b31
JF
2525};
2526
80ce96ea 2527
ff26aa29 2528/*
e733ee54
JF
2529 * Tree backend
2530 */
2531
4795d620 2532/* Parse output from git-ls-tree(1):
e733ee54
JF
2533 *
2534 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2535 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2536 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2537 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2538 */
2539
2540#define SIZEOF_TREE_ATTR \
2541 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2542
2543#define TREE_UP_FORMAT "040000 tree %s\t.."
2544
2545static int
2546tree_compare_entry(enum line_type type1, char *name1,
2547 enum line_type type2, char *name2)
2548{
2549 if (type1 != type2) {
2550 if (type1 == LINE_TREE_DIR)
2551 return -1;
2552 return 1;
2553 }
2554
2555 return strcmp(name1, name2);
2556}
2557
2558static bool
2559tree_read(struct view *view, char *text)
2560{
be04d936 2561 size_t textlen = text ? strlen(text) : 0;
e733ee54
JF
2562 char buf[SIZEOF_STR];
2563 unsigned long pos;
2564 enum line_type type;
f88a5319 2565 bool first_read = view->lines == 0;
e733ee54
JF
2566
2567 if (textlen <= SIZEOF_TREE_ATTR)
2568 return FALSE;
2569
2570 type = text[STRING_SIZE("100644 ")] == 't'
2571 ? LINE_TREE_DIR : LINE_TREE_FILE;
2572
f88a5319 2573 if (first_read) {
e733ee54 2574 /* Add path info line */
0a0d8910
JF
2575 if (!string_format(buf, "Directory path /%s", opt_path) ||
2576 !realloc_lines(view, view->line_size + 1) ||
2577 !add_line_text(view, buf, LINE_DEFAULT))
e733ee54
JF
2578 return FALSE;
2579
2580 /* Insert "link" to parent directory. */
0a0d8910
JF
2581 if (*opt_path) {
2582 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2583 !realloc_lines(view, view->line_size + 1) ||
2584 !add_line_text(view, buf, LINE_TREE_DIR))
2585 return FALSE;
2586 }
e733ee54
JF
2587 }
2588
2589 /* Strip the path part ... */
2590 if (*opt_path) {
2591 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2592 size_t striplen = strlen(opt_path);
2593 char *path = text + SIZEOF_TREE_ATTR;
2594
2595 if (pathlen > striplen)
2596 memmove(path, path + striplen,
2597 pathlen - striplen + 1);
2598 }
2599
2600 /* Skip "Directory ..." and ".." line. */
2601 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2602 struct line *line = &view->line[pos];
2603 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2604 char *path2 = text + SIZEOF_TREE_ATTR;
2605 int cmp = tree_compare_entry(line->type, path1, type, path2);
2606
2607 if (cmp <= 0)
2608 continue;
2609
2610 text = strdup(text);
2611 if (!text)
2612 return FALSE;
2613
2614 if (view->lines > pos)
2615 memmove(&view->line[pos + 1], &view->line[pos],
2616 (view->lines - pos) * sizeof(*line));
2617
2618 line = &view->line[pos];
2619 line->data = text;
2620 line->type = type;
2621 view->lines++;
2622 return TRUE;
2623 }
2624
0a0d8910 2625 if (!add_line_text(view, text, type))
e733ee54
JF
2626 return FALSE;
2627
f88a5319
JF
2628 /* Move the current line to the first tree entry. */
2629 if (first_read)
2630 view->lineno++;
2631
e733ee54
JF
2632 return TRUE;
2633}
2634
2635static bool
2636tree_enter(struct view *view, struct line *line)
2637{
aac64c17 2638 enum open_flags flags;
e733ee54
JF
2639 enum request request;
2640
2641 switch (line->type) {
2642 case LINE_TREE_DIR:
2643 /* Depending on whether it is a subdir or parent (updir?) link
2644 * mangle the path buffer. */
2645 if (line == &view->line[1] && *opt_path) {
2646 size_t path_len = strlen(opt_path);
2647 char *dirsep = opt_path + path_len - 1;
2648
2649 while (dirsep > opt_path && dirsep[-1] != '/')
2650 dirsep--;
2651
2652 dirsep[0] = 0;
2653
2654 } else {
d65ced0d 2655 size_t pathlen = strlen(opt_path);
4ea4ce91 2656 size_t origlen = pathlen;
4795d620 2657 char *data = line->data;
e733ee54
JF
2658 char *basename = data + SIZEOF_TREE_ATTR;
2659
4038038f 2660 if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
4ea4ce91
JF
2661 opt_path[origlen] = 0;
2662 return TRUE;
2663 }
e733ee54
JF
2664 }
2665
2666 /* Trees and subtrees share the same ID, so they are not not
2667 * unique like blobs. */
aac64c17 2668 flags = OPEN_RELOAD;
e733ee54
JF
2669 request = REQ_VIEW_TREE;
2670 break;
2671
2672 case LINE_TREE_FILE:
aac64c17 2673 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
e733ee54
JF
2674 request = REQ_VIEW_BLOB;
2675 break;
2676
2677 default:
2678 return TRUE;
2679 }
2680
2681 open_view(view, request, flags);
2682
e733ee54
JF
2683 return TRUE;
2684}
2685
d720de4b
JF
2686static void
2687tree_select(struct view *view, struct line *line)
2688{
035ba11f 2689 char *text = line->data + STRING_SIZE("100644 blob ");
73c76ef5
JF
2690
2691 if (line->type == LINE_TREE_FILE) {
2463b4ea 2692 string_copy_rev(ref_blob, text);
d720de4b 2693
ebbaf4fe
JF
2694 } else if (line->type != LINE_TREE_DIR) {
2695 return;
d720de4b 2696 }
ebbaf4fe 2697
2463b4ea 2698 string_copy_rev(view->ref, text);
d720de4b
JF
2699}
2700
e733ee54
JF
2701static struct view_ops tree_ops = {
2702 "file",
2703 pager_draw,
2704 tree_read,
2705 tree_enter,
2706 pager_grep,
d720de4b 2707 tree_select,
e733ee54
JF
2708};
2709
2710static bool
2711blob_read(struct view *view, char *line)
2712{
0a0d8910 2713 return add_line_text(view, line, LINE_DEFAULT);
e733ee54
JF
2714}
2715
2716static struct view_ops blob_ops = {
2717 "line",
2718 pager_draw,
2719 blob_read,
2720 pager_enter,
2721 pager_grep,
d720de4b 2722 pager_select,
e733ee54
JF
2723};
2724
2725
2726/*
ccc33449 2727 * Revision graph
ff26aa29
JF
2728 */
2729
2730struct commit {
10446330 2731 char id[SIZEOF_REV]; /* SHA1 ID. */
aea510c8 2732 char title[128]; /* First line of the commit message. */
54efb62b
JF
2733 char author[75]; /* Author of the commit. */
2734 struct tm time; /* Date from the author ident. */
2735 struct ref **refs; /* Repository references. */
2736 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
2737 size_t graph_size; /* The width of the graph array. */
ff26aa29 2738};
c34d9c9f 2739
ccc33449
JF
2740/* Size of rev graph with no "padding" columns */
2741#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
2b757533 2742
2ce5c87c
JF
2743struct rev_graph {
2744 struct rev_graph *prev, *next, *parents;
2b757533
JF
2745 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
2746 size_t size;
88757ebd
JF
2747 struct commit *commit;
2748 size_t pos;
2b757533
JF
2749};
2750
2b757533 2751/* Parents of the commit being visualized. */
446a5c36 2752static struct rev_graph graph_parents[4];
c8d60a25 2753
c65a501a 2754/* The current stack of revisions on the graph. */
446a5c36
JF
2755static struct rev_graph graph_stacks[4] = {
2756 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
c65a501a 2757 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
446a5c36
JF
2758 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
2759 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
c65a501a
JF
2760};
2761
9e43b9cd 2762static inline bool
2ce5c87c 2763graph_parent_is_merge(struct rev_graph *graph)
9e43b9cd
JF
2764{
2765 return graph->parents->size > 1;
2766}
2767
88757ebd 2768static inline void
2ce5c87c 2769append_to_rev_graph(struct rev_graph *graph, chtype symbol)
88757ebd 2770{
2c27faac
JF
2771 struct commit *commit = graph->commit;
2772
2773 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
2774 commit->graph[commit->graph_size++] = symbol;
88757ebd
JF
2775}
2776
2b757533 2777static void
2ce5c87c 2778done_rev_graph(struct rev_graph *graph)
987890af
JF
2779{
2780 if (graph_parent_is_merge(graph) &&
2781 graph->pos < graph->size - 1 &&
2782 graph->next->size == graph->size + graph->parents->size - 1) {
2783 size_t i = graph->pos + graph->parents->size - 1;
2784
2785 graph->commit->graph_size = i * 2;
2786 while (i < graph->next->size - 1) {
2787 append_to_rev_graph(graph, ' ');
2788 append_to_rev_graph(graph, '\\');
2789 i++;
2790 }
2791 }
2792
2793 graph->size = graph->pos = 0;
2794 graph->commit = NULL;
2795 memset(graph->parents, 0, sizeof(*graph->parents));
2796}
2797
2798static void
2ce5c87c 2799push_rev_graph(struct rev_graph *graph, char *parent)
2b757533 2800{
2fe894e6
JF
2801 int i;
2802
2803 /* "Collapse" duplicate parents lines.
2804 *
2805 * FIXME: This needs to also update update the drawn graph but
2806 * for now it just serves as a method for pruning graph lines. */
2807 for (i = 0; i < graph->size; i++)
2808 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
2809 return;
2b757533 2810
2ce5c87c
JF
2811 if (graph->size < SIZEOF_REVITEMS) {
2812 string_ncopy(graph->rev[graph->size++], parent, SIZEOF_REV);
2b757533
JF
2813 }
2814}
2815
92507a24
JF
2816static chtype
2817get_rev_graph_symbol(struct rev_graph *graph)
2b757533 2818{
92507a24 2819 chtype symbol;
2b757533 2820
c65a501a 2821 if (graph->parents->size == 0)
c8d60a25 2822 symbol = REVGRAPH_INIT;
18ffaa23 2823 else if (graph_parent_is_merge(graph))
c8d60a25 2824 symbol = REVGRAPH_MERGE;
c65a501a 2825 else if (graph->pos >= graph->size)
c8d60a25 2826 symbol = REVGRAPH_BRANCH;
2b757533 2827 else
c8d60a25 2828 symbol = REVGRAPH_COMMIT;
1dcb3bec 2829
92507a24
JF
2830 return symbol;
2831}
2832
2833static void
2834draw_rev_graph(struct rev_graph *graph)
2835{
e937c2c8
JF
2836 struct rev_filler {
2837 chtype separator, line;
2838 };
2839 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
2840 static struct rev_filler fillers[] = {
2841 { ' ', REVGRAPH_LINE },
2842 { '`', '.' },
2843 { '\'', ' ' },
2844 { '/', ' ' },
e937c2c8 2845 };
92507a24 2846 chtype symbol = get_rev_graph_symbol(graph);
e937c2c8 2847 struct rev_filler *filler;
92507a24
JF
2848 size_t i;
2849
e937c2c8 2850 filler = &fillers[DEFAULT];
110e948e 2851
c65a501a 2852 for (i = 0; i < graph->pos; i++) {
e937c2c8 2853 append_to_rev_graph(graph, filler->line);
9e43b9cd 2854 if (graph_parent_is_merge(graph->prev) &&
e937c2c8
JF
2855 graph->prev->pos == i)
2856 filler = &fillers[RSHARP];
2857
2858 append_to_rev_graph(graph, filler->separator);
110e948e
JF
2859 }
2860
92507a24 2861 /* Place the symbol for this revision. */
c65a501a 2862 append_to_rev_graph(graph, symbol);
2b757533 2863
e937c2c8
JF
2864 if (graph->prev->size > graph->size)
2865 filler = &fillers[RDIAG];
2866 else
2867 filler = &fillers[DEFAULT];
2868
c8d60a25 2869 i++;
2b757533 2870
c65a501a 2871 for (; i < graph->size; i++) {
e937c2c8
JF
2872 append_to_rev_graph(graph, filler->separator);
2873 append_to_rev_graph(graph, filler->line);
2874 if (graph_parent_is_merge(graph->prev) &&
2875 i < graph->prev->pos + graph->parents->size)
2876 filler = &fillers[RSHARP];
2877 if (graph->prev->size > graph->size)
2878 filler = &fillers[LDIAG];
c65a501a
JF
2879 }
2880
2881 if (graph->prev->size > graph->size) {
e937c2c8
JF
2882 append_to_rev_graph(graph, filler->separator);
2883 if (filler->line != ' ')
2884 append_to_rev_graph(graph, filler->line);
2b757533 2885 }
b5d8f208
JF
2886}
2887
61eed810
JF
2888/* Prepare the next rev graph */
2889static void
2890prepare_rev_graph(struct rev_graph *graph)
b5d8f208 2891{
b5d8f208
JF
2892 size_t i;
2893
320df4ea 2894 /* First, traverse all lines of revisions up to the active one. */
c65a501a
JF
2895 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
2896 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
b5d8f208 2897 break;
b5d8f208 2898
2ce5c87c 2899 push_rev_graph(graph->next, graph->rev[graph->pos]);
b5d8f208
JF
2900 }
2901
320df4ea 2902 /* Interleave the new revision parent(s). */
c65a501a 2903 for (i = 0; i < graph->parents->size; i++)
2ce5c87c 2904 push_rev_graph(graph->next, graph->parents->rev[i]);
b5d8f208 2905
320df4ea 2906 /* Lastly, put any remaining revisions. */
c65a501a 2907 for (i = graph->pos + 1; i < graph->size; i++)
2ce5c87c 2908 push_rev_graph(graph->next, graph->rev[i]);
61eed810
JF
2909}
2910
2911static void
2912update_rev_graph(struct rev_graph *graph)
2913{
446a5c36
JF
2914 /* If this is the finalizing update ... */
2915 if (graph->commit)
2916 prepare_rev_graph(graph);
2917
2918 /* Graph visualization needs a one rev look-ahead,
2919 * so the first update doesn't visualize anything. */
2920 if (!graph->prev->commit)
2921 return;
c65a501a 2922
61eed810
JF
2923 draw_rev_graph(graph->prev);
2924 done_rev_graph(graph->prev->prev);
2b757533
JF
2925}
2926
ccc33449
JF
2927
2928/*
2929 * Main view backend
2930 */
2931
2932static bool
2933main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2934{
2935 char buf[DATE_COLS + 1];
2936 struct commit *commit = line->data;
2937 enum line_type type;
2938 int col = 0;
2939 size_t timelen;
2940 size_t authorlen;
2941 int trimmed = 1;
2942
2943 if (!*commit->author)
2944 return FALSE;
2945
2946 wmove(view->win, lineno, col);
2947
2948 if (selected) {
2949 type = LINE_CURSOR;
2950 wattrset(view->win, get_line_attr(type));
2951 wchgat(view->win, -1, 0, type, NULL);
2952
2953 } else {
2954 type = LINE_MAIN_COMMIT;
2955 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2956 }
2957
2958 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2959 waddnstr(view->win, buf, timelen);
2960 waddstr(view->win, " ");
2961
2962 col += DATE_COLS;
2963 wmove(view->win, lineno, col);
2964 if (type != LINE_CURSOR)
2965 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2966
2967 if (opt_utf8) {
2968 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2969 } else {
2970 authorlen = strlen(commit->author);
2971 if (authorlen > AUTHOR_COLS - 2) {
2972 authorlen = AUTHOR_COLS - 2;
2973 trimmed = 1;
2974 }
2975 }
2976
2977 if (trimmed) {
2978 waddnstr(view->win, commit->author, authorlen);
2979 if (type != LINE_CURSOR)
2980 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2981 waddch(view->win, '~');
2982 } else {
2983 waddstr(view->win, commit->author);
2984 }
2985
2986 col += AUTHOR_COLS;
2987 if (type != LINE_CURSOR)
2988 wattrset(view->win, A_NORMAL);
2989
2990 if (opt_rev_graph && commit->graph_size) {
2991 size_t i;
2992
2993 wmove(view->win, lineno, col);
2994 /* Using waddch() instead of waddnstr() ensures that
2995 * they'll be rendered correctly for the cursor line. */
2996 for (i = 0; i < commit->graph_size; i++)
2997 waddch(view->win, commit->graph[i]);
2998
8716b9ed 2999 waddch(view->win, ' ');
ccc33449
JF
3000 col += commit->graph_size + 1;
3001 }
3002
3003 wmove(view->win, lineno, col);
3004
3005 if (commit->refs) {
3006 size_t i = 0;
3007
3008 do {
3009 if (type == LINE_CURSOR)
3010 ;
3011 else if (commit->refs[i]->tag)
3012 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
e15ec88e
JF
3013 else if (commit->refs[i]->remote)
3014 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
ccc33449
JF
3015 else
3016 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3017 waddstr(view->win, "[");
3018 waddstr(view->win, commit->refs[i]->name);
3019 waddstr(view->win, "]");
3020 if (type != LINE_CURSOR)
3021 wattrset(view->win, A_NORMAL);
3022 waddstr(view->win, " ");
3023 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3024 } while (commit->refs[i++]->next);
3025 }
3026
3027 if (type != LINE_CURSOR)
3028 wattrset(view->win, get_line_attr(type));
3029
3030 {
3031 int titlelen = strlen(commit->title);
3032
3033 if (col + titlelen > view->width)
3034 titlelen = view->width - col;
3035
3036 waddnstr(view->win, commit->title, titlelen);
3037 }
3038
3039 return TRUE;
3040}
3041
4c6fabc2 3042/* Reads git log --pretty=raw output and parses it into the commit struct. */
6b161b31 3043static bool
701e4f5d 3044main_read(struct view *view, char *line)
22f66b0a 3045{
2ce5c87c 3046 static struct rev_graph *graph = graph_stacks;
be04d936 3047 enum line_type type;
701e4f5d
JF
3048 struct commit *commit = view->lines
3049 ? view->line[view->lines - 1].data : NULL;
22f66b0a 3050
be04d936 3051 if (!line) {
446a5c36 3052 update_rev_graph(graph);
be04d936
JF
3053 return TRUE;
3054 }
3055
3056 type = get_line_type(line);
3057
78c70acd
JF
3058 switch (type) {
3059 case LINE_COMMIT:
22f66b0a
JF
3060 commit = calloc(1, sizeof(struct commit));
3061 if (!commit)
3062 return FALSE;
3063
4c6fabc2 3064 line += STRING_SIZE("commit ");
b76c2afc 3065
fe7233c3 3066 view->line[view->lines++].data = commit;
2463b4ea 3067 string_copy_rev(commit->id, line);
c34d9c9f 3068 commit->refs = get_refs(commit->id);
c65a501a 3069 graph->commit = commit;
2b757533
JF
3070 break;
3071
3072 case LINE_PARENT:
3073 if (commit) {
3074 line += STRING_SIZE("parent ");
2ce5c87c 3075 push_rev_graph(graph->parents, line);
2b757533 3076 }
78c70acd 3077 break;
22f66b0a 3078
8855ada4 3079 case LINE_AUTHOR:
b76c2afc 3080 {
19c3ac60
JF
3081 /* Parse author lines where the name may be empty:
3082 * author <email@address.tld> 1138474660 +0100
3083 */
4c6fabc2 3084 char *ident = line + STRING_SIZE("author ");
19c3ac60
JF
3085 char *nameend = strchr(ident, '<');
3086 char *emailend = strchr(ident, '>');
b76c2afc 3087
19c3ac60 3088 if (!commit || !nameend || !emailend)
fe7233c3
JF
3089 break;
3090
c65a501a
JF
3091 update_rev_graph(graph);
3092 graph = graph->next;
2b757533 3093
19c3ac60
JF
3094 *nameend = *emailend = 0;
3095 ident = chomp_string(ident);
3096 if (!*ident) {
3097 ident = chomp_string(nameend + 1);
3098 if (!*ident)
3099 ident = "Unknown";
b76c2afc
JF
3100 }
3101
82e78006 3102 string_copy(commit->author, ident);
b76c2afc 3103
4c6fabc2 3104 /* Parse epoch and timezone */
19c3ac60
JF
3105 if (emailend[1] == ' ') {
3106 char *secs = emailend + 2;
3107 char *zone = strchr(secs, ' ');
3108 time_t time = (time_t) atol(secs);
b76c2afc 3109
4c6fabc2 3110 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
b76c2afc
JF
3111 long tz;
3112
3113 zone++;
3114 tz = ('0' - zone[1]) * 60 * 60 * 10;
3115 tz += ('0' - zone[2]) * 60 * 60;
3116 tz += ('0' - zone[3]) * 60;
3117 tz += ('0' - zone[4]) * 60;
3118
3119 if (zone[0] == '-')
3120 tz = -tz;
3121
3122 time -= tz;
3123 }
19c3ac60 3124
b76c2afc
JF
3125 gmtime_r(&time, &commit->time);
3126 }
3127 break;
3128 }
78c70acd 3129 default:
701e4f5d 3130 if (!commit)
2e8488b4
JF
3131 break;
3132
3133 /* Fill in the commit title if it has not already been set. */
2e8488b4
JF
3134 if (commit->title[0])
3135 break;
3136
3137 /* Require titles to start with a non-space character at the
3138 * offset used by git log. */
9073c64a
JF
3139 if (strncmp(line, " ", 4))
3140 break;
3141 line += 4;
3142 /* Well, if the title starts with a whitespace character,
3143 * try to be forgiving. Otherwise we end up with no title. */
3144 while (isspace(*line))
3145 line++;
3146 if (*line == '\0')
82e78006 3147 break;
9073c64a
JF
3148 /* FIXME: More graceful handling of titles; append "..." to
3149 * shortened titles, etc. */
82e78006 3150
9073c64a 3151 string_copy(commit->title, line);
22f66b0a
JF
3152 }
3153
3154 return TRUE;
3155}
3156
6b161b31 3157static bool
fe7233c3 3158main_enter(struct view *view, struct line *line)
b801d8b2 3159{
b3a54cba
JF
3160 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3161
3162 open_view(view, REQ_VIEW_DIFF, flags);
6b161b31 3163 return TRUE;
b801d8b2
JF
3164}
3165
4af34daa
JF
3166static bool
3167main_grep(struct view *view, struct line *line)
3168{
3169 struct commit *commit = line->data;
3170 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3171 char buf[DATE_COLS + 1];
3172 regmatch_t pmatch;
3173
3174 for (state = S_TITLE; state < S_END; state++) {
3175 char *text;
3176
3177 switch (state) {
3178 case S_TITLE: text = commit->title; break;
3179 case S_AUTHOR: text = commit->author; break;
3180 case S_DATE:
3181 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3182 continue;
3183 text = buf;
3184 break;
3185
3186 default:
3187 return FALSE;
3188 }
3189
b77b2cb8 3190 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4af34daa
JF
3191 return TRUE;
3192 }
3193
3194 return FALSE;
3195}
3196
d720de4b
JF
3197static void
3198main_select(struct view *view, struct line *line)
3199{
3200 struct commit *commit = line->data;
3201
2463b4ea
JF
3202 string_copy_rev(view->ref, commit->id);
3203 string_copy_rev(ref_commit, view->ref);
d720de4b
JF
3204}
3205
6b161b31 3206static struct view_ops main_ops = {
6734f6b9 3207 "commit",
6b161b31
JF
3208 main_draw,
3209 main_read,
3210 main_enter,
4af34daa 3211 main_grep,
d720de4b 3212 main_select,
6b161b31 3213};
2e8488b4 3214
c34d9c9f 3215
6b161b31 3216/*
10e290ee
JF
3217 * Unicode / UTF-8 handling
3218 *
3219 * NOTE: Much of the following code for dealing with unicode is derived from
3220 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3221 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3222 */
3223
3224/* I've (over)annotated a lot of code snippets because I am not entirely
3225 * confident that the approach taken by this small UTF-8 interface is correct.
3226 * --jonas */
3227
3228static inline int
3229unicode_width(unsigned long c)
3230{
3231 if (c >= 0x1100 &&
3232 (c <= 0x115f /* Hangul Jamo */
3233 || c == 0x2329
3234 || c == 0x232a
3235 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
f97f4012 3236 /* CJK ... Yi */
10e290ee
JF
3237 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
3238 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
3239 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
3240 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
3241 || (c >= 0xffe0 && c <= 0xffe6)
3242 || (c >= 0x20000 && c <= 0x2fffd)
3243 || (c >= 0x30000 && c <= 0x3fffd)))
3244 return 2;
3245
3246 return 1;
3247}
3248
3249/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3250 * Illegal bytes are set one. */
3251static const unsigned char utf8_bytes[256] = {
3252 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,
3253 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,
3254 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,
3255 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,
3256 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,
3257 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,
3258 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,
3259 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,
3260};
3261
3262/* Decode UTF-8 multi-byte representation into a unicode character. */
3263static inline unsigned long
3264utf8_to_unicode(const char *string, size_t length)
3265{
3266 unsigned long unicode;
3267
3268 switch (length) {
3269 case 1:
3270 unicode = string[0];
3271 break;
3272 case 2:
3273 unicode = (string[0] & 0x1f) << 6;
3274 unicode += (string[1] & 0x3f);
3275 break;
3276 case 3:
3277 unicode = (string[0] & 0x0f) << 12;
3278 unicode += ((string[1] & 0x3f) << 6);
3279 unicode += (string[2] & 0x3f);
3280 break;
3281 case 4:
3282 unicode = (string[0] & 0x0f) << 18;
3283 unicode += ((string[1] & 0x3f) << 12);
3284 unicode += ((string[2] & 0x3f) << 6);
3285 unicode += (string[3] & 0x3f);
3286 break;
3287 case 5:
3288 unicode = (string[0] & 0x0f) << 24;
3289 unicode += ((string[1] & 0x3f) << 18);
3290 unicode += ((string[2] & 0x3f) << 12);
3291 unicode += ((string[3] & 0x3f) << 6);
3292 unicode += (string[4] & 0x3f);
3293 break;
68b6e0eb 3294 case 6:
10e290ee
JF
3295 unicode = (string[0] & 0x01) << 30;
3296 unicode += ((string[1] & 0x3f) << 24);
3297 unicode += ((string[2] & 0x3f) << 18);
3298 unicode += ((string[3] & 0x3f) << 12);
3299 unicode += ((string[4] & 0x3f) << 6);
3300 unicode += (string[5] & 0x3f);
3301 break;
3302 default:
3303 die("Invalid unicode length");
3304 }
3305
3306 /* Invalid characters could return the special 0xfffd value but NUL
3307 * should be just as good. */
3308 return unicode > 0xffff ? 0 : unicode;
3309}
3310
3311/* Calculates how much of string can be shown within the given maximum width
3312 * and sets trimmed parameter to non-zero value if all of string could not be
3313 * shown.
3314 *
3315 * Additionally, adds to coloffset how many many columns to move to align with
3316 * the expected position. Takes into account how multi-byte and double-width
3317 * characters will effect the cursor position.
3318 *
3319 * Returns the number of bytes to output from string to satisfy max_width. */
3320static size_t
3321utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3322{
3323 const char *start = string;
3324 const char *end = strchr(string, '\0');
3325 size_t mbwidth = 0;
3326 size_t width = 0;
3327
3328 *trimmed = 0;
3329
3330 while (string < end) {
3331 int c = *(unsigned char *) string;
3332 unsigned char bytes = utf8_bytes[c];
3333 size_t ucwidth;
3334 unsigned long unicode;
3335
3336 if (string + bytes > end)
3337 break;
3338
3339 /* Change representation to figure out whether
3340 * it is a single- or double-width character. */
3341
3342 unicode = utf8_to_unicode(string, bytes);
3343 /* FIXME: Graceful handling of invalid unicode character. */
3344 if (!unicode)
3345 break;
3346
3347 ucwidth = unicode_width(unicode);
3348 width += ucwidth;
3349 if (width > max_width) {
3350 *trimmed = 1;
3351 break;
3352 }
3353
3354 /* The column offset collects the differences between the
3355 * number of bytes encoding a character and the number of
3356 * columns will be used for rendering said character.
3357 *
3358 * So if some character A is encoded in 2 bytes, but will be
3359 * represented on the screen using only 1 byte this will and up
3360 * adding 1 to the multi-byte column offset.
3361 *
3362 * Assumes that no double-width character can be encoding in
3363 * less than two bytes. */
3364 if (bytes > ucwidth)
3365 mbwidth += bytes - ucwidth;
3366
3367 string += bytes;
3368 }
3369
3370 *coloffset += mbwidth;
3371
3372 return string - start;
3373}
3374
3375
3376/*
6b161b31
JF
3377 * Status management
3378 */
2e8488b4 3379
8855ada4 3380/* Whether or not the curses interface has been initialized. */
68b6e0eb 3381static bool cursed = FALSE;
8855ada4 3382
6b161b31
JF
3383/* The status window is used for polling keystrokes. */
3384static WINDOW *status_win;
4a2909a7 3385
21be28fb
JF
3386static bool status_empty = TRUE;
3387
2e8488b4 3388/* Update status and title window. */
4a2909a7
JF
3389static void
3390report(const char *msg, ...)
3391{
6706b2ba 3392 struct view *view = display[current_view];
b76c2afc 3393
ab4af23e
JF
3394 if (input_mode)
3395 return;
3396
21be28fb 3397 if (!status_empty || *msg) {
6706b2ba 3398 va_list args;
4a2909a7 3399
6706b2ba 3400 va_start(args, msg);
4b76734f 3401
6706b2ba
JF
3402 wmove(status_win, 0, 0);
3403 if (*msg) {
3404 vwprintw(status_win, msg, args);
21be28fb 3405 status_empty = FALSE;
6706b2ba 3406 } else {
21be28fb 3407 status_empty = TRUE;
6706b2ba 3408 }
390a8262 3409 wclrtoeol(status_win);
6706b2ba 3410 wrefresh(status_win);
b801d8b2 3411
6706b2ba
JF
3412 va_end(args);
3413 }
3414
3415 update_view_title(view);
2bee3bde 3416 update_display_cursor(view);
b801d8b2
JF
3417}
3418
6b161b31
JF
3419/* Controls when nodelay should be in effect when polling user input. */
3420static void
1ba2ae4b 3421set_nonblocking_input(bool loading)
b801d8b2 3422{
6706b2ba 3423 static unsigned int loading_views;
b801d8b2 3424
6706b2ba
JF
3425 if ((loading == FALSE && loading_views-- == 1) ||
3426 (loading == TRUE && loading_views++ == 0))
1ba2ae4b 3427 nodelay(status_win, loading);
6b161b31
JF
3428}
3429
3430static void
3431init_display(void)
3432{
3433 int x, y;
b76c2afc 3434
6908bdbd
JF
3435 /* Initialize the curses library */
3436 if (isatty(STDIN_FILENO)) {
8855ada4 3437 cursed = !!initscr();
6908bdbd
JF
3438 } else {
3439 /* Leave stdin and stdout alone when acting as a pager. */
3440 FILE *io = fopen("/dev/tty", "r+");
3441
e6f60674
JF
3442 if (!io)
3443 die("Failed to open /dev/tty");
8855ada4 3444 cursed = !!newterm(NULL, io, io);
6908bdbd
JF
3445 }
3446
8855ada4
JF
3447 if (!cursed)
3448 die("Failed to initialize curses");
3449
2e8488b4
JF
3450 nonl(); /* Tell curses not to do NL->CR/NL on output */
3451 cbreak(); /* Take input chars one at a time, no wait for \n */
3452 noecho(); /* Don't echo input */
b801d8b2 3453 leaveok(stdscr, TRUE);
b801d8b2
JF
3454
3455 if (has_colors())
3456 init_colors();
3457
3458 getmaxyx(stdscr, y, x);
3459 status_win = newwin(1, 0, y - 1, 0);
3460 if (!status_win)
3461 die("Failed to create status window");
3462
3463 /* Enable keyboard mapping */
3464 keypad(status_win, TRUE);
78c70acd 3465 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6b161b31
JF
3466}
3467
4af34daa 3468static char *
cb9e48c1 3469read_prompt(const char *prompt)
ef5404a4
JF
3470{
3471 enum { READING, STOP, CANCEL } status = READING;
9e21ce5c 3472 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
ef5404a4
JF
3473 int pos = 0;
3474
3475 while (status == READING) {
3476 struct view *view;
3477 int i, key;
3478
ab4af23e
JF
3479 input_mode = TRUE;
3480
699ae55b 3481 foreach_view (view, i)
ef5404a4
JF
3482 update_view(view);
3483
ab4af23e
JF
3484 input_mode = FALSE;
3485
3486 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
3487 wclrtoeol(status_win);
3488
ef5404a4
JF
3489 /* Refresh, accept single keystroke of input */
3490 key = wgetch(status_win);
3491 switch (key) {
3492 case KEY_RETURN:
3493 case KEY_ENTER:
3494 case '\n':
3495 status = pos ? STOP : CANCEL;
3496 break;
3497
3498 case KEY_BACKSPACE:
3499 if (pos > 0)
3500 pos--;
3501 else
3502 status = CANCEL;
3503 break;
3504
3505 case KEY_ESC:
3506 status = CANCEL;
3507 break;
3508
3509 case ERR:
3510 break;
3511
3512 default:
3513 if (pos >= sizeof(buf)) {
3514 report("Input string too long");
9e21ce5c 3515 return NULL;
ef5404a4
JF
3516 }
3517
3518 if (isprint(key))
3519 buf[pos++] = (char) key;
3520 }
3521 }
3522
7a06ebdf
JF
3523 /* Clear the status window */
3524 status_empty = FALSE;
3525 report("");
3526
3527 if (status == CANCEL)
9e21ce5c 3528 return NULL;
ef5404a4
JF
3529
3530 buf[pos++] = 0;
ef5404a4 3531
9e21ce5c 3532 return buf;
ef5404a4 3533}
c34d9c9f
JF
3534
3535/*
3536 * Repository references
3537 */
3538
3539static struct ref *refs;
3a91b75e 3540static size_t refs_size;
c34d9c9f 3541
1307df1a
JF
3542/* Id <-> ref store */
3543static struct ref ***id_refs;
3544static size_t id_refs_size;
3545
c34d9c9f
JF
3546static struct ref **
3547get_refs(char *id)
3548{
1307df1a
JF
3549 struct ref ***tmp_id_refs;
3550 struct ref **ref_list = NULL;
3551 size_t ref_list_size = 0;
c34d9c9f
JF
3552 size_t i;
3553
1307df1a
JF
3554 for (i = 0; i < id_refs_size; i++)
3555 if (!strcmp(id, id_refs[i][0]->id))
3556 return id_refs[i];
3557
3558 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3559 if (!tmp_id_refs)
3560 return NULL;
3561
3562 id_refs = tmp_id_refs;
3563
c34d9c9f
JF
3564 for (i = 0; i < refs_size; i++) {
3565 struct ref **tmp;
3566
3567 if (strcmp(id, refs[i].id))
3568 continue;
3569
1307df1a 3570 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
c34d9c9f 3571 if (!tmp) {
1307df1a
JF
3572 if (ref_list)
3573 free(ref_list);
c34d9c9f
JF
3574 return NULL;
3575 }
3576
1307df1a
JF
3577 ref_list = tmp;
3578 if (ref_list_size > 0)
3579 ref_list[ref_list_size - 1]->next = 1;
3580 ref_list[ref_list_size] = &refs[i];
3af8774e
JF
3581
3582 /* XXX: The properties of the commit chains ensures that we can
3583 * safely modify the shared ref. The repo references will
3584 * always be similar for the same id. */
1307df1a
JF
3585 ref_list[ref_list_size]->next = 0;
3586 ref_list_size++;
c34d9c9f
JF
3587 }
3588
1307df1a
JF
3589 if (ref_list)
3590 id_refs[id_refs_size++] = ref_list;
3591
3592 return ref_list;
c34d9c9f
JF
3593}
3594
3595static int
d0cea5f9 3596read_ref(char *id, int idlen, char *name, int namelen)
c34d9c9f 3597{
d0cea5f9
JF
3598 struct ref *ref;
3599 bool tag = FALSE;
e15ec88e 3600 bool remote = FALSE;
d0cea5f9 3601
8b0297ae
JF
3602 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3603 /* Commits referenced by tags has "^{}" appended. */
3604 if (name[namelen - 1] != '}')
3605 return OK;
3606
d0cea5f9
JF
3607 while (namelen > 0 && name[namelen] != '^')
3608 namelen--;
c34d9c9f 3609
d0cea5f9 3610 tag = TRUE;
8b0297ae
JF
3611 namelen -= STRING_SIZE("refs/tags/");
3612 name += STRING_SIZE("refs/tags/");
c34d9c9f 3613
e15ec88e
JF
3614 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
3615 remote = TRUE;
3616 namelen -= STRING_SIZE("refs/remotes/");
3617 name += STRING_SIZE("refs/remotes/");
3618
d0cea5f9 3619 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
8b0297ae
JF
3620 namelen -= STRING_SIZE("refs/heads/");
3621 name += STRING_SIZE("refs/heads/");
c34d9c9f 3622
d0cea5f9
JF
3623 } else if (!strcmp(name, "HEAD")) {
3624 return OK;
3625 }
6706b2ba 3626
d0cea5f9
JF
3627 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3628 if (!refs)
3629 return ERR;
c34d9c9f 3630
d0cea5f9 3631 ref = &refs[refs_size++];
8b0297ae 3632 ref->name = malloc(namelen + 1);
d0cea5f9
JF
3633 if (!ref->name)
3634 return ERR;
3af8774e 3635
8b0297ae
JF
3636 strncpy(ref->name, name, namelen);
3637 ref->name[namelen] = 0;
d0cea5f9 3638 ref->tag = tag;
e15ec88e 3639 ref->remote = remote;
2463b4ea 3640 string_copy_rev(ref->id, id);
3af8774e 3641
d0cea5f9
JF
3642 return OK;
3643}
c34d9c9f 3644
d0cea5f9
JF
3645static int
3646load_refs(void)
3647{
3648 const char *cmd_env = getenv("TIG_LS_REMOTE");
3649 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
c34d9c9f 3650
4a63c884 3651 return read_properties(popen(cmd, "r"), "\t", read_ref);
d0cea5f9 3652}
c34d9c9f 3653
d0cea5f9 3654static int
14c778a6 3655read_repo_config_option(char *name, int namelen, char *value, int valuelen)
d0cea5f9 3656{
22913179 3657 if (!strcmp(name, "i18n.commitencoding"))
d0cea5f9 3658 string_copy(opt_encoding, value);
c34d9c9f 3659
c34d9c9f
JF
3660 return OK;
3661}
3662
4670cf89 3663static int
14c778a6 3664load_repo_config(void)
4670cf89 3665{
66749723 3666 return read_properties(popen("git repo-config --list", "r"),
14c778a6 3667 "=", read_repo_config_option);
d0cea5f9
JF
3668}
3669
3670static int
4a63c884 3671read_properties(FILE *pipe, const char *separators,
d0cea5f9
JF
3672 int (*read_property)(char *, int, char *, int))
3673{
4670cf89
JF
3674 char buffer[BUFSIZ];
3675 char *name;
d0cea5f9 3676 int state = OK;
4670cf89
JF
3677
3678 if (!pipe)
3679 return ERR;
3680
d0cea5f9 3681 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4a63c884
JF
3682 char *value;
3683 size_t namelen;
3684 size_t valuelen;
4670cf89 3685
4a63c884
JF
3686 name = chomp_string(name);
3687 namelen = strcspn(name, separators);
3688
3689 if (name[namelen]) {
3690 name[namelen] = 0;
3691 value = chomp_string(name + namelen + 1);
d0cea5f9 3692 valuelen = strlen(value);
4670cf89 3693
d0cea5f9 3694 } else {
d0cea5f9
JF
3695 value = "";
3696 valuelen = 0;
4670cf89 3697 }
d0cea5f9 3698
3c3801c2 3699 state = read_property(name, namelen, value, valuelen);
4670cf89
JF
3700 }
3701
d0cea5f9
JF
3702 if (state != ERR && ferror(pipe))
3703 state = ERR;
4670cf89
JF
3704
3705 pclose(pipe);
3706
d0cea5f9 3707 return state;
4670cf89
JF
3708}
3709
d0cea5f9 3710
6b161b31
JF
3711/*
3712 * Main
3713 */
3714
b5c9e67f 3715static void __NORETURN
6b161b31
JF
3716quit(int sig)
3717{
8855ada4
JF
3718 /* XXX: Restore tty modes and let the OS cleanup the rest! */
3719 if (cursed)
3720 endwin();
6b161b31
JF
3721 exit(0);
3722}
3723
c6704a4e
JF
3724static void __NORETURN
3725die(const char *err, ...)
6b161b31
JF
3726{
3727 va_list args;
3728
3729 endwin();
3730
3731 va_start(args, err);
3732 fputs("tig: ", stderr);
3733 vfprintf(stderr, err, args);
3734 fputs("\n", stderr);
3735 va_end(args);
3736
3737 exit(1);
3738}
3739
3740int
3741main(int argc, char *argv[])
3742{
1ba2ae4b 3743 struct view *view;
6b161b31 3744 enum request request;
1ba2ae4b 3745 size_t i;
6b161b31
JF
3746
3747 signal(SIGINT, quit);
3748
6b68fd24
JF
3749 if (setlocale(LC_ALL, "")) {
3750 string_copy(opt_codeset, nl_langinfo(CODESET));
3751 }
3752
660e09ad
JF
3753 if (load_options() == ERR)
3754 die("Failed to load user config.");
3755
3756 /* Load the repo config file so options can be overwritten from
afdc35b3 3757 * the command line. */
14c778a6 3758 if (load_repo_config() == ERR)
afdc35b3
JF
3759 die("Failed to load repo config.");
3760
8855ada4 3761 if (!parse_options(argc, argv))
6b161b31
JF
3762 return 0;
3763
6b68fd24
JF
3764 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3765 opt_iconv = iconv_open(opt_codeset, opt_encoding);
20f4b4a3 3766 if (opt_iconv == ICONV_NONE)
6b68fd24
JF
3767 die("Failed to initialize character set conversion");
3768 }
3769
c34d9c9f
JF
3770 if (load_refs() == ERR)
3771 die("Failed to load refs.");
3772
7bb55251
JF
3773 /* Require a git repository unless when running in pager mode. */
3774 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3775 die("Not a git repository");
3776
1ba2ae4b
JF
3777 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3778 view->cmd_env = getenv(view->cmd_env);
3779
6b161b31
JF
3780 request = opt_request;
3781
3782 init_display();
b801d8b2
JF
3783
3784 while (view_driver(display[current_view], request)) {
6b161b31 3785 int key;
b801d8b2
JF
3786 int i;
3787
699ae55b 3788 foreach_view (view, i)
6b161b31 3789 update_view(view);
b801d8b2
JF
3790
3791 /* Refresh, accept single keystroke of input */
6b161b31 3792 key = wgetch(status_win);
04e2b7b2 3793
cf4d82e6
JF
3794 /* wgetch() with nodelay() enabled returns ERR when there's no
3795 * input. */
3796 if (key == ERR) {
3797 request = REQ_NONE;
8b534a13 3798 continue;
cf4d82e6 3799 }
04e2b7b2
JF
3800
3801 request = get_keybinding(display[current_view]->keymap, key);
03a93dbb 3802
6706b2ba 3803 /* Some low-level request handling. This keeps access to
fac7db6c
JF
3804 * status_win restricted. */
3805 switch (request) {
3806 case REQ_PROMPT:
9e21ce5c
JF
3807 {
3808 char *cmd = read_prompt(":");
3809
3810 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3811 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3812 opt_request = REQ_VIEW_DIFF;
3813 } else {
3814 opt_request = REQ_VIEW_PAGER;
3815 }
3816 break;
3817 }
fac7db6c 3818
1d754561 3819 request = REQ_NONE;
9e21ce5c
JF
3820 break;
3821 }
4af34daa
JF
3822 case REQ_SEARCH:
3823 case REQ_SEARCH_BACK:
3824 {
3825 const char *prompt = request == REQ_SEARCH
3826 ? "/" : "?";
3827 char *search = read_prompt(prompt);
3828
3829 if (search)
3830 string_copy(opt_search, search);
3831 else
3832 request = REQ_NONE;
3833 break;
3834 }
fac7db6c
JF
3835 case REQ_SCREEN_RESIZE:
3836 {
3837 int height, width;
3838
3839 getmaxyx(stdscr, height, width);
3840
3841 /* Resize the status view and let the view driver take
3842 * care of resizing the displayed views. */
3843 wresize(status_win, 1, width);
3844 mvwin(status_win, height - 1, 0);
3845 wrefresh(status_win);
3846 break;
3847 }
3848 default:
3849 break;
03a93dbb 3850 }
b801d8b2
JF
3851 }
3852
3853 quit(0);
3854
3855 return 0;
3856}