New actions toggle-date, toggle-author, and toggle-refs.
[tig] / tig.c
CommitLineData
53924375 1/* Copyright (c) 2006-2007 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
776bf2ac
JF
14#ifdef HAVE_CONFIG_H
15#include "config.h"
16#endif
17
ec31d0d0
SG
18#ifndef TIG_VERSION
19#define TIG_VERSION "unknown-version"
b76c2afc
JF
20#endif
21
8855ada4
JF
22#ifndef DEBUG
23#define NDEBUG
24#endif
25
22f66b0a 26#include <assert.h>
4c6fabc2 27#include <errno.h>
22f66b0a
JF
28#include <ctype.h>
29#include <signal.h>
b801d8b2 30#include <stdarg.h>
b801d8b2 31#include <stdio.h>
22f66b0a 32#include <stdlib.h>
b801d8b2 33#include <string.h>
810f0078
JF
34#include <sys/types.h>
35#include <sys/stat.h>
6908bdbd 36#include <unistd.h>
b76c2afc 37#include <time.h>
b801d8b2 38
4af34daa
JF
39#include <regex.h>
40
6b68fd24
JF
41#include <locale.h>
42#include <langinfo.h>
43#include <iconv.h>
44
6a19a303
JF
45/* ncurses(3): Must be defined to have extended wide-character functions. */
46#define _XOPEN_SOURCE_EXTENDED
47
b801d8b2 48#include <curses.h>
b801d8b2 49
e2da526d
JF
50#if __GNUC__ >= 3
51#define __NORETURN __attribute__((__noreturn__))
52#else
53#define __NORETURN
54#endif
55
56static void __NORETURN die(const char *err, ...);
77452abc 57static void warn(const char *msg, ...);
b801d8b2 58static void report(const char *msg, ...);
5699e0cf 59static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
1ba2ae4b 60static void set_nonblocking_input(bool loading);
012e76e9 61static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
6b161b31
JF
62
63#define ABS(x) ((x) >= 0 ? (x) : -(x))
64#define MIN(x, y) ((x) < (y) ? (x) : (y))
65
66#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
67#define STRING_SIZE(x) (sizeof(x) - 1)
b76c2afc 68
17482b11 69#define SIZEOF_STR 1024 /* Default string size. */
2e8488b4 70#define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
10446330 71#define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
c8d60a25
JF
72
73/* Revision graph */
74
75#define REVGRAPH_INIT 'I'
76#define REVGRAPH_MERGE 'M'
77#define REVGRAPH_BRANCH '+'
78#define REVGRAPH_COMMIT '*'
e81e9c2c 79#define REVGRAPH_BOUND '^'
c8d60a25
JF
80#define REVGRAPH_LINE '|'
81
54efb62b 82#define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
b801d8b2 83
82e78006
JF
84/* This color name can be used to refer to the default term colors. */
85#define COLOR_DEFAULT (-1)
78c70acd 86
6b68fd24 87#define ICONV_NONE ((iconv_t) -1)
58a5e4ea
JF
88#ifndef ICONV_CONST
89#define ICONV_CONST /* nothing */
90#endif
6b68fd24 91
82e78006 92/* The format and size of the date column in the main view. */
4c6fabc2 93#define DATE_FORMAT "%Y-%m-%d %H:%M"
6b161b31 94#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
4c6fabc2 95
10e290ee
JF
96#define AUTHOR_COLS 20
97
a28bcc22 98/* The default interval between line numbers. */
4a2909a7 99#define NUMBER_INTERVAL 1
82e78006 100
6706b2ba
JF
101#define TABSIZE 8
102
a28bcc22
JF
103#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
104
96e58f5b 105#ifndef GIT_CONFIG
da633326 106#define GIT_CONFIG "git config"
96e58f5b
JF
107#endif
108
8eb62770 109#define TIG_LS_REMOTE \
337d7377 110 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
8eb62770
JF
111
112#define TIG_DIFF_CMD \
ee74874b 113 "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
8eb62770
JF
114
115#define TIG_LOG_CMD \
8897e66a 116 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
8eb62770
JF
117
118#define TIG_MAIN_CMD \
e81e9c2c 119 "git log --no-color --topo-order --boundary --pretty=raw %s 2>/dev/null"
8eb62770 120
e733ee54
JF
121#define TIG_TREE_CMD \
122 "git ls-tree %s %s"
123
124#define TIG_BLOB_CMD \
125 "git cat-file blob %s"
126
8eb62770
JF
127/* XXX: Needs to be defined to the empty string. */
128#define TIG_HELP_CMD ""
129#define TIG_PAGER_CMD ""
173d76ea 130#define TIG_STATUS_CMD ""
3e634113 131#define TIG_STAGE_CMD ""
8eb62770 132
8855ada4 133/* Some ascii-shorthands fitted into the ncurses namespace. */
a28bcc22
JF
134#define KEY_TAB '\t'
135#define KEY_RETURN '\r'
4a2909a7
JF
136#define KEY_ESC 27
137
6706b2ba 138
c34d9c9f 139struct ref {
468876c9 140 char *name; /* Ref name; tag or head names are shortened. */
10446330 141 char id[SIZEOF_REV]; /* Commit SHA1 ID */
468876c9 142 unsigned int tag:1; /* Is it a tag? */
e15ec88e 143 unsigned int remote:1; /* Is it a remote ref? */
468876c9 144 unsigned int next:1; /* For ref lists: are there more refs? */
c34d9c9f
JF
145};
146
ff26aa29 147static struct ref **get_refs(char *id);
4c6fabc2 148
660e09ad
JF
149struct int_map {
150 const char *name;
151 int namelen;
152 int value;
153};
154
155static int
156set_from_int_map(struct int_map *map, size_t map_size,
157 int *value, const char *name, int namelen)
158{
159
160 int i;
161
162 for (i = 0; i < map_size; i++)
163 if (namelen == map[i].namelen &&
164 !strncasecmp(name, map[i].name, namelen)) {
165 *value = map[i].value;
166 return OK;
167 }
168
169 return ERR;
170}
171
6706b2ba 172
03a93dbb
JF
173/*
174 * String helpers
175 */
78c70acd 176
82e78006 177static inline void
9a48919b 178string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
82e78006 179{
9a48919b
JF
180 if (srclen > dstlen - 1)
181 srclen = dstlen - 1;
03a93dbb 182
9a48919b
JF
183 strncpy(dst, src, srclen);
184 dst[srclen] = 0;
82e78006
JF
185}
186
9a48919b
JF
187/* Shorthands for safely copying into a fixed buffer. */
188
82e78006 189#define string_copy(dst, src) \
751e27c9 190 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
9a48919b
JF
191
192#define string_ncopy(dst, src, srclen) \
193 string_ncopy_do(dst, sizeof(dst), src, srclen)
82e78006 194
2463b4ea
JF
195#define string_copy_rev(dst, src) \
196 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
197
91c5d983
JF
198#define string_add(dst, from, src) \
199 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
200
4a63c884
JF
201static char *
202chomp_string(char *name)
203{
204 int namelen;
205
206 while (isspace(*name))
207 name++;
208
209 namelen = strlen(name) - 1;
210 while (namelen > 0 && isspace(name[namelen]))
211 name[namelen--] = 0;
212
213 return name;
214}
215
cc2d1364 216static bool
d65ced0d 217string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
cc2d1364
JF
218{
219 va_list args;
d65ced0d 220 size_t pos = bufpos ? *bufpos : 0;
cc2d1364
JF
221
222 va_start(args, fmt);
223 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
224 va_end(args);
225
226 if (bufpos)
227 *bufpos = pos;
228
229 return pos >= bufsize ? FALSE : TRUE;
230}
231
232#define string_format(buf, fmt, args...) \
233 string_nformat(buf, sizeof(buf), NULL, fmt, args)
234
235#define string_format_from(buf, from, fmt, args...) \
236 string_nformat(buf, sizeof(buf), from, fmt, args)
6706b2ba 237
201f5a18
JF
238static int
239string_enum_compare(const char *str1, const char *str2, int len)
240{
241 size_t i;
242
243#define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
244
245 /* Diff-Header == DIFF_HEADER */
246 for (i = 0; i < len; i++) {
247 if (toupper(str1[i]) == toupper(str2[i]))
248 continue;
249
250 if (string_enum_sep(str1[i]) &&
251 string_enum_sep(str2[i]))
252 continue;
253
254 return str1[i] - str2[i];
255 }
256
257 return 0;
258}
259
03a93dbb
JF
260/* Shell quoting
261 *
262 * NOTE: The following is a slightly modified copy of the git project's shell
263 * quoting routines found in the quote.c file.
264 *
265 * Help to copy the thing properly quoted for the shell safety. any single
266 * quote is replaced with '\'', any exclamation point is replaced with '\!',
267 * and the whole thing is enclosed in a
268 *
269 * E.g.
270 * original sq_quote result
271 * name ==> name ==> 'name'
272 * a b ==> a b ==> 'a b'
273 * a'b ==> a'\''b ==> 'a'\''b'
274 * a!b ==> a'\!'b ==> 'a'\!'b'
275 */
276
277static size_t
17482b11 278sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
03a93dbb
JF
279{
280 char c;
281
17482b11 282#define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
03a93dbb
JF
283
284 BUFPUT('\'');
285 while ((c = *src++)) {
286 if (c == '\'' || c == '!') {
287 BUFPUT('\'');
288 BUFPUT('\\');
289 BUFPUT(c);
290 BUFPUT('\'');
291 } else {
292 BUFPUT(c);
293 }
294 }
295 BUFPUT('\'');
296
f0f114ac
JF
297 if (bufsize < SIZEOF_STR)
298 buf[bufsize] = 0;
299
03a93dbb
JF
300 return bufsize;
301}
302
82e78006 303
24b5b3e0
JF
304/*
305 * User requests
306 */
307
308#define REQ_INFO \
309 /* XXX: Keep the view request first and in sync with views[]. */ \
310 REQ_GROUP("View switching") \
311 REQ_(VIEW_MAIN, "Show main view"), \
312 REQ_(VIEW_DIFF, "Show diff view"), \
313 REQ_(VIEW_LOG, "Show log view"), \
e733ee54
JF
314 REQ_(VIEW_TREE, "Show tree view"), \
315 REQ_(VIEW_BLOB, "Show blob view"), \
24b5b3e0
JF
316 REQ_(VIEW_HELP, "Show help page"), \
317 REQ_(VIEW_PAGER, "Show pager view"), \
173d76ea 318 REQ_(VIEW_STATUS, "Show status view"), \
3e634113 319 REQ_(VIEW_STAGE, "Show stage view"), \
24b5b3e0
JF
320 \
321 REQ_GROUP("View manipulation") \
322 REQ_(ENTER, "Enter current line and scroll"), \
323 REQ_(NEXT, "Move to next"), \
324 REQ_(PREVIOUS, "Move to previous"), \
325 REQ_(VIEW_NEXT, "Move focus to next view"), \
acaef3b3 326 REQ_(REFRESH, "Reload and refresh"), \
24b5b3e0
JF
327 REQ_(VIEW_CLOSE, "Close the current view"), \
328 REQ_(QUIT, "Close all views and quit"), \
329 \
330 REQ_GROUP("Cursor navigation") \
331 REQ_(MOVE_UP, "Move cursor one line up"), \
332 REQ_(MOVE_DOWN, "Move cursor one line down"), \
333 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
334 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
335 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
336 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
337 \
338 REQ_GROUP("Scrolling") \
339 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
340 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
341 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
342 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
343 \
4af34daa
JF
344 REQ_GROUP("Searching") \
345 REQ_(SEARCH, "Search the view"), \
346 REQ_(SEARCH_BACK, "Search backwards in the view"), \
347 REQ_(FIND_NEXT, "Find next search match"), \
348 REQ_(FIND_PREV, "Find previous search match"), \
349 \
24b5b3e0
JF
350 REQ_GROUP("Misc") \
351 REQ_(PROMPT, "Bring up the prompt"), \
24b5b3e0
JF
352 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
353 REQ_(SCREEN_RESIZE, "Resize the screen"), \
354 REQ_(SHOW_VERSION, "Show version information"), \
355 REQ_(STOP_LOADING, "Stop all loading views"), \
54efb62b 356 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
823057f4
DV
357 REQ_(TOGGLE_DATE, "Toggle date display"), \
358 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
ca1d71ea 359 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
823057f4 360 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
0cea0d43 361 REQ_(STATUS_UPDATE, "Update file status"), \
b5c18d9d 362 REQ_(STATUS_MERGE, "Merge file using external tool"), \
c509eed2 363 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
226da94b 364 REQ_(EDIT, "Open in editor"), \
d31a629d 365 REQ_(NONE, "Do nothing")
24b5b3e0
JF
366
367
368/* User action requests. */
369enum request {
370#define REQ_GROUP(help)
371#define REQ_(req, help) REQ_##req
372
373 /* Offset all requests to avoid conflicts with ncurses getch values. */
374 REQ_OFFSET = KEY_MAX + 1,
d31a629d 375 REQ_INFO
24b5b3e0
JF
376
377#undef REQ_GROUP
378#undef REQ_
379};
380
381struct request_info {
382 enum request request;
04e2b7b2
JF
383 char *name;
384 int namelen;
24b5b3e0
JF
385 char *help;
386};
387
388static struct request_info req_info[] = {
04e2b7b2
JF
389#define REQ_GROUP(help) { 0, NULL, 0, (help) },
390#define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
24b5b3e0
JF
391 REQ_INFO
392#undef REQ_GROUP
393#undef REQ_
394};
395
04e2b7b2
JF
396static enum request
397get_request(const char *name)
398{
399 int namelen = strlen(name);
400 int i;
401
402 for (i = 0; i < ARRAY_SIZE(req_info); i++)
403 if (req_info[i].namelen == namelen &&
404 !string_enum_compare(req_info[i].name, name, namelen))
405 return req_info[i].request;
406
d31a629d 407 return REQ_NONE;
04e2b7b2
JF
408}
409
410
8eb62770
JF
411/*
412 * Options
413 */
b76c2afc 414
4b8c01a3 415static const char usage[] =
ec31d0d0 416"tig " TIG_VERSION " (" __DATE__ ")\n"
4b8c01a3 417"\n"
77452abc
JF
418"Usage: tig [options] [revs] [--] [paths]\n"
419" or: tig show [options] [revs] [--] [paths]\n"
420" or: tig status\n"
421" or: tig < [git command output]\n"
4b8c01a3
JF
422"\n"
423"Options:\n"
77452abc
JF
424" -v, --version Show version and exit\n"
425" -h, --help Show help message and exit\n";
4b8c01a3 426
6706b2ba 427/* Option and state variables. */
823057f4
DV
428static bool opt_date = TRUE;
429static bool opt_author = TRUE;
92d30f5c 430static bool opt_line_number = FALSE;
11ce319e 431static bool opt_rev_graph = FALSE;
823057f4 432static bool opt_show_refs = TRUE;
92d30f5c
JF
433static int opt_num_interval = NUMBER_INTERVAL;
434static int opt_tab_size = TABSIZE;
435static enum request opt_request = REQ_VIEW_MAIN;
436static char opt_cmd[SIZEOF_STR] = "";
e733ee54 437static char opt_path[SIZEOF_STR] = "";
92d30f5c
JF
438static FILE *opt_pipe = NULL;
439static char opt_encoding[20] = "UTF-8";
440static bool opt_utf8 = TRUE;
441static char opt_codeset[20] = "UTF-8";
442static iconv_t opt_iconv = ICONV_NONE;
443static char opt_search[SIZEOF_STR] = "";
91c5d983 444static char opt_cdup[SIZEOF_STR] = "";
810f0078 445static char opt_git_dir[SIZEOF_STR] = "";
5bdd523b 446static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
0cea0d43 447static char opt_editor[SIZEOF_STR] = "";
b76c2afc 448
6dbf6c19
JF
449enum option_type {
450 OPT_NONE,
451 OPT_INT,
452};
453
454static bool
455check_option(char *opt, char short_name, char *name, enum option_type type, ...)
456{
457 va_list args;
458 char *value = "";
459 int *number;
460
461 if (opt[0] != '-')
462 return FALSE;
463
464 if (opt[1] == '-') {
465 int namelen = strlen(name);
466
467 opt += 2;
468
469 if (strncmp(opt, name, namelen))
470 return FALSE;
471
472 if (opt[namelen] == '=')
473 value = opt + namelen + 1;
474
475 } else {
476 if (!short_name || opt[1] != short_name)
477 return FALSE;
478 value = opt + 2;
479 }
480
481 va_start(args, type);
482 if (type == OPT_INT) {
483 number = va_arg(args, int *);
484 if (isdigit(*value))
485 *number = atoi(value);
486 }
487 va_end(args);
488
489 return TRUE;
490}
491
b76c2afc 492/* Returns the index of log or diff command or -1 to exit. */
8855ada4 493static bool
b76c2afc
JF
494parse_options(int argc, char *argv[])
495{
77452abc
JF
496 char *altargv[1024];
497 int altargc = 0;
498 char *subcommand = NULL;
b76c2afc
JF
499 int i;
500
501 for (i = 1; i < argc; i++) {
502 char *opt = argv[i];
503
3621d94e 504 if (!strcmp(opt, "log") ||
77452abc
JF
505 !strcmp(opt, "diff")) {
506 subcommand = opt;
3621d94e
JF
507 opt_request = opt[0] == 'l'
508 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
77452abc
JF
509 warn("`tig %s' has been deprecated", opt);
510 break;
511 }
512
513 if (!strcmp(opt, "show")) {
514 subcommand = opt;
515 opt_request = REQ_VIEW_DIFF;
516 break;
517 }
518
519 if (!strcmp(opt, "status")) {
520 subcommand = opt;
521 opt_request = REQ_VIEW_STATUS;
3621d94e
JF
522 break;
523 }
524
8d27b36e
JF
525 if (opt[0] && opt[0] != '-')
526 break;
527
bfe97931
JF
528 if (!strcmp(opt, "--")) {
529 i++;
530 break;
531 }
532
533 if (check_option(opt, 'v', "version", OPT_NONE)) {
534 printf("tig version %s\n", TIG_VERSION);
535 return FALSE;
536 }
537
538 if (check_option(opt, 'h', "help", OPT_NONE)) {
539 printf(usage);
540 return FALSE;
541 }
542
543 if (!strcmp(opt, "-S")) {
77452abc 544 warn("`%s' has been deprecated; use `tig status' instead", opt);
bfe97931
JF
545 opt_request = REQ_VIEW_STATUS;
546 continue;
547 }
548
6b161b31 549 if (!strcmp(opt, "-l")) {
4a2909a7 550 opt_request = REQ_VIEW_LOG;
77452abc 551 } else if (!strcmp(opt, "-d")) {
4a2909a7 552 opt_request = REQ_VIEW_DIFF;
77452abc 553 } else if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
6b161b31 554 opt_line_number = TRUE;
77452abc 555 } else if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
6dbf6c19 556 opt_tab_size = MIN(opt_tab_size, TABSIZE);
77452abc
JF
557 } else {
558 if (altargc >= ARRAY_SIZE(altargv))
559 die("maximum number of arguments exceeded");
560 altargv[altargc++] = opt;
6706b2ba
JF
561 continue;
562 }
563
77452abc 564 warn("`%s' has been deprecated", opt);
b76c2afc
JF
565 }
566
77452abc
JF
567 /* Check that no 'alt' arguments occured before a subcommand. */
568 if (subcommand && i < argc && altargc > 0)
569 die("unknown arguments before `%s'", argv[i]);
570
6908bdbd 571 if (!isatty(STDIN_FILENO)) {
6908bdbd
JF
572 opt_request = REQ_VIEW_PAGER;
573 opt_pipe = stdin;
574
77452abc
JF
575 } else if (opt_request == REQ_VIEW_STATUS) {
576 if (argc - i > 1)
577 warn("ignoring arguments after `%s'", argv[i]);
578
579 } else if (i < argc || altargc > 0) {
580 int alti = 0;
6908bdbd
JF
581 size_t buf_size;
582
6908bdbd 583 if (opt_request == REQ_VIEW_MAIN)
8855ada4
JF
584 /* XXX: This is vulnerable to the user overriding
585 * options required for the main view parser. */
e81e9c2c 586 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary");
6908bdbd
JF
587 else
588 string_copy(opt_cmd, "git");
589 buf_size = strlen(opt_cmd);
590
77452abc
JF
591 while (buf_size < sizeof(opt_cmd) && alti < altargc) {
592 opt_cmd[buf_size++] = ' ';
593 buf_size = sq_quote(opt_cmd, buf_size, altargv[alti++]);
594 }
595
6908bdbd
JF
596 while (buf_size < sizeof(opt_cmd) && i < argc) {
597 opt_cmd[buf_size++] = ' ';
598 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
599 }
600
601 if (buf_size >= sizeof(opt_cmd))
602 die("command too long");
603
604 opt_cmd[buf_size] = 0;
6908bdbd
JF
605 }
606
afdc35b3
JF
607 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
608 opt_utf8 = FALSE;
609
8855ada4 610 return TRUE;
b76c2afc
JF
611}
612
613
54efb62b
JF
614/*
615 * Line-oriented content detection.
616 */
617
2e8488b4 618#define LINE_INFO \
660e09ad 619LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
a28bcc22
JF
620LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
621LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
622LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
660e09ad
JF
623LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
624LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
625LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
626LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
627LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
628LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
629LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
630LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
631LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
632LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
6908bdbd 633LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
8855ada4 634LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
6908bdbd
JF
635LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
636LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
8855ada4
JF
637LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
638LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
7b99a34c 639LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
a28bcc22
JF
640LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
641LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
642LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
8855ada4 643LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
a28bcc22 644LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
a28bcc22 645LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
d4d8de8f 646LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
a28bcc22
JF
647LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
648LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
649LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
6b161b31
JF
650LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
651LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
a28bcc22
JF
652LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
653LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
654LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
c34d9c9f
JF
655LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
656LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
e15ec88e 657LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
660e09ad 658LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
f83b1c33 659LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
e733ee54 660LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
173d76ea 661LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
53924375 662LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
173d76ea 663LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
53924375
JF
664LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
665LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
173d76ea 666LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
660e09ad 667
78c70acd 668enum line_type {
2e8488b4
JF
669#define LINE(type, line, fg, bg, attr) \
670 LINE_##type
671 LINE_INFO
672#undef LINE
78c70acd
JF
673};
674
675struct line_info {
660e09ad
JF
676 const char *name; /* Option name. */
677 int namelen; /* Size of option name. */
4685845e 678 const char *line; /* The start of line to match. */
2e8488b4
JF
679 int linelen; /* Size of string to match. */
680 int fg, bg, attr; /* Color and text attributes for the lines. */
78c70acd
JF
681};
682
2e8488b4 683static struct line_info line_info[] = {
78c70acd 684#define LINE(type, line, fg, bg, attr) \
660e09ad 685 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
2e8488b4
JF
686 LINE_INFO
687#undef LINE
78c70acd
JF
688};
689
2e8488b4
JF
690static enum line_type
691get_line_type(char *line)
78c70acd
JF
692{
693 int linelen = strlen(line);
a28bcc22 694 enum line_type type;
78c70acd 695
a28bcc22 696 for (type = 0; type < ARRAY_SIZE(line_info); type++)
2e8488b4 697 /* Case insensitive search matches Signed-off-by lines better. */
a28bcc22
JF
698 if (linelen >= line_info[type].linelen &&
699 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
700 return type;
78c70acd 701
2e8488b4 702 return LINE_DEFAULT;
78c70acd
JF
703}
704
2e8488b4 705static inline int
78c70acd
JF
706get_line_attr(enum line_type type)
707{
2e8488b4
JF
708 assert(type < ARRAY_SIZE(line_info));
709 return COLOR_PAIR(type) | line_info[type].attr;
78c70acd
JF
710}
711
660e09ad
JF
712static struct line_info *
713get_line_info(char *name, int namelen)
714{
715 enum line_type type;
660e09ad
JF
716
717 for (type = 0; type < ARRAY_SIZE(line_info); type++)
718 if (namelen == line_info[type].namelen &&
201f5a18 719 !string_enum_compare(line_info[type].name, name, namelen))
660e09ad
JF
720 return &line_info[type];
721
722 return NULL;
723}
724
78c70acd
JF
725static void
726init_colors(void)
727{
62c7d1a7
JF
728 int default_bg = line_info[LINE_DEFAULT].bg;
729 int default_fg = line_info[LINE_DEFAULT].fg;
a28bcc22 730 enum line_type type;
78c70acd
JF
731
732 start_color();
733
62c7d1a7
JF
734 if (assume_default_colors(default_fg, default_bg) == ERR) {
735 default_bg = COLOR_BLACK;
736 default_fg = COLOR_WHITE;
78c70acd
JF
737 }
738
a28bcc22
JF
739 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
740 struct line_info *info = &line_info[type];
82e78006
JF
741 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
742 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
78c70acd 743
a28bcc22 744 init_pair(type, fg, bg);
78c70acd
JF
745 }
746}
747
fe7233c3
JF
748struct line {
749 enum line_type type;
3c571d67
JF
750
751 /* State flags */
752 unsigned int selected:1;
753
fe7233c3
JF
754 void *data; /* User data */
755};
756
78c70acd 757
1899507c 758/*
37157fa0
JF
759 * Keys
760 */
761
93a97d86 762struct keybinding {
37157fa0 763 int alias;
93a97d86 764 enum request request;
04e2b7b2 765 struct keybinding *next;
37157fa0
JF
766};
767
93a97d86 768static struct keybinding default_keybindings[] = {
37157fa0
JF
769 /* View switching */
770 { 'm', REQ_VIEW_MAIN },
771 { 'd', REQ_VIEW_DIFF },
772 { 'l', REQ_VIEW_LOG },
e733ee54 773 { 't', REQ_VIEW_TREE },
0001fc34 774 { 'f', REQ_VIEW_BLOB },
37157fa0
JF
775 { 'p', REQ_VIEW_PAGER },
776 { 'h', REQ_VIEW_HELP },
173d76ea 777 { 'S', REQ_VIEW_STATUS },
3e634113 778 { 'c', REQ_VIEW_STAGE },
37157fa0
JF
779
780 /* View manipulation */
781 { 'q', REQ_VIEW_CLOSE },
782 { KEY_TAB, REQ_VIEW_NEXT },
783 { KEY_RETURN, REQ_ENTER },
784 { KEY_UP, REQ_PREVIOUS },
785 { KEY_DOWN, REQ_NEXT },
acaef3b3 786 { 'R', REQ_REFRESH },
37157fa0
JF
787
788 /* Cursor navigation */
789 { 'k', REQ_MOVE_UP },
790 { 'j', REQ_MOVE_DOWN },
791 { KEY_HOME, REQ_MOVE_FIRST_LINE },
792 { KEY_END, REQ_MOVE_LAST_LINE },
793 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
794 { ' ', REQ_MOVE_PAGE_DOWN },
795 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
796 { 'b', REQ_MOVE_PAGE_UP },
797 { '-', REQ_MOVE_PAGE_UP },
798
799 /* Scrolling */
800 { KEY_IC, REQ_SCROLL_LINE_UP },
801 { KEY_DC, REQ_SCROLL_LINE_DOWN },
802 { 'w', REQ_SCROLL_PAGE_UP },
803 { 's', REQ_SCROLL_PAGE_DOWN },
804
4af34daa
JF
805 /* Searching */
806 { '/', REQ_SEARCH },
807 { '?', REQ_SEARCH_BACK },
808 { 'n', REQ_FIND_NEXT },
809 { 'N', REQ_FIND_PREV },
810
37157fa0
JF
811 /* Misc */
812 { 'Q', REQ_QUIT },
813 { 'z', REQ_STOP_LOADING },
814 { 'v', REQ_SHOW_VERSION },
815 { 'r', REQ_SCREEN_REDRAW },
904e68d8 816 { '.', REQ_TOGGLE_LINENO },
823057f4
DV
817 { 'D', REQ_TOGGLE_DATE },
818 { 'A', REQ_TOGGLE_AUTHOR },
73fb51d5 819 { 'g', REQ_TOGGLE_REV_GRAPH },
823057f4 820 { 'F', REQ_TOGGLE_REFS },
37157fa0 821 { ':', REQ_PROMPT },
ca1d71ea 822 { 'u', REQ_STATUS_UPDATE },
b5c18d9d 823 { 'M', REQ_STATUS_MERGE },
c509eed2 824 { ',', REQ_TREE_PARENT },
0cea0d43 825 { 'e', REQ_EDIT },
37157fa0 826
1d754561 827 /* Using the ncurses SIGWINCH handler. */
37157fa0
JF
828 { KEY_RESIZE, REQ_SCREEN_RESIZE },
829};
830
04e2b7b2
JF
831#define KEYMAP_INFO \
832 KEYMAP_(GENERIC), \
833 KEYMAP_(MAIN), \
834 KEYMAP_(DIFF), \
835 KEYMAP_(LOG), \
e733ee54
JF
836 KEYMAP_(TREE), \
837 KEYMAP_(BLOB), \
04e2b7b2 838 KEYMAP_(PAGER), \
173d76ea 839 KEYMAP_(HELP), \
3e634113
JF
840 KEYMAP_(STATUS), \
841 KEYMAP_(STAGE)
04e2b7b2
JF
842
843enum keymap {
844#define KEYMAP_(name) KEYMAP_##name
845 KEYMAP_INFO
846#undef KEYMAP_
847};
848
849static struct int_map keymap_table[] = {
850#define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
851 KEYMAP_INFO
852#undef KEYMAP_
853};
854
855#define set_keymap(map, name) \
856 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
857
858static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
859
860static void
861add_keybinding(enum keymap keymap, enum request request, int key)
862{
863 struct keybinding *keybinding;
864
865 keybinding = calloc(1, sizeof(*keybinding));
866 if (!keybinding)
867 die("Failed to allocate keybinding");
868
869 keybinding->alias = key;
870 keybinding->request = request;
871 keybinding->next = keybindings[keymap];
872 keybindings[keymap] = keybinding;
873}
874
875/* Looks for a key binding first in the given map, then in the generic map, and
876 * lastly in the default keybindings. */
37157fa0 877static enum request
04e2b7b2 878get_keybinding(enum keymap keymap, int key)
37157fa0 879{
04e2b7b2 880 struct keybinding *kbd;
37157fa0
JF
881 int i;
882
04e2b7b2
JF
883 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
884 if (kbd->alias == key)
885 return kbd->request;
886
887 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
888 if (kbd->alias == key)
889 return kbd->request;
890
93a97d86
JF
891 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
892 if (default_keybindings[i].alias == key)
893 return default_keybindings[i].request;
37157fa0
JF
894
895 return (enum request) key;
896}
897
93a97d86 898
37157fa0
JF
899struct key {
900 char *name;
901 int value;
902};
903
904static struct key key_table[] = {
905 { "Enter", KEY_RETURN },
906 { "Space", ' ' },
907 { "Backspace", KEY_BACKSPACE },
908 { "Tab", KEY_TAB },
909 { "Escape", KEY_ESC },
910 { "Left", KEY_LEFT },
911 { "Right", KEY_RIGHT },
912 { "Up", KEY_UP },
913 { "Down", KEY_DOWN },
914 { "Insert", KEY_IC },
915 { "Delete", KEY_DC },
74f83ee6 916 { "Hash", '#' },
37157fa0
JF
917 { "Home", KEY_HOME },
918 { "End", KEY_END },
919 { "PageUp", KEY_PPAGE },
920 { "PageDown", KEY_NPAGE },
921 { "F1", KEY_F(1) },
922 { "F2", KEY_F(2) },
923 { "F3", KEY_F(3) },
924 { "F4", KEY_F(4) },
925 { "F5", KEY_F(5) },
926 { "F6", KEY_F(6) },
927 { "F7", KEY_F(7) },
928 { "F8", KEY_F(8) },
929 { "F9", KEY_F(9) },
930 { "F10", KEY_F(10) },
931 { "F11", KEY_F(11) },
932 { "F12", KEY_F(12) },
933};
934
04e2b7b2
JF
935static int
936get_key_value(const char *name)
937{
938 int i;
939
940 for (i = 0; i < ARRAY_SIZE(key_table); i++)
941 if (!strcasecmp(key_table[i].name, name))
942 return key_table[i].value;
943
944 if (strlen(name) == 1 && isprint(*name))
945 return (int) *name;
946
947 return ERR;
948}
949
37157fa0 950static char *
9eb14b72
JF
951get_key_name(int key_value)
952{
953 static char key_char[] = "'X'";
954 char *seq = NULL;
955 int key;
956
957 for (key = 0; key < ARRAY_SIZE(key_table); key++)
958 if (key_table[key].value == key_value)
959 seq = key_table[key].name;
960
961 if (seq == NULL &&
962 key_value < 127 &&
963 isprint(key_value)) {
964 key_char[1] = (char) key_value;
965 seq = key_char;
966 }
967
968 return seq ? seq : "'?'";
969}
970
971static char *
37157fa0
JF
972get_key(enum request request)
973{
974 static char buf[BUFSIZ];
d65ced0d 975 size_t pos = 0;
520094b4 976 char *sep = "";
37157fa0
JF
977 int i;
978
979 buf[pos] = 0;
980
93a97d86
JF
981 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
982 struct keybinding *keybinding = &default_keybindings[i];
37157fa0 983
93a97d86 984 if (keybinding->request != request)
37157fa0
JF
985 continue;
986
738bee4a
JF
987 if (!string_format_from(buf, &pos, "%s%s", sep,
988 get_key_name(keybinding->alias)))
37157fa0
JF
989 return "Too many keybindings!";
990 sep = ", ";
991 }
992
993 return buf;
994}
995
9eb14b72
JF
996struct run_request {
997 enum keymap keymap;
998 int key;
999 char cmd[SIZEOF_STR];
1000};
1001
1002static struct run_request *run_request;
1003static size_t run_requests;
1004
1005static enum request
1006add_run_request(enum keymap keymap, int key, int argc, char **argv)
1007{
1008 struct run_request *tmp;
1009 struct run_request req = { keymap, key };
1010 size_t bufpos;
1011
1012 for (bufpos = 0; argc > 0; argc--, argv++)
1013 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
1014 return REQ_NONE;
1015
1016 req.cmd[bufpos - 1] = 0;
1017
1018 tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1019 if (!tmp)
1020 return REQ_NONE;
1021
1022 run_request = tmp;
1023 run_request[run_requests++] = req;
1024
1025 return REQ_NONE + run_requests;
1026}
1027
1028static struct run_request *
1029get_run_request(enum request request)
1030{
1031 if (request <= REQ_NONE)
1032 return NULL;
1033 return &run_request[request - REQ_NONE - 1];
1034}
37157fa0 1035
f655964f
JF
1036static void
1037add_builtin_run_requests(void)
1038{
1039 struct {
1040 enum keymap keymap;
1041 int key;
1042 char *argv[1];
1043 } reqs[] = {
1044 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
1045 { KEYMAP_GENERIC, 'G', { "git gc" } },
1046 };
1047 int i;
1048
1049 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1050 enum request req;
1051
1052 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1053 if (req != REQ_NONE)
1054 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1055 }
1056}
1057
37157fa0 1058/*
1899507c
JF
1059 * User config file handling.
1060 */
1061
5dc795f2
JF
1062static struct int_map color_map[] = {
1063#define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1064 COLOR_MAP(DEFAULT),
1065 COLOR_MAP(BLACK),
1066 COLOR_MAP(BLUE),
1067 COLOR_MAP(CYAN),
1068 COLOR_MAP(GREEN),
1069 COLOR_MAP(MAGENTA),
1070 COLOR_MAP(RED),
1071 COLOR_MAP(WHITE),
1072 COLOR_MAP(YELLOW),
1073};
1074
9256ab05
JF
1075#define set_color(color, name) \
1076 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
660e09ad 1077
5dc795f2
JF
1078static struct int_map attr_map[] = {
1079#define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1080 ATTR_MAP(NORMAL),
1081 ATTR_MAP(BLINK),
1082 ATTR_MAP(BOLD),
1083 ATTR_MAP(DIM),
1084 ATTR_MAP(REVERSE),
1085 ATTR_MAP(STANDOUT),
1086 ATTR_MAP(UNDERLINE),
1087};
1088
9256ab05
JF
1089#define set_attribute(attr, name) \
1090 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
660e09ad 1091
3c3801c2
JF
1092static int config_lineno;
1093static bool config_errors;
1094static char *config_msg;
1095
5bfd96c7 1096/* Wants: object fgcolor bgcolor [attr] */
660e09ad 1097static int
5bfd96c7 1098option_color_command(int argc, char *argv[])
660e09ad 1099{
bca8fcaa
JF
1100 struct line_info *info;
1101
9256ab05
JF
1102 if (argc != 3 && argc != 4) {
1103 config_msg = "Wrong number of arguments given to color command";
1104 return ERR;
1105 }
1106
1107 info = get_line_info(argv[0], strlen(argv[0]));
bca8fcaa
JF
1108 if (!info) {
1109 config_msg = "Unknown color name";
1110 return ERR;
1111 }
660e09ad 1112
a3653368
JF
1113 if (set_color(&info->fg, argv[1]) == ERR ||
1114 set_color(&info->bg, argv[2]) == ERR) {
bca8fcaa
JF
1115 config_msg = "Unknown color";
1116 return ERR;
1117 }
660e09ad 1118
9256ab05 1119 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
bca8fcaa
JF
1120 config_msg = "Unknown attribute";
1121 return ERR;
660e09ad
JF
1122 }
1123
bca8fcaa
JF
1124 return OK;
1125}
1126
5bfd96c7
JF
1127/* Wants: name = value */
1128static int
1129option_set_command(int argc, char *argv[])
1130{
1131 if (argc != 3) {
1132 config_msg = "Wrong number of arguments given to set command";
1133 return ERR;
1134 }
1135
1136 if (strcmp(argv[1], "=")) {
1137 config_msg = "No value assigned";
1138 return ERR;
1139 }
1140
1141 if (!strcmp(argv[0], "show-rev-graph")) {
1142 opt_rev_graph = (!strcmp(argv[2], "1") ||
1143 !strcmp(argv[2], "true") ||
1144 !strcmp(argv[2], "yes"));
1145 return OK;
1146 }
1147
1148 if (!strcmp(argv[0], "line-number-interval")) {
1149 opt_num_interval = atoi(argv[2]);
1150 return OK;
1151 }
1152
1153 if (!strcmp(argv[0], "tab-size")) {
1154 opt_tab_size = atoi(argv[2]);
1155 return OK;
1156 }
1157
cb7267ee 1158 if (!strcmp(argv[0], "commit-encoding")) {
3cc9a4d4
JF
1159 char *arg = argv[2];
1160 int delimiter = *arg;
1161 int i;
1162
1163 switch (delimiter) {
1164 case '"':
1165 case '\'':
1166 for (arg++, i = 0; arg[i]; i++)
1167 if (arg[i] == delimiter) {
1168 arg[i] = 0;
1169 break;
1170 }
1171 default:
739e81de 1172 string_ncopy(opt_encoding, arg, strlen(arg));
3cc9a4d4
JF
1173 return OK;
1174 }
5bfd96c7
JF
1175 }
1176
a3653368 1177 config_msg = "Unknown variable name";
5bfd96c7
JF
1178 return ERR;
1179}
1180
04e2b7b2
JF
1181/* Wants: mode request key */
1182static int
1183option_bind_command(int argc, char *argv[])
1184{
1185 enum request request;
1186 int keymap;
1187 int key;
1188
9eb14b72 1189 if (argc < 3) {
04e2b7b2
JF
1190 config_msg = "Wrong number of arguments given to bind command";
1191 return ERR;
1192 }
1193
1194 if (set_keymap(&keymap, argv[0]) == ERR) {
1195 config_msg = "Unknown key map";
1196 return ERR;
1197 }
1198
1199 key = get_key_value(argv[1]);
1200 if (key == ERR) {
1201 config_msg = "Unknown key";
1202 return ERR;
1203 }
1204
1205 request = get_request(argv[2]);
f40385ae 1206 if (request == REQ_NONE) {
f655964f
JF
1207 const char *obsolete[] = { "cherry-pick" };
1208 size_t namelen = strlen(argv[2]);
1209 int i;
1210
1211 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1212 if (namelen == strlen(obsolete[i]) &&
1213 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1214 config_msg = "Obsolete request name";
1215 return ERR;
1216 }
1217 }
1218 }
9eb14b72
JF
1219 if (request == REQ_NONE && *argv[2]++ == '!')
1220 request = add_run_request(keymap, key, argc - 2, argv + 2);
d31a629d 1221 if (request == REQ_NONE) {
04e2b7b2
JF
1222 config_msg = "Unknown request name";
1223 return ERR;
1224 }
1225
1226 add_keybinding(keymap, request, key);
1227
1228 return OK;
1229}
1230
bca8fcaa 1231static int
9256ab05 1232set_option(char *opt, char *value)
bca8fcaa 1233{
9256ab05
JF
1234 char *argv[16];
1235 int valuelen;
1236 int argc = 0;
1237
1238 /* Tokenize */
1239 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1240 argv[argc++] = value;
9256ab05 1241 value += valuelen;
b86250da
JF
1242
1243 /* Nothing more to tokenize or last available token. */
1244 if (!*value || argc >= ARRAY_SIZE(argv))
9256ab05
JF
1245 break;
1246
1247 *value++ = 0;
1248 while (isspace(*value))
1249 value++;
1250 }
1251
1252 if (!strcmp(opt, "color"))
5bfd96c7
JF
1253 return option_color_command(argc, argv);
1254
1255 if (!strcmp(opt, "set"))
1256 return option_set_command(argc, argv);
bca8fcaa 1257
04e2b7b2
JF
1258 if (!strcmp(opt, "bind"))
1259 return option_bind_command(argc, argv);
1260
a3653368 1261 config_msg = "Unknown option command";
660e09ad
JF
1262 return ERR;
1263}
1264
1265static int
5699e0cf 1266read_option(char *opt, size_t optlen, char *value, size_t valuelen)
3c3801c2 1267{
a3653368
JF
1268 int status = OK;
1269
3c3801c2
JF
1270 config_lineno++;
1271 config_msg = "Internal error";
1272
a3653368
JF
1273 /* Check for comment markers, since read_properties() will
1274 * only ensure opt and value are split at first " \t". */
74f83ee6 1275 optlen = strcspn(opt, "#");
a3653368 1276 if (optlen == 0)
3c3801c2
JF
1277 return OK;
1278
a3653368
JF
1279 if (opt[optlen] != 0) {
1280 config_msg = "No option value";
1281 status = ERR;
1282
1283 } else {
1284 /* Look for comment endings in the value. */
5699e0cf 1285 size_t len = strcspn(value, "#");
a3653368
JF
1286
1287 if (len < valuelen) {
1288 valuelen = len;
1289 value[valuelen] = 0;
1290 }
1291
1292 status = set_option(opt, value);
3c3801c2
JF
1293 }
1294
a3653368
JF
1295 if (status == ERR) {
1296 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
07c3971e 1297 config_lineno, (int) optlen, opt, config_msg);
3c3801c2
JF
1298 config_errors = TRUE;
1299 }
1300
1301 /* Always keep going if errors are encountered. */
1302 return OK;
1303}
1304
b6607e7e
DV
1305static void
1306load_option_file(const char *path)
660e09ad 1307{
660e09ad
JF
1308 FILE *file;
1309
b6607e7e
DV
1310 /* It's ok that the file doesn't exist. */
1311 file = fopen(path, "r");
1312 if (!file)
1313 return;
1314
3c3801c2
JF
1315 config_lineno = 0;
1316 config_errors = FALSE;
1317
b6607e7e
DV
1318 if (read_properties(file, " \t", read_option) == ERR ||
1319 config_errors == TRUE)
1320 fprintf(stderr, "Errors while loading %s.\n", path);
1321}
f655964f 1322
b6607e7e
DV
1323static int
1324load_options(void)
1325{
1326 char *home = getenv("HOME");
1327 char *tigrc_user = getenv("TIGRC_USER");
1328 char *tigrc_system = getenv("TIGRC_SYSTEM");
1329 char buf[SIZEOF_STR];
660e09ad 1330
b6607e7e 1331 add_builtin_run_requests();
660e09ad 1332
b6607e7e
DV
1333 if (!tigrc_system) {
1334 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1335 return ERR;
1336 tigrc_system = buf;
1337 }
1338 load_option_file(tigrc_system);
1339
1340 if (!tigrc_user) {
1341 if (!home || !string_format(buf, "%s/.tigrc", home))
1342 return ERR;
1343 tigrc_user = buf;
1344 }
1345 load_option_file(tigrc_user);
3c3801c2
JF
1346
1347 return OK;
660e09ad
JF
1348}
1349
1350
d839253b 1351/*
468876c9 1352 * The viewer
d839253b 1353 */
c2124ccd
JF
1354
1355struct view;
fe7233c3 1356struct view_ops;
c2124ccd
JF
1357
1358/* The display array of active views and the index of the current view. */
1359static struct view *display[2];
1360static unsigned int current_view;
1361
ab4af23e
JF
1362/* Reading from the prompt? */
1363static bool input_mode = FALSE;
1364
33c4f9ea 1365#define foreach_displayed_view(view, i) \
c2124ccd
JF
1366 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1367
9f41488f 1368#define displayed_views() (display[1] != NULL ? 2 : 1)
c2124ccd 1369
d839253b 1370/* Current head and commit ID */
e733ee54 1371static char ref_blob[SIZEOF_REF] = "";
c2124ccd
JF
1372static char ref_commit[SIZEOF_REF] = "HEAD";
1373static char ref_head[SIZEOF_REF] = "HEAD";
1374
b801d8b2 1375struct view {
03a93dbb 1376 const char *name; /* View name */
4685845e
TH
1377 const char *cmd_fmt; /* Default command line format */
1378 const char *cmd_env; /* Command line set via environment */
e733ee54 1379 const char *id; /* Points to either of ref_{head,commit,blob} */
6b161b31 1380
fe7233c3 1381 struct view_ops *ops; /* View operations */
22f66b0a 1382
04e2b7b2
JF
1383 enum keymap keymap; /* What keymap does this view have */
1384
17482b11 1385 char cmd[SIZEOF_STR]; /* Command buffer */
49f2b43f
JF
1386 char ref[SIZEOF_REF]; /* Hovered commit reference */
1387 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2e8488b4 1388
8855ada4
JF
1389 int height, width; /* The width and height of the main window */
1390 WINDOW *win; /* The main window */
1391 WINDOW *title; /* The title window living below the main window */
b801d8b2
JF
1392
1393 /* Navigation */
1394 unsigned long offset; /* Offset of the window top */
1395 unsigned long lineno; /* Current line number */
1396
4af34daa
JF
1397 /* Searching */
1398 char grep[SIZEOF_STR]; /* Search string */
b77b2cb8 1399 regex_t *regex; /* Pre-compiled regex */
4af34daa 1400
f6da0b66
JF
1401 /* If non-NULL, points to the view that opened this view. If this view
1402 * is closed tig will switch back to the parent view. */
1403 struct view *parent;
1404
b801d8b2
JF
1405 /* Buffering */
1406 unsigned long lines; /* Total number of lines */
fe7233c3 1407 struct line *line; /* Line index */
e2c01617 1408 unsigned long line_size;/* Total number of allocated lines */
8855ada4 1409 unsigned int digits; /* Number of digits in the lines member. */
b801d8b2
JF
1410
1411 /* Loading */
1412 FILE *pipe;
2e8488b4 1413 time_t start_time;
b801d8b2
JF
1414};
1415
fe7233c3
JF
1416struct view_ops {
1417 /* What type of content being displayed. Used in the title bar. */
1418 const char *type;
f098944b
JF
1419 /* Open and reads in all view content. */
1420 bool (*open)(struct view *view);
fe7233c3 1421 /* Read one line; updates view->line. */
701e4f5d 1422 bool (*read)(struct view *view, char *data);
f098944b
JF
1423 /* Draw one line; @lineno must be < view->height. */
1424 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
586c423d
JF
1425 /* Depending on view handle a special requests. */
1426 enum request (*request)(struct view *view, enum request request, struct line *line);
4af34daa
JF
1427 /* Search for regex in a line. */
1428 bool (*grep)(struct view *view, struct line *line);
d720de4b
JF
1429 /* Select line */
1430 void (*select)(struct view *view, struct line *line);
fe7233c3
JF
1431};
1432
6b161b31
JF
1433static struct view_ops pager_ops;
1434static struct view_ops main_ops;
e733ee54
JF
1435static struct view_ops tree_ops;
1436static struct view_ops blob_ops;
f098944b 1437static struct view_ops help_ops;
173d76ea 1438static struct view_ops status_ops;
3e634113 1439static struct view_ops stage_ops;
a28bcc22 1440
04e2b7b2
JF
1441#define VIEW_STR(name, cmd, env, ref, ops, map) \
1442 { name, cmd, #env, ref, ops, map}
1ba2ae4b 1443
95d7ddcd 1444#define VIEW_(id, name, ops, ref) \
04e2b7b2 1445 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1ba2ae4b 1446
c2124ccd 1447
b801d8b2 1448static struct view views[] = {
173d76ea
JF
1449 VIEW_(MAIN, "main", &main_ops, ref_head),
1450 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1451 VIEW_(LOG, "log", &pager_ops, ref_head),
1452 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1453 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
b64c5b75
JF
1454 VIEW_(HELP, "help", &help_ops, ""),
1455 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
173d76ea 1456 VIEW_(STATUS, "status", &status_ops, ""),
3e634113 1457 VIEW_(STAGE, "stage", &stage_ops, ""),
b801d8b2
JF
1458};
1459
a28bcc22
JF
1460#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1461
699ae55b
JF
1462#define foreach_view(view, i) \
1463 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1464
1465#define view_is_displayed(view) \
1466 (view == display[0] || view == display[1])
4c6fabc2 1467
1c919d68
DV
1468static int
1469draw_text(struct view *view, const char *string, int max_len, int col,
1470 bool use_tilde, int tilde_attr)
1471{
71029993 1472 int len = 0;
22026548 1473 int trimmed = FALSE;
1c919d68 1474
22026548
JF
1475 if (max_len <= 0)
1476 return 0;
1c919d68 1477
22026548
JF
1478 if (opt_utf8) {
1479 len = utf8_length(string, max_len, &trimmed, use_tilde);
1480 } else {
1481 len = strlen(string);
1482 if (len > max_len) {
1483 if (use_tilde) {
1484 max_len -= 1;
1c919d68 1485 }
22026548
JF
1486 len = max_len;
1487 trimmed = TRUE;
1c919d68 1488 }
22026548
JF
1489 }
1490
1491 waddnstr(view->win, string, len);
1492 if (trimmed && use_tilde) {
1493 if (tilde_attr != -1)
1494 wattrset(view->win, tilde_attr);
1495 waddch(view->win, '~');
1496 len++;
1c919d68
DV
1497 }
1498
71029993 1499 return len;
1c919d68
DV
1500}
1501
fe7233c3
JF
1502static bool
1503draw_view_line(struct view *view, unsigned int lineno)
1504{
d720de4b 1505 struct line *line;
5dcf8064 1506 bool selected = (view->offset + lineno == view->lineno);
4887d44e 1507 bool draw_ok;
d720de4b 1508
699ae55b
JF
1509 assert(view_is_displayed(view));
1510
fe7233c3
JF
1511 if (view->offset + lineno >= view->lines)
1512 return FALSE;
1513
d720de4b
JF
1514 line = &view->line[view->offset + lineno];
1515
3c571d67
JF
1516 if (selected) {
1517 line->selected = TRUE;
d720de4b 1518 view->ops->select(view, line);
3c571d67
JF
1519 } else if (line->selected) {
1520 line->selected = FALSE;
1521 wmove(view->win, lineno, 0);
1522 wclrtoeol(view->win);
1523 }
d720de4b 1524
4887d44e
JF
1525 scrollok(view->win, FALSE);
1526 draw_ok = view->ops->draw(view, line, lineno, selected);
1527 scrollok(view->win, TRUE);
1528
1529 return draw_ok;
fe7233c3
JF
1530}
1531
b801d8b2 1532static void
82e78006 1533redraw_view_from(struct view *view, int lineno)
b801d8b2 1534{
82e78006 1535 assert(0 <= lineno && lineno < view->height);
b801d8b2 1536
82e78006 1537 for (; lineno < view->height; lineno++) {
fe7233c3 1538 if (!draw_view_line(view, lineno))
fd85fef1 1539 break;
b801d8b2
JF
1540 }
1541
1542 redrawwin(view->win);
ab4af23e
JF
1543 if (input_mode)
1544 wnoutrefresh(view->win);
1545 else
1546 wrefresh(view->win);
b801d8b2
JF
1547}
1548
b76c2afc 1549static void
82e78006
JF
1550redraw_view(struct view *view)
1551{
1552 wclear(view->win);
1553 redraw_view_from(view, 0);
1554}
1555
c2124ccd 1556
6b161b31 1557static void
81030ec8
JF
1558update_view_title(struct view *view)
1559{
3c112a88 1560 char buf[SIZEOF_STR];
71d1c7db
JF
1561 char state[SIZEOF_STR];
1562 size_t bufpos = 0, statelen = 0;
81030ec8 1563
3c112a88 1564 assert(view_is_displayed(view));
81030ec8 1565
249016a6 1566 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
6d9c07af 1567 unsigned int view_lines = view->offset + view->height;
c19f8017 1568 unsigned int lines = view->lines
6d9c07af 1569 ? MIN(view_lines, view->lines) * 100 / view->lines
c19f8017
JF
1570 : 0;
1571
71d1c7db 1572 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
3c112a88
JF
1573 view->ops->type,
1574 view->lineno + 1,
1575 view->lines,
1576 lines);
81030ec8 1577
5becf244
JF
1578 if (view->pipe) {
1579 time_t secs = time(NULL) - view->start_time;
f97f4012 1580
5becf244
JF
1581 /* Three git seconds are a long time ... */
1582 if (secs > 2)
71d1c7db 1583 string_format_from(state, &statelen, " %lds", secs);
5becf244 1584 }
81030ec8
JF
1585 }
1586
71d1c7db
JF
1587 string_format_from(buf, &bufpos, "[%s]", view->name);
1588 if (*view->ref && bufpos < view->width) {
1589 size_t refsize = strlen(view->ref);
1590 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1591
1592 if (minsize < view->width)
1593 refsize = view->width - minsize + 7;
d1858deb 1594 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
71d1c7db 1595 }
f97f4012 1596
71d1c7db
JF
1597 if (statelen && bufpos < view->width) {
1598 string_format_from(buf, &bufpos, " %s", state);
f97f4012
JF
1599 }
1600
3c112a88
JF
1601 if (view == display[current_view])
1602 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1603 else
1604 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1605
3c112a88 1606 mvwaddnstr(view->title, 0, 0, buf, bufpos);
390a8262 1607 wclrtoeol(view->title);
976447f8 1608 wmove(view->title, 0, view->width - 1);
ab4af23e
JF
1609
1610 if (input_mode)
1611 wnoutrefresh(view->title);
1612 else
1613 wrefresh(view->title);
81030ec8
JF
1614}
1615
1616static void
6b161b31 1617resize_display(void)
b76c2afc 1618{
03a93dbb 1619 int offset, i;
6b161b31
JF
1620 struct view *base = display[0];
1621 struct view *view = display[1] ? display[1] : display[0];
b76c2afc 1622
6b161b31 1623 /* Setup window dimensions */
b76c2afc 1624
03a93dbb 1625 getmaxyx(stdscr, base->height, base->width);
b76c2afc 1626
6b161b31 1627 /* Make room for the status window. */
03a93dbb 1628 base->height -= 1;
6b161b31
JF
1629
1630 if (view != base) {
03a93dbb
JF
1631 /* Horizontal split. */
1632 view->width = base->width;
6b161b31
JF
1633 view->height = SCALE_SPLIT_VIEW(base->height);
1634 base->height -= view->height;
1635
1636 /* Make room for the title bar. */
1637 view->height -= 1;
1638 }
1639
1640 /* Make room for the title bar. */
1641 base->height -= 1;
1642
1643 offset = 0;
1644
33c4f9ea 1645 foreach_displayed_view (view, i) {
b76c2afc 1646 if (!view->win) {
c19f8017 1647 view->win = newwin(view->height, 0, offset, 0);
6b161b31
JF
1648 if (!view->win)
1649 die("Failed to create %s view", view->name);
1650
1651 scrollok(view->win, TRUE);
1652
1653 view->title = newwin(1, 0, offset + view->height, 0);
1654 if (!view->title)
1655 die("Failed to create title window");
1656
1657 } else {
c19f8017 1658 wresize(view->win, view->height, view->width);
6b161b31
JF
1659 mvwin(view->win, offset, 0);
1660 mvwin(view->title, offset + view->height, 0);
a28bcc22 1661 }
a28bcc22 1662
6b161b31 1663 offset += view->height + 1;
b76c2afc 1664 }
6b161b31 1665}
b76c2afc 1666
6b161b31 1667static void
20bb5e18
JF
1668redraw_display(void)
1669{
1670 struct view *view;
1671 int i;
1672
33c4f9ea 1673 foreach_displayed_view (view, i) {
20bb5e18
JF
1674 redraw_view(view);
1675 update_view_title(view);
1676 }
1677}
1678
85af6284 1679static void
2bee3bde 1680update_display_cursor(struct view *view)
85af6284 1681{
85af6284
JF
1682 /* Move the cursor to the right-most column of the cursor line.
1683 *
1684 * XXX: This could turn out to be a bit expensive, but it ensures that
1685 * the cursor does not jump around. */
1686 if (view->lines) {
1687 wmove(view->win, view->lineno - view->offset, view->width - 1);
1688 wrefresh(view->win);
1689 }
1690}
20bb5e18 1691
2e8488b4
JF
1692/*
1693 * Navigation
1694 */
1695
4a2909a7 1696/* Scrolling backend */
b801d8b2 1697static void
8c317212 1698do_scroll_view(struct view *view, int lines)
b801d8b2 1699{
a0087dd5
JF
1700 bool redraw_current_line = FALSE;
1701
fd85fef1
JF
1702 /* The rendering expects the new offset. */
1703 view->offset += lines;
1704
1705 assert(0 <= view->offset && view->offset < view->lines);
1706 assert(lines);
b801d8b2 1707
a0087dd5
JF
1708 /* Move current line into the view. */
1709 if (view->lineno < view->offset) {
1710 view->lineno = view->offset;
1711 redraw_current_line = TRUE;
1712 } else if (view->lineno >= view->offset + view->height) {
1713 view->lineno = view->offset + view->height - 1;
1714 redraw_current_line = TRUE;
1715 }
1716
1717 assert(view->offset <= view->lineno && view->lineno < view->lines);
1718
82e78006 1719 /* Redraw the whole screen if scrolling is pointless. */
4c6fabc2 1720 if (view->height < ABS(lines)) {
b76c2afc
JF
1721 redraw_view(view);
1722
1723 } else {
22f66b0a 1724 int line = lines > 0 ? view->height - lines : 0;
82e78006 1725 int end = line + ABS(lines);
fd85fef1
JF
1726
1727 wscrl(view->win, lines);
1728
22f66b0a 1729 for (; line < end; line++) {
fe7233c3 1730 if (!draw_view_line(view, line))
fd85fef1
JF
1731 break;
1732 }
fd85fef1 1733
a0087dd5
JF
1734 if (redraw_current_line)
1735 draw_view_line(view, view->lineno - view->offset);
fd85fef1
JF
1736 }
1737
fd85fef1
JF
1738 redrawwin(view->win);
1739 wrefresh(view->win);
9d3f5834 1740 report("");
fd85fef1 1741}
78c70acd 1742
4a2909a7 1743/* Scroll frontend */
fd85fef1 1744static void
6b161b31 1745scroll_view(struct view *view, enum request request)
fd85fef1
JF
1746{
1747 int lines = 1;
b801d8b2 1748
8c317212
JF
1749 assert(view_is_displayed(view));
1750
b801d8b2 1751 switch (request) {
4a2909a7 1752 case REQ_SCROLL_PAGE_DOWN:
fd85fef1 1753 lines = view->height;
4a2909a7 1754 case REQ_SCROLL_LINE_DOWN:
b801d8b2 1755 if (view->offset + lines > view->lines)
bde3653a 1756 lines = view->lines - view->offset;
b801d8b2 1757
fd85fef1 1758 if (lines == 0 || view->offset + view->height >= view->lines) {
eb98559e 1759 report("Cannot scroll beyond the last line");
b801d8b2
JF
1760 return;
1761 }
1762 break;
1763
4a2909a7 1764 case REQ_SCROLL_PAGE_UP:
fd85fef1 1765 lines = view->height;
4a2909a7 1766 case REQ_SCROLL_LINE_UP:
b801d8b2
JF
1767 if (lines > view->offset)
1768 lines = view->offset;
1769
1770 if (lines == 0) {
eb98559e 1771 report("Cannot scroll beyond the first line");
b801d8b2
JF
1772 return;
1773 }
1774
fd85fef1 1775 lines = -lines;
b801d8b2 1776 break;
03a93dbb 1777
6b161b31
JF
1778 default:
1779 die("request %d not handled in switch", request);
b801d8b2
JF
1780 }
1781
8c317212 1782 do_scroll_view(view, lines);
fd85fef1 1783}
b801d8b2 1784
4a2909a7 1785/* Cursor moving */
fd85fef1 1786static void
8522ecc7 1787move_view(struct view *view, enum request request)
fd85fef1 1788{
dfaa6c81 1789 int scroll_steps = 0;
fd85fef1 1790 int steps;
b801d8b2 1791
fd85fef1 1792 switch (request) {
4a2909a7 1793 case REQ_MOVE_FIRST_LINE:
78c70acd
JF
1794 steps = -view->lineno;
1795 break;
1796
4a2909a7 1797 case REQ_MOVE_LAST_LINE:
78c70acd
JF
1798 steps = view->lines - view->lineno - 1;
1799 break;
1800
4a2909a7 1801 case REQ_MOVE_PAGE_UP:
78c70acd
JF
1802 steps = view->height > view->lineno
1803 ? -view->lineno : -view->height;
1804 break;
1805
4a2909a7 1806 case REQ_MOVE_PAGE_DOWN:
78c70acd
JF
1807 steps = view->lineno + view->height >= view->lines
1808 ? view->lines - view->lineno - 1 : view->height;
1809 break;
1810
4a2909a7 1811 case REQ_MOVE_UP:
fd85fef1
JF
1812 steps = -1;
1813 break;
b801d8b2 1814
4a2909a7 1815 case REQ_MOVE_DOWN:
fd85fef1
JF
1816 steps = 1;
1817 break;
6b161b31
JF
1818
1819 default:
1820 die("request %d not handled in switch", request);
78c70acd 1821 }
b801d8b2 1822
4c6fabc2 1823 if (steps <= 0 && view->lineno == 0) {
eb98559e 1824 report("Cannot move beyond the first line");
78c70acd 1825 return;
b801d8b2 1826
6908bdbd 1827 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
eb98559e 1828 report("Cannot move beyond the last line");
78c70acd 1829 return;
fd85fef1
JF
1830 }
1831
4c6fabc2 1832 /* Move the current line */
fd85fef1 1833 view->lineno += steps;
4c6fabc2
JF
1834 assert(0 <= view->lineno && view->lineno < view->lines);
1835
4c6fabc2 1836 /* Check whether the view needs to be scrolled */
fd85fef1
JF
1837 if (view->lineno < view->offset ||
1838 view->lineno >= view->offset + view->height) {
dfaa6c81 1839 scroll_steps = steps;
fd85fef1 1840 if (steps < 0 && -steps > view->offset) {
dfaa6c81 1841 scroll_steps = -view->offset;
b76c2afc
JF
1842
1843 } else if (steps > 0) {
1844 if (view->lineno == view->lines - 1 &&
1845 view->lines > view->height) {
dfaa6c81
JF
1846 scroll_steps = view->lines - view->offset - 1;
1847 if (scroll_steps >= view->height)
1848 scroll_steps -= view->height - 1;
b76c2afc 1849 }
b801d8b2 1850 }
8522ecc7
JF
1851 }
1852
1853 if (!view_is_displayed(view)) {
a3965365
JF
1854 view->offset += scroll_steps;
1855 assert(0 <= view->offset && view->offset < view->lines);
8522ecc7
JF
1856 view->ops->select(view, &view->line[view->lineno]);
1857 return;
1858 }
1859
1860 /* Repaint the old "current" line if we be scrolling */
1861 if (ABS(steps) < view->height)
1862 draw_view_line(view, view->lineno - steps - view->offset);
1863
dfaa6c81
JF
1864 if (scroll_steps) {
1865 do_scroll_view(view, scroll_steps);
fd85fef1 1866 return;
b801d8b2
JF
1867 }
1868
4c6fabc2 1869 /* Draw the current line */
fe7233c3 1870 draw_view_line(view, view->lineno - view->offset);
fd85fef1 1871
b801d8b2
JF
1872 redrawwin(view->win);
1873 wrefresh(view->win);
9d3f5834 1874 report("");
b801d8b2
JF
1875}
1876
b801d8b2 1877
2e8488b4 1878/*
4af34daa
JF
1879 * Searching
1880 */
1881
c02d8fce 1882static void search_view(struct view *view, enum request request);
4af34daa
JF
1883
1884static bool
1885find_next_line(struct view *view, unsigned long lineno, struct line *line)
1886{
699ae55b
JF
1887 assert(view_is_displayed(view));
1888
4af34daa
JF
1889 if (!view->ops->grep(view, line))
1890 return FALSE;
1891
1892 if (lineno - view->offset >= view->height) {
1893 view->offset = lineno;
1894 view->lineno = lineno;
1895 redraw_view(view);
1896
1897 } else {
1898 unsigned long old_lineno = view->lineno - view->offset;
1899
1900 view->lineno = lineno;
4af34daa
JF
1901 draw_view_line(view, old_lineno);
1902
1903 draw_view_line(view, view->lineno - view->offset);
1904 redrawwin(view->win);
1905 wrefresh(view->win);
1906 }
1907
1908 report("Line %ld matches '%s'", lineno + 1, view->grep);
1909 return TRUE;
1910}
1911
1912static void
1913find_next(struct view *view, enum request request)
1914{
1915 unsigned long lineno = view->lineno;
1916 int direction;
1917
1918 if (!*view->grep) {
1919 if (!*opt_search)
1920 report("No previous search");
1921 else
c02d8fce 1922 search_view(view, request);
4af34daa
JF
1923 return;
1924 }
1925
1926 switch (request) {
1927 case REQ_SEARCH:
1928 case REQ_FIND_NEXT:
1929 direction = 1;
1930 break;
1931
1932 case REQ_SEARCH_BACK:
1933 case REQ_FIND_PREV:
1934 direction = -1;
1935 break;
1936
1937 default:
1938 return;
1939 }
1940
1941 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1942 lineno += direction;
1943
1944 /* Note, lineno is unsigned long so will wrap around in which case it
1945 * will become bigger than view->lines. */
1946 for (; lineno < view->lines; lineno += direction) {
1947 struct line *line = &view->line[lineno];
1948
1949 if (find_next_line(view, lineno, line))
1950 return;
1951 }
1952
1953 report("No match found for '%s'", view->grep);
1954}
1955
1956static void
c02d8fce 1957search_view(struct view *view, enum request request)
4af34daa
JF
1958{
1959 int regex_err;
1960
b77b2cb8
JF
1961 if (view->regex) {
1962 regfree(view->regex);
4af34daa 1963 *view->grep = 0;
b77b2cb8
JF
1964 } else {
1965 view->regex = calloc(1, sizeof(*view->regex));
1966 if (!view->regex)
1967 return;
4af34daa
JF
1968 }
1969
c02d8fce 1970 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
4af34daa
JF
1971 if (regex_err != 0) {
1972 char buf[SIZEOF_STR] = "unknown error";
1973
b77b2cb8 1974 regerror(regex_err, view->regex, buf, sizeof(buf));
e9cacd58 1975 report("Search failed: %s", buf);
4af34daa
JF
1976 return;
1977 }
1978
c02d8fce 1979 string_copy(view->grep, opt_search);
4af34daa
JF
1980
1981 find_next(view, request);
1982}
1983
1984/*
2e8488b4
JF
1985 * Incremental updating
1986 */
b801d8b2 1987
199d1288
JF
1988static void
1989end_update(struct view *view)
1990{
1991 if (!view->pipe)
1992 return;
1993 set_nonblocking_input(FALSE);
1994 if (view->pipe == stdin)
1995 fclose(view->pipe);
1996 else
1997 pclose(view->pipe);
1998 view->pipe = NULL;
1999}
2000
03a93dbb 2001static bool
b801d8b2
JF
2002begin_update(struct view *view)
2003{
199d1288
JF
2004 if (view->pipe)
2005 end_update(view);
2006
03a93dbb
JF
2007 if (opt_cmd[0]) {
2008 string_copy(view->cmd, opt_cmd);
2009 opt_cmd[0] = 0;
035ba11f
JF
2010 /* When running random commands, initially show the
2011 * command in the title. However, it maybe later be
2012 * overwritten if a commit line is selected. */
809a9f48
JF
2013 if (view == VIEW(REQ_VIEW_PAGER))
2014 string_copy(view->ref, view->cmd);
2015 else
2016 view->ref[0] = 0;
e733ee54
JF
2017
2018 } else if (view == VIEW(REQ_VIEW_TREE)) {
2019 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
f0f114ac 2020 char path[SIZEOF_STR];
e733ee54
JF
2021
2022 if (strcmp(view->vid, view->id))
f0f114ac
JF
2023 opt_path[0] = path[0] = 0;
2024 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2025 return FALSE;
e733ee54 2026
739e81de 2027 if (!string_format(view->cmd, format, view->id, path))
e733ee54
JF
2028 return FALSE;
2029
03a93dbb 2030 } else {
4685845e 2031 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
739e81de 2032 const char *id = view->id;
1ba2ae4b 2033
cc2d1364 2034 if (!string_format(view->cmd, format, id, id, id, id, id))
03a93dbb 2035 return FALSE;
035ba11f
JF
2036
2037 /* Put the current ref_* value to the view title ref
2038 * member. This is needed by the blob view. Most other
2039 * views sets it automatically after loading because the
2040 * first line is a commit line. */
739e81de 2041 string_copy_rev(view->ref, view->id);
03a93dbb 2042 }
b801d8b2 2043
6908bdbd
JF
2044 /* Special case for the pager view. */
2045 if (opt_pipe) {
2046 view->pipe = opt_pipe;
2047 opt_pipe = NULL;
2048 } else {
2049 view->pipe = popen(view->cmd, "r");
2050 }
2051
2e8488b4
JF
2052 if (!view->pipe)
2053 return FALSE;
b801d8b2 2054
6b161b31 2055 set_nonblocking_input(TRUE);
b801d8b2
JF
2056
2057 view->offset = 0;
2058 view->lines = 0;
2059 view->lineno = 0;
739e81de 2060 string_copy_rev(view->vid, view->id);
b801d8b2 2061
2e8488b4
JF
2062 if (view->line) {
2063 int i;
2064
2065 for (i = 0; i < view->lines; i++)
fe7233c3
JF
2066 if (view->line[i].data)
2067 free(view->line[i].data);
2e8488b4
JF
2068
2069 free(view->line);
2070 view->line = NULL;
2071 }
2072
2073 view->start_time = time(NULL);
2074
b801d8b2
JF
2075 return TRUE;
2076}
2077
e2c01617
JF
2078static struct line *
2079realloc_lines(struct view *view, size_t line_size)
2080{
2081 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2082
2083 if (!tmp)
2084 return NULL;
2085
2086 view->line = tmp;
2087 view->line_size = line_size;
2088 return view->line;
2089}
2090
03a93dbb 2091static bool
b801d8b2
JF
2092update_view(struct view *view)
2093{
6b68fd24
JF
2094 char in_buffer[BUFSIZ];
2095 char out_buffer[BUFSIZ * 2];
b801d8b2 2096 char *line;
82e78006
JF
2097 /* The number of lines to read. If too low it will cause too much
2098 * redrawing (and possible flickering), if too high responsiveness
2099 * will suffer. */
8855ada4 2100 unsigned long lines = view->height;
82e78006 2101 int redraw_from = -1;
b801d8b2
JF
2102
2103 if (!view->pipe)
2104 return TRUE;
2105
82e78006
JF
2106 /* Only redraw if lines are visible. */
2107 if (view->offset + view->height >= view->lines)
2108 redraw_from = view->lines - view->offset;
b801d8b2 2109
699ae55b 2110 /* FIXME: This is probably not perfect for backgrounded views. */
e2c01617 2111 if (!realloc_lines(view, view->lines + lines))
b801d8b2
JF
2112 goto alloc_error;
2113
6b68fd24
JF
2114 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2115 size_t linelen = strlen(line);
b801d8b2 2116
b801d8b2
JF
2117 if (linelen)
2118 line[linelen - 1] = 0;
2119
6b68fd24 2120 if (opt_iconv != ICONV_NONE) {
e47afdf2 2121 ICONV_CONST char *inbuf = line;
6b68fd24
JF
2122 size_t inlen = linelen;
2123
2124 char *outbuf = out_buffer;
2125 size_t outlen = sizeof(out_buffer);
2126
2127 size_t ret;
2128
7361622d 2129 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
6b68fd24
JF
2130 if (ret != (size_t) -1) {
2131 line = out_buffer;
2132 linelen = strlen(out_buffer);
2133 }
2134 }
2135
701e4f5d 2136 if (!view->ops->read(view, line))
b801d8b2 2137 goto alloc_error;
fd85fef1
JF
2138
2139 if (lines-- == 1)
2140 break;
b801d8b2
JF
2141 }
2142
8855ada4
JF
2143 {
2144 int digits;
2145
2146 lines = view->lines;
2147 for (digits = 0; lines; digits++)
2148 lines /= 10;
2149
2150 /* Keep the displayed view in sync with line number scaling. */
2151 if (digits != view->digits) {
2152 view->digits = digits;
2153 redraw_from = 0;
2154 }
2155 }
2156
699ae55b
JF
2157 if (!view_is_displayed(view))
2158 goto check_pipe;
2159
e733ee54
JF
2160 if (view == VIEW(REQ_VIEW_TREE)) {
2161 /* Clear the view and redraw everything since the tree sorting
2162 * might have rearranged things. */
2163 redraw_view(view);
2164
2165 } else if (redraw_from >= 0) {
82e78006 2166 /* If this is an incremental update, redraw the previous line
a28bcc22
JF
2167 * since for commits some members could have changed when
2168 * loading the main view. */
82e78006
JF
2169 if (redraw_from > 0)
2170 redraw_from--;
2171
9eded379
JF
2172 /* Since revision graph visualization requires knowledge
2173 * about the parent commit, it causes a further one-off
2174 * needed to be redrawn for incremental updates. */
2175 if (redraw_from > 0 && opt_rev_graph)
2176 redraw_from--;
2177
82e78006
JF
2178 /* Incrementally draw avoids flickering. */
2179 redraw_view_from(view, redraw_from);
4c6fabc2 2180 }
b801d8b2 2181
eb98559e
JF
2182 /* Update the title _after_ the redraw so that if the redraw picks up a
2183 * commit reference in view->ref it'll be available here. */
2184 update_view_title(view);
2185
699ae55b 2186check_pipe:
b801d8b2 2187 if (ferror(view->pipe)) {
03a93dbb 2188 report("Failed to read: %s", strerror(errno));
b801d8b2
JF
2189 goto end;
2190
2191 } else if (feof(view->pipe)) {
f97f4012 2192 report("");
b801d8b2
JF
2193 goto end;
2194 }
2195
2196 return TRUE;
2197
2198alloc_error:
2e8488b4 2199 report("Allocation failure");
b801d8b2
JF
2200
2201end:
be04d936 2202 view->ops->read(view, NULL);
b801d8b2
JF
2203 end_update(view);
2204 return FALSE;
2205}
2206
0a0d8910 2207static struct line *
e314c36d 2208add_line_data(struct view *view, void *data, enum line_type type)
0a0d8910 2209{
e314c36d 2210 struct line *line = &view->line[view->lines++];
0a0d8910 2211
e314c36d 2212 memset(line, 0, sizeof(*line));
0a0d8910 2213 line->type = type;
e314c36d 2214 line->data = data;
0a0d8910
JF
2215
2216 return line;
2217}
2218
e314c36d
JF
2219static struct line *
2220add_line_text(struct view *view, char *data, enum line_type type)
2221{
2222 if (data)
2223 data = strdup(data);
2224
2225 return data ? add_line_data(view, data, type) : NULL;
2226}
2227
79d445ca 2228
e10154d5
JF
2229/*
2230 * View opening
2231 */
2232
49f2b43f
JF
2233enum open_flags {
2234 OPEN_DEFAULT = 0, /* Use default view switching. */
2235 OPEN_SPLIT = 1, /* Split current view. */
2236 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2237 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2238};
2239
6b161b31 2240static void
49f2b43f 2241open_view(struct view *prev, enum request request, enum open_flags flags)
b801d8b2 2242{
49f2b43f
JF
2243 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2244 bool split = !!(flags & OPEN_SPLIT);
2245 bool reload = !!(flags & OPEN_RELOAD);
a28bcc22 2246 struct view *view = VIEW(request);
9f41488f 2247 int nviews = displayed_views();
6e950a52 2248 struct view *base_view = display[0];
b801d8b2 2249
49f2b43f 2250 if (view == prev && nviews == 1 && !reload) {
6b161b31
JF
2251 report("Already in %s view", view->name);
2252 return;
2253 }
b801d8b2 2254
f098944b
JF
2255 if (view->ops->open) {
2256 if (!view->ops->open(view)) {
2257 report("Failed to load %s view", view->name);
2258 return;
2259 }
2509b112
JF
2260
2261 } else if ((reload || strcmp(view->vid, view->id)) &&
2262 !begin_update(view)) {
6b161b31
JF
2263 report("Failed to load %s view", view->name);
2264 return;
2265 }
a28bcc22 2266
6b161b31 2267 if (split) {
8d741c06 2268 display[1] = view;
6b161b31 2269 if (!backgrounded)
8d741c06 2270 current_view = 1;
6b161b31
JF
2271 } else {
2272 /* Maximize the current view. */
2273 memset(display, 0, sizeof(display));
2274 current_view = 0;
2275 display[current_view] = view;
a28bcc22 2276 }
b801d8b2 2277
6e950a52
JF
2278 /* Resize the view when switching between split- and full-screen,
2279 * or when switching between two different full-screen views. */
2280 if (nviews != displayed_views() ||
2281 (nviews == 1 && base_view != display[0]))
a006db63 2282 resize_display();
b801d8b2 2283
a8891802 2284 if (split && prev->lineno - prev->offset >= prev->height) {
03a93dbb 2285 /* Take the title line into account. */
eb98559e 2286 int lines = prev->lineno - prev->offset - prev->height + 1;
03a93dbb
JF
2287
2288 /* Scroll the view that was split if the current line is
2289 * outside the new limited view. */
8c317212 2290 do_scroll_view(prev, lines);
03a93dbb
JF
2291 }
2292
6b161b31 2293 if (prev && view != prev) {
9b995f0c 2294 if (split && !backgrounded) {
f0b3ab80
JF
2295 /* "Blur" the previous view. */
2296 update_view_title(prev);
9f396969 2297 }
f0b3ab80 2298
f6da0b66 2299 view->parent = prev;
b801d8b2
JF
2300 }
2301
9f396969 2302 if (view->pipe && view->lines == 0) {
03a93dbb
JF
2303 /* Clear the old view and let the incremental updating refill
2304 * the screen. */
2305 wclear(view->win);
f97f4012 2306 report("");
03a93dbb
JF
2307 } else {
2308 redraw_view(view);
24b5b3e0 2309 report("");
03a93dbb 2310 }
6706b2ba
JF
2311
2312 /* If the view is backgrounded the above calls to report()
2313 * won't redraw the view title. */
2314 if (backgrounded)
2315 update_view_title(view);
b801d8b2
JF
2316}
2317
0cea0d43 2318static void
d24ef76c
JF
2319open_external_viewer(const char *cmd)
2320{
2321 def_prog_mode(); /* save current tty modes */
2322 endwin(); /* restore original tty modes */
2323 system(cmd);
2324 fprintf(stderr, "Press Enter to continue");
2325 getc(stdin);
2326 reset_prog_mode();
2327 redraw_display();
2328}
2329
2330static void
b5c18d9d
JF
2331open_mergetool(const char *file)
2332{
2333 char cmd[SIZEOF_STR];
2334 char file_sq[SIZEOF_STR];
2335
2336 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2337 string_format(cmd, "git mergetool %s", file_sq)) {
2338 open_external_viewer(cmd);
2339 }
2340}
2341
2342static void
d24ef76c 2343open_editor(bool from_root, const char *file)
0cea0d43
JF
2344{
2345 char cmd[SIZEOF_STR];
2346 char file_sq[SIZEOF_STR];
2347 char *editor;
7d31b059 2348 char *prefix = from_root ? opt_cdup : "";
0cea0d43
JF
2349
2350 editor = getenv("GIT_EDITOR");
2351 if (!editor && *opt_editor)
2352 editor = opt_editor;
2353 if (!editor)
2354 editor = getenv("VISUAL");
2355 if (!editor)
2356 editor = getenv("EDITOR");
2357 if (!editor)
2358 editor = "vi";
2359
2360 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
7d31b059 2361 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
d24ef76c 2362 open_external_viewer(cmd);
0cea0d43
JF
2363 }
2364}
b801d8b2 2365
9eb14b72
JF
2366static void
2367open_run_request(enum request request)
2368{
2369 struct run_request *req = get_run_request(request);
2370 char buf[SIZEOF_STR * 2];
2371 size_t bufpos;
2372 char *cmd;
2373
2374 if (!req) {
2375 report("Unknown run request");
2376 return;
2377 }
2378
2379 bufpos = 0;
2380 cmd = req->cmd;
2381
2382 while (cmd) {
2383 char *next = strstr(cmd, "%(");
2384 int len = next - cmd;
2385 char *value;
2386
2387 if (!next) {
2388 len = strlen(cmd);
2389 value = "";
2390
2391 } else if (!strncmp(next, "%(head)", 7)) {
2392 value = ref_head;
2393
2394 } else if (!strncmp(next, "%(commit)", 9)) {
2395 value = ref_commit;
2396
2397 } else if (!strncmp(next, "%(blob)", 7)) {
2398 value = ref_blob;
2399
2400 } else {
2401 report("Unknown replacement in run request: `%s`", req->cmd);
2402 return;
2403 }
2404
2405 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2406 return;
2407
2408 if (next)
2409 next = strchr(next, ')') + 1;
2410 cmd = next;
2411 }
2412
2413 open_external_viewer(buf);
2414}
2415
6b161b31
JF
2416/*
2417 * User request switch noodle
2418 */
2419
b801d8b2 2420static int
6b161b31 2421view_driver(struct view *view, enum request request)
b801d8b2 2422{
b801d8b2
JF
2423 int i;
2424
1bace428
JF
2425 if (request == REQ_NONE) {
2426 doupdate();
2427 return TRUE;
2428 }
2429
9eb14b72
JF
2430 if (request > REQ_NONE) {
2431 open_run_request(request);
2432 return TRUE;
2433 }
2434
586c423d
JF
2435 if (view && view->lines) {
2436 request = view->ops->request(view, request, &view->line[view->lineno]);
2437 if (request == REQ_NONE)
2438 return TRUE;
2439 }
2440
b801d8b2 2441 switch (request) {
4a2909a7
JF
2442 case REQ_MOVE_UP:
2443 case REQ_MOVE_DOWN:
2444 case REQ_MOVE_PAGE_UP:
2445 case REQ_MOVE_PAGE_DOWN:
2446 case REQ_MOVE_FIRST_LINE:
2447 case REQ_MOVE_LAST_LINE:
8522ecc7 2448 move_view(view, request);
fd85fef1
JF
2449 break;
2450
4a2909a7
JF
2451 case REQ_SCROLL_LINE_DOWN:
2452 case REQ_SCROLL_LINE_UP:
2453 case REQ_SCROLL_PAGE_DOWN:
2454 case REQ_SCROLL_PAGE_UP:
a28bcc22 2455 scroll_view(view, request);
b801d8b2
JF
2456 break;
2457
e733ee54
JF
2458 case REQ_VIEW_BLOB:
2459 if (!ref_blob[0]) {
550cd4b5
JF
2460 report("No file chosen, press %s to open tree view",
2461 get_key(REQ_VIEW_TREE));
e733ee54
JF
2462 break;
2463 }
5c4358d1
JF
2464 open_view(view, request, OPEN_DEFAULT);
2465 break;
2466
2467 case REQ_VIEW_PAGER:
b64c5b75 2468 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
5c4358d1
JF
2469 report("No pager content, press %s to run command from prompt",
2470 get_key(REQ_PROMPT));
2471 break;
2472 }
2473 open_view(view, request, OPEN_DEFAULT);
2474 break;
2475
3e634113
JF
2476 case REQ_VIEW_STAGE:
2477 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2478 report("No stage content, press %s to open the status view and choose file",
2479 get_key(REQ_VIEW_STATUS));
2480 break;
2481 }
2482 open_view(view, request, OPEN_DEFAULT);
2483 break;
2484
c38c64bb
JF
2485 case REQ_VIEW_STATUS:
2486 if (opt_is_inside_work_tree == FALSE) {
2487 report("The status view requires a working tree");
2488 break;
2489 }
2490 open_view(view, request, OPEN_DEFAULT);
2491 break;
2492
4a2909a7 2493 case REQ_VIEW_MAIN:
4a2909a7 2494 case REQ_VIEW_DIFF:
2e8488b4 2495 case REQ_VIEW_LOG:
e733ee54 2496 case REQ_VIEW_TREE:
2e8488b4 2497 case REQ_VIEW_HELP:
49f2b43f 2498 open_view(view, request, OPEN_DEFAULT);
b801d8b2
JF
2499 break;
2500
b3a54cba
JF
2501 case REQ_NEXT:
2502 case REQ_PREVIOUS:
2503 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2504
e733ee54
JF
2505 if ((view == VIEW(REQ_VIEW_DIFF) &&
2506 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3e634113 2507 (view == VIEW(REQ_VIEW_STAGE) &&
b9b5b4cd 2508 view->parent == VIEW(REQ_VIEW_STATUS)) ||
e733ee54
JF
2509 (view == VIEW(REQ_VIEW_BLOB) &&
2510 view->parent == VIEW(REQ_VIEW_TREE))) {
03400136
WF
2511 int line;
2512
b3a54cba 2513 view = view->parent;
03400136 2514 line = view->lineno;
8522ecc7
JF
2515 move_view(view, request);
2516 if (view_is_displayed(view))
f0b3ab80 2517 update_view_title(view);
328d27f7
JF
2518 if (line != view->lineno)
2519 view->ops->request(view, REQ_ENTER,
2520 &view->line[view->lineno]);
2521
b3a54cba 2522 } else {
8522ecc7 2523 move_view(view, request);
b3a54cba 2524 }
328d27f7 2525 break;
6b161b31 2526
03a93dbb
JF
2527 case REQ_VIEW_NEXT:
2528 {
9f41488f 2529 int nviews = displayed_views();
03a93dbb
JF
2530 int next_view = (current_view + 1) % nviews;
2531
2532 if (next_view == current_view) {
2533 report("Only one view is displayed");
2534 break;
2535 }
2536
2537 current_view = next_view;
2538 /* Blur out the title of the previous view. */
2539 update_view_title(view);
6734f6b9 2540 report("");
03a93dbb
JF
2541 break;
2542 }
acaef3b3
JF
2543 case REQ_REFRESH:
2544 report("Refreshing is not yet supported for the %s view", view->name);
2545 break;
2546
24b5b3e0 2547 case REQ_TOGGLE_LINENO:
b76c2afc 2548 opt_line_number = !opt_line_number;
20bb5e18 2549 redraw_display();
b801d8b2
JF
2550 break;
2551
823057f4
DV
2552 case REQ_TOGGLE_DATE:
2553 opt_date = !opt_date;
2554 redraw_display();
2555 break;
2556
2557 case REQ_TOGGLE_AUTHOR:
2558 opt_author = !opt_author;
2559 redraw_display();
2560 break;
2561
54efb62b
JF
2562 case REQ_TOGGLE_REV_GRAPH:
2563 opt_rev_graph = !opt_rev_graph;
2564 redraw_display();
2565 break;
2566
823057f4
DV
2567 case REQ_TOGGLE_REFS:
2568 opt_show_refs = !opt_show_refs;
2569 redraw_display();
2570 break;
2571
03a93dbb 2572 case REQ_PROMPT:
8855ada4 2573 /* Always reload^Wrerun commands from the prompt. */
49f2b43f 2574 open_view(view, opt_request, OPEN_RELOAD);
03a93dbb
JF
2575 break;
2576
4af34daa
JF
2577 case REQ_SEARCH:
2578 case REQ_SEARCH_BACK:
c02d8fce 2579 search_view(view, request);
4af34daa
JF
2580 break;
2581
2582 case REQ_FIND_NEXT:
2583 case REQ_FIND_PREV:
2584 find_next(view, request);
2585 break;
2586
4a2909a7 2587 case REQ_STOP_LOADING:
59a45d3a
JF
2588 for (i = 0; i < ARRAY_SIZE(views); i++) {
2589 view = &views[i];
2e8488b4 2590 if (view->pipe)
6a7bb912 2591 report("Stopped loading the %s view", view->name),
03a93dbb
JF
2592 end_update(view);
2593 }
b801d8b2
JF
2594 break;
2595
4a2909a7 2596 case REQ_SHOW_VERSION:
ec31d0d0 2597 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
b801d8b2
JF
2598 return TRUE;
2599
fac7db6c
JF
2600 case REQ_SCREEN_RESIZE:
2601 resize_display();
2602 /* Fall-through */
4a2909a7 2603 case REQ_SCREEN_REDRAW:
20bb5e18 2604 redraw_display();
4a2909a7
JF
2605 break;
2606
0cea0d43
JF
2607 case REQ_EDIT:
2608 report("Nothing to edit");
531c6c69
JF
2609 break;
2610
226da94b 2611
531c6c69
JF
2612 case REQ_ENTER:
2613 report("Nothing to enter");
2614 break;
ca1d71ea 2615
b801d8b2 2616
4f9b667a 2617 case REQ_VIEW_CLOSE:
2fcf5401
JF
2618 /* XXX: Mark closed views by letting view->parent point to the
2619 * view itself. Parents to closed view should never be
2620 * followed. */
2621 if (view->parent &&
2622 view->parent->parent != view->parent) {
4f9b667a
JF
2623 memset(display, 0, sizeof(display));
2624 current_view = 0;
f6da0b66 2625 display[current_view] = view->parent;
2fcf5401 2626 view->parent = view;
4f9b667a
JF
2627 resize_display();
2628 redraw_display();
2629 break;
2630 }
2631 /* Fall-through */
b801d8b2
JF
2632 case REQ_QUIT:
2633 return FALSE;
2634
2635 default:
2e8488b4 2636 /* An unknown key will show most commonly used commands. */
468876c9 2637 report("Unknown key, press 'h' for help");
b801d8b2
JF
2638 return TRUE;
2639 }
2640
2641 return TRUE;
2642}
2643
2644
2645/*
ff26aa29 2646 * Pager backend
b801d8b2
JF
2647 */
2648
6b161b31 2649static bool
5dcf8064 2650pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
b801d8b2 2651{
fe7233c3
JF
2652 char *text = line->data;
2653 enum line_type type = line->type;
78c70acd 2654 int attr;
b801d8b2 2655
6706b2ba
JF
2656 wmove(view->win, lineno, 0);
2657
5dcf8064 2658 if (selected) {
78c70acd 2659 type = LINE_CURSOR;
6706b2ba 2660 wchgat(view->win, -1, 0, type, NULL);
fd85fef1
JF
2661 }
2662
78c70acd 2663 attr = get_line_attr(type);
b801d8b2 2664 wattrset(view->win, attr);
b76c2afc 2665
6706b2ba
JF
2666 if (opt_line_number || opt_tab_size < TABSIZE) {
2667 static char spaces[] = " ";
2668 int col_offset = 0, col = 0;
2669
2670 if (opt_line_number) {
2671 unsigned long real_lineno = view->offset + lineno + 1;
82e78006 2672
6706b2ba
JF
2673 if (real_lineno == 1 ||
2674 (real_lineno % opt_num_interval) == 0) {
2675 wprintw(view->win, "%.*d", view->digits, real_lineno);
8855ada4 2676
6706b2ba
JF
2677 } else {
2678 waddnstr(view->win, spaces,
2679 MIN(view->digits, STRING_SIZE(spaces)));
2680 }
2681 waddstr(view->win, ": ");
2682 col_offset = view->digits + 2;
2683 }
8855ada4 2684
fe7233c3 2685 while (text && col_offset + col < view->width) {
6706b2ba 2686 int cols_max = view->width - col_offset - col;
fe7233c3 2687 char *pos = text;
6706b2ba 2688 int cols;
4c6fabc2 2689
fe7233c3
JF
2690 if (*text == '\t') {
2691 text++;
6706b2ba 2692 assert(sizeof(spaces) > TABSIZE);
fe7233c3 2693 pos = spaces;
6706b2ba 2694 cols = opt_tab_size - (col % opt_tab_size);
82e78006 2695
b76c2afc 2696 } else {
fe7233c3
JF
2697 text = strchr(text, '\t');
2698 cols = line ? text - pos : strlen(pos);
b76c2afc 2699 }
6706b2ba 2700
fe7233c3 2701 waddnstr(view->win, pos, MIN(cols, cols_max));
6706b2ba 2702 col += cols;
b76c2afc 2703 }
b76c2afc
JF
2704
2705 } else {
749cdc92 2706 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
b801d8b2 2707
749cdc92 2708 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
6706b2ba 2709 }
2e8488b4 2710
b801d8b2
JF
2711 return TRUE;
2712}
2713
dc23c0e3 2714static bool
d65ced0d 2715add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
dc23c0e3 2716{
17482b11 2717 char refbuf[SIZEOF_STR];
dc23c0e3
JF
2718 char *ref = NULL;
2719 FILE *pipe;
2720
d3c345f7 2721 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
dc23c0e3
JF
2722 return TRUE;
2723
2724 pipe = popen(refbuf, "r");
2725 if (!pipe)
2726 return TRUE;
2727
2728 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2729 ref = chomp_string(ref);
2730 pclose(pipe);
2731
2732 if (!ref || !*ref)
2733 return TRUE;
2734
2735 /* This is the only fatal call, since it can "corrupt" the buffer. */
17482b11 2736 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
dc23c0e3
JF
2737 return FALSE;
2738
2739 return TRUE;
2740}
2741
7b99a34c
JF
2742static void
2743add_pager_refs(struct view *view, struct line *line)
2744{
17482b11 2745 char buf[SIZEOF_STR];
9295982a 2746 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
7b99a34c 2747 struct ref **refs;
d65ced0d 2748 size_t bufpos = 0, refpos = 0;
7b99a34c 2749 const char *sep = "Refs: ";
dc23c0e3 2750 bool is_tag = FALSE;
7b99a34c
JF
2751
2752 assert(line->type == LINE_COMMIT);
2753
c9ca1ec3 2754 refs = get_refs(commit_id);
dc23c0e3
JF
2755 if (!refs) {
2756 if (view == VIEW(REQ_VIEW_DIFF))
2757 goto try_add_describe_ref;
7b99a34c 2758 return;
dc23c0e3 2759 }
7b99a34c
JF
2760
2761 do {
cc2d1364 2762 struct ref *ref = refs[refpos];
e15ec88e
JF
2763 char *fmt = ref->tag ? "%s[%s]" :
2764 ref->remote ? "%s<%s>" : "%s%s";
7b99a34c 2765
cc2d1364
JF
2766 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2767 return;
7b99a34c 2768 sep = ", ";
dc23c0e3
JF
2769 if (ref->tag)
2770 is_tag = TRUE;
7b99a34c
JF
2771 } while (refs[refpos++]->next);
2772
dc23c0e3
JF
2773 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2774try_add_describe_ref:
d42c8a35 2775 /* Add <tag>-g<commit_id> "fake" reference. */
dc23c0e3
JF
2776 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2777 return;
2778 }
2779
d42c8a35
JF
2780 if (bufpos == 0)
2781 return;
2782
cc2d1364 2783 if (!realloc_lines(view, view->line_size + 1))
7b99a34c
JF
2784 return;
2785
0a0d8910 2786 add_line_text(view, buf, LINE_PP_REFS);
7b99a34c
JF
2787}
2788
6b161b31 2789static bool
701e4f5d 2790pager_read(struct view *view, char *data)
22f66b0a 2791{
0a0d8910 2792 struct line *line;
22f66b0a 2793
be04d936
JF
2794 if (!data)
2795 return TRUE;
2796
0a0d8910
JF
2797 line = add_line_text(view, data, get_line_type(data));
2798 if (!line)
7b99a34c 2799 return FALSE;
fe7233c3 2800
7b99a34c
JF
2801 if (line->type == LINE_COMMIT &&
2802 (view == VIEW(REQ_VIEW_DIFF) ||
2803 view == VIEW(REQ_VIEW_LOG)))
2804 add_pager_refs(view, line);
2805
22f66b0a
JF
2806 return TRUE;
2807}
2808
586c423d
JF
2809static enum request
2810pager_request(struct view *view, enum request request, struct line *line)
6b161b31 2811{
91e8e277 2812 int split = 0;
6b161b31 2813
586c423d
JF
2814 if (request != REQ_ENTER)
2815 return request;
2816
9fbbd28f
JF
2817 if (line->type == LINE_COMMIT &&
2818 (view == VIEW(REQ_VIEW_LOG) ||
2819 view == VIEW(REQ_VIEW_PAGER))) {
91e8e277
JF
2820 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2821 split = 1;
67e48ac5
JF
2822 }
2823
91e8e277
JF
2824 /* Always scroll the view even if it was split. That way
2825 * you can use Enter to scroll through the log view and
2826 * split open each commit diff. */
2827 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2828
2829 /* FIXME: A minor workaround. Scrolling the view will call report("")
9d82d824
JF
2830 * but if we are scrolling a non-current view this won't properly
2831 * update the view title. */
91e8e277
JF
2832 if (split)
2833 update_view_title(view);
6b161b31 2834
586c423d 2835 return REQ_NONE;
6b161b31
JF
2836}
2837
4af34daa
JF
2838static bool
2839pager_grep(struct view *view, struct line *line)
2840{
2841 regmatch_t pmatch;
2842 char *text = line->data;
2843
2844 if (!*text)
2845 return FALSE;
2846
b77b2cb8 2847 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
4af34daa
JF
2848 return FALSE;
2849
2850 return TRUE;
2851}
2852
d720de4b
JF
2853static void
2854pager_select(struct view *view, struct line *line)
2855{
2856 if (line->type == LINE_COMMIT) {
9295982a 2857 char *text = (char *)line->data + STRING_SIZE("commit ");
d720de4b 2858
035ba11f 2859 if (view != VIEW(REQ_VIEW_PAGER))
2463b4ea
JF
2860 string_copy_rev(view->ref, text);
2861 string_copy_rev(ref_commit, text);
d720de4b
JF
2862 }
2863}
2864
6b161b31 2865static struct view_ops pager_ops = {
6734f6b9 2866 "line",
f098944b 2867 NULL,
6b161b31 2868 pager_read,
f098944b 2869 pager_draw,
586c423d 2870 pager_request,
f098944b
JF
2871 pager_grep,
2872 pager_select,
2873};
2874
2875
2876/*
2877 * Help backend
2878 */
2879
2880static bool
2881help_open(struct view *view)
2882{
2883 char buf[BUFSIZ];
2884 int lines = ARRAY_SIZE(req_info) + 2;
2885 int i;
2886
2887 if (view->lines > 0)
2888 return TRUE;
2889
2890 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2891 if (!req_info[i].request)
2892 lines++;
2893
9eb14b72
JF
2894 lines += run_requests + 1;
2895
f098944b
JF
2896 view->line = calloc(lines, sizeof(*view->line));
2897 if (!view->line)
2898 return FALSE;
2899
2900 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2901
2902 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2903 char *key;
2904
0e4360b6
JF
2905 if (req_info[i].request == REQ_NONE)
2906 continue;
2907
f098944b
JF
2908 if (!req_info[i].request) {
2909 add_line_text(view, "", LINE_DEFAULT);
2910 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2911 continue;
2912 }
2913
2914 key = get_key(req_info[i].request);
0e4360b6
JF
2915 if (!*key)
2916 key = "(no key defined)";
2917
f098944b
JF
2918 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2919 continue;
2920
2921 add_line_text(view, buf, LINE_DEFAULT);
2922 }
2923
9eb14b72
JF
2924 if (run_requests) {
2925 add_line_text(view, "", LINE_DEFAULT);
2926 add_line_text(view, "External commands:", LINE_DEFAULT);
2927 }
2928
2929 for (i = 0; i < run_requests; i++) {
2930 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2931 char *key;
2932
2933 if (!req)
2934 continue;
2935
2936 key = get_key_name(req->key);
2937 if (!*key)
2938 key = "(no key defined)";
2939
2940 if (!string_format(buf, " %-10s %-14s `%s`",
2941 keymap_table[req->keymap].name,
2942 key, req->cmd))
2943 continue;
2944
2945 add_line_text(view, buf, LINE_DEFAULT);
2946 }
2947
f098944b
JF
2948 return TRUE;
2949}
2950
2951static struct view_ops help_ops = {
2952 "line",
2953 help_open,
2954 NULL,
2955 pager_draw,
586c423d 2956 pager_request,
4af34daa 2957 pager_grep,
d720de4b 2958 pager_select,
6b161b31
JF
2959};
2960
80ce96ea 2961
ff26aa29 2962/*
e733ee54
JF
2963 * Tree backend
2964 */
2965
69efc854
JF
2966struct tree_stack_entry {
2967 struct tree_stack_entry *prev; /* Entry below this in the stack */
2968 unsigned long lineno; /* Line number to restore */
2969 char *name; /* Position of name in opt_path */
2970};
2971
2972/* The top of the path stack. */
2973static struct tree_stack_entry *tree_stack = NULL;
2974unsigned long tree_lineno = 0;
2975
2976static void
2977pop_tree_stack_entry(void)
2978{
2979 struct tree_stack_entry *entry = tree_stack;
2980
2981 tree_lineno = entry->lineno;
2982 entry->name[0] = 0;
2983 tree_stack = entry->prev;
2984 free(entry);
2985}
2986
2987static void
2988push_tree_stack_entry(char *name, unsigned long lineno)
2989{
2990 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2991 size_t pathlen = strlen(opt_path);
2992
2993 if (!entry)
2994 return;
2995
2996 entry->prev = tree_stack;
2997 entry->name = opt_path + pathlen;
2998 tree_stack = entry;
2999
3000 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3001 pop_tree_stack_entry();
3002 return;
3003 }
3004
3005 /* Move the current line to the first tree entry. */
3006 tree_lineno = 1;
3007 entry->lineno = lineno;
3008}
3009
4795d620 3010/* Parse output from git-ls-tree(1):
e733ee54
JF
3011 *
3012 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3013 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3014 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3015 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3016 */
3017
3018#define SIZEOF_TREE_ATTR \
3019 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3020
3021#define TREE_UP_FORMAT "040000 tree %s\t.."
3022
3023static int
3024tree_compare_entry(enum line_type type1, char *name1,
3025 enum line_type type2, char *name2)
3026{
3027 if (type1 != type2) {
3028 if (type1 == LINE_TREE_DIR)
3029 return -1;
3030 return 1;
3031 }
3032
3033 return strcmp(name1, name2);
3034}
3035
3036static bool
3037tree_read(struct view *view, char *text)
3038{
be04d936 3039 size_t textlen = text ? strlen(text) : 0;
e733ee54
JF
3040 char buf[SIZEOF_STR];
3041 unsigned long pos;
3042 enum line_type type;
f88a5319 3043 bool first_read = view->lines == 0;
e733ee54
JF
3044
3045 if (textlen <= SIZEOF_TREE_ATTR)
3046 return FALSE;
3047
3048 type = text[STRING_SIZE("100644 ")] == 't'
3049 ? LINE_TREE_DIR : LINE_TREE_FILE;
3050
f88a5319 3051 if (first_read) {
e733ee54 3052 /* Add path info line */
0a0d8910
JF
3053 if (!string_format(buf, "Directory path /%s", opt_path) ||
3054 !realloc_lines(view, view->line_size + 1) ||
3055 !add_line_text(view, buf, LINE_DEFAULT))
e733ee54
JF
3056 return FALSE;
3057
3058 /* Insert "link" to parent directory. */
0a0d8910
JF
3059 if (*opt_path) {
3060 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3061 !realloc_lines(view, view->line_size + 1) ||
3062 !add_line_text(view, buf, LINE_TREE_DIR))
3063 return FALSE;
3064 }
e733ee54
JF
3065 }
3066
3067 /* Strip the path part ... */
3068 if (*opt_path) {
3069 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3070 size_t striplen = strlen(opt_path);
3071 char *path = text + SIZEOF_TREE_ATTR;
3072
3073 if (pathlen > striplen)
3074 memmove(path, path + striplen,
3075 pathlen - striplen + 1);
3076 }
3077
3078 /* Skip "Directory ..." and ".." line. */
3079 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3080 struct line *line = &view->line[pos];
3081 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3082 char *path2 = text + SIZEOF_TREE_ATTR;
3083 int cmp = tree_compare_entry(line->type, path1, type, path2);
3084
3085 if (cmp <= 0)
3086 continue;
3087
3088 text = strdup(text);
3089 if (!text)
3090 return FALSE;
3091
3092 if (view->lines > pos)
3093 memmove(&view->line[pos + 1], &view->line[pos],
3094 (view->lines - pos) * sizeof(*line));
3095
3096 line = &view->line[pos];
3097 line->data = text;
3098 line->type = type;
3099 view->lines++;
3100 return TRUE;
3101 }
3102
0a0d8910 3103 if (!add_line_text(view, text, type))
e733ee54
JF
3104 return FALSE;
3105
69efc854
JF
3106 if (tree_lineno > view->lineno) {
3107 view->lineno = tree_lineno;
3108 tree_lineno = 0;
3109 }
f88a5319 3110
e733ee54
JF
3111 return TRUE;
3112}
3113
586c423d
JF
3114static enum request
3115tree_request(struct view *view, enum request request, struct line *line)
e733ee54 3116{
aac64c17 3117 enum open_flags flags;
586c423d 3118
c509eed2
DV
3119 if (request == REQ_TREE_PARENT) {
3120 if (*opt_path) {
3121 /* fake 'cd ..' */
3122 request = REQ_ENTER;
3123 line = &view->line[1];
3124 } else {
3125 /* quit view if at top of tree */
3126 return REQ_VIEW_CLOSE;
3127 }
3128 }
586c423d
JF
3129 if (request != REQ_ENTER)
3130 return request;
e733ee54 3131
69efc854
JF
3132 /* Cleanup the stack if the tree view is at a different tree. */
3133 while (!*opt_path && tree_stack)
3134 pop_tree_stack_entry();
3135
e733ee54
JF
3136 switch (line->type) {
3137 case LINE_TREE_DIR:
3138 /* Depending on whether it is a subdir or parent (updir?) link
3139 * mangle the path buffer. */
3140 if (line == &view->line[1] && *opt_path) {
69efc854 3141 pop_tree_stack_entry();
e733ee54
JF
3142
3143 } else {
4795d620 3144 char *data = line->data;
e733ee54
JF
3145 char *basename = data + SIZEOF_TREE_ATTR;
3146
69efc854 3147 push_tree_stack_entry(basename, view->lineno);
e733ee54
JF
3148 }
3149
3150 /* Trees and subtrees share the same ID, so they are not not
3151 * unique like blobs. */
aac64c17 3152 flags = OPEN_RELOAD;
e733ee54
JF
3153 request = REQ_VIEW_TREE;
3154 break;
3155
3156 case LINE_TREE_FILE:
aac64c17 3157 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
e733ee54
JF
3158 request = REQ_VIEW_BLOB;
3159 break;
3160
3161 default:
3162 return TRUE;
3163 }
3164
3165 open_view(view, request, flags);
69efc854
JF
3166 if (request == REQ_VIEW_TREE) {
3167 view->lineno = tree_lineno;
3168 }
e733ee54 3169
586c423d 3170 return REQ_NONE;
e733ee54
JF
3171}
3172
d720de4b
JF
3173static void
3174tree_select(struct view *view, struct line *line)
3175{
9295982a 3176 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
73c76ef5
JF
3177
3178 if (line->type == LINE_TREE_FILE) {
2463b4ea 3179 string_copy_rev(ref_blob, text);
d720de4b 3180
ebbaf4fe
JF
3181 } else if (line->type != LINE_TREE_DIR) {
3182 return;
d720de4b 3183 }
ebbaf4fe 3184
2463b4ea 3185 string_copy_rev(view->ref, text);
d720de4b
JF
3186}
3187
e733ee54
JF
3188static struct view_ops tree_ops = {
3189 "file",
f098944b 3190 NULL,
e733ee54 3191 tree_read,
f098944b 3192 pager_draw,
586c423d 3193 tree_request,
e733ee54 3194 pager_grep,
d720de4b 3195 tree_select,
e733ee54
JF
3196};
3197
3198static bool
3199blob_read(struct view *view, char *line)
3200{
c115e7ac 3201 return add_line_text(view, line, LINE_DEFAULT) != NULL;
e733ee54
JF
3202}
3203
3204static struct view_ops blob_ops = {
3205 "line",
f098944b 3206 NULL,
e733ee54 3207 blob_read,
f098944b 3208 pager_draw,
586c423d 3209 pager_request,
e733ee54 3210 pager_grep,
d720de4b 3211 pager_select,
e733ee54
JF
3212};
3213
3214
3215/*
173d76ea
JF
3216 * Status backend
3217 */
3218
3219struct status {
3220 char status;
3221 struct {
3222 mode_t mode;
3223 char rev[SIZEOF_REV];
5ba030cf 3224 char name[SIZEOF_STR];
173d76ea
JF
3225 } old;
3226 struct {
3227 mode_t mode;
3228 char rev[SIZEOF_REV];
5ba030cf 3229 char name[SIZEOF_STR];
173d76ea 3230 } new;
173d76ea
JF
3231};
3232
b33611d8
JF
3233static struct status stage_status;
3234static enum line_type stage_line_type;
3235
173d76ea
JF
3236/* Get fields from the diff line:
3237 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3238 */
3239static inline bool
3240status_get_diff(struct status *file, char *buf, size_t bufsize)
3241{
3242 char *old_mode = buf + 1;
3243 char *new_mode = buf + 8;
3244 char *old_rev = buf + 15;
3245 char *new_rev = buf + 56;
3246 char *status = buf + 97;
3247
5ba030cf 3248 if (bufsize < 99 ||
173d76ea
JF
3249 old_mode[-1] != ':' ||
3250 new_mode[-1] != ' ' ||
3251 old_rev[-1] != ' ' ||
3252 new_rev[-1] != ' ' ||
3253 status[-1] != ' ')
3254 return FALSE;
3255
3256 file->status = *status;
3257
3258 string_copy_rev(file->old.rev, old_rev);
3259 string_copy_rev(file->new.rev, new_rev);
3260
3261 file->old.mode = strtoul(old_mode, NULL, 8);
3262 file->new.mode = strtoul(new_mode, NULL, 8);
3263
5ba030cf 3264 file->old.name[0] = file->new.name[0] = 0;
173d76ea
JF
3265
3266 return TRUE;
3267}
3268
3269static bool
3270status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3271{
3272 struct status *file = NULL;
b5c18d9d 3273 struct status *unmerged = NULL;
173d76ea
JF
3274 char buf[SIZEOF_STR * 4];
3275 size_t bufsize = 0;
3276 FILE *pipe;
3277
3278 pipe = popen(cmd, "r");
3279 if (!pipe)
3280 return FALSE;
3281
3282 add_line_data(view, NULL, type);
3283
3284 while (!feof(pipe) && !ferror(pipe)) {
3285 char *sep;
3286 size_t readsize;
3287
3288 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3289 if (!readsize)
3290 break;
3291 bufsize += readsize;
3292
3293 /* Process while we have NUL chars. */
3294 while ((sep = memchr(buf, 0, bufsize))) {
3295 size_t sepsize = sep - buf + 1;
3296
3297 if (!file) {
3298 if (!realloc_lines(view, view->line_size + 1))
3299 goto error_out;
3300
3301 file = calloc(1, sizeof(*file));
3302 if (!file)
3303 goto error_out;
3304
3305 add_line_data(view, file, type);
3306 }
3307
3308 /* Parse diff info part. */
3309 if (!diff) {
3310 file->status = '?';
3311
3312 } else if (!file->status) {
3313 if (!status_get_diff(file, buf, sepsize))
3314 goto error_out;
3315
3316 bufsize -= sepsize;
3317 memmove(buf, sep + 1, bufsize);
3318
3319 sep = memchr(buf, 0, bufsize);
3320 if (!sep)
3321 break;
3322 sepsize = sep - buf + 1;
b5c18d9d
JF
3323
3324 /* Collapse all 'M'odified entries that
3325 * follow a associated 'U'nmerged entry.
3326 */
3327 if (file->status == 'U') {
3328 unmerged = file;
3329
3330 } else if (unmerged) {
5ba030cf 3331 int collapse = !strcmp(buf, unmerged->new.name);
b5c18d9d
JF
3332
3333 unmerged = NULL;
3334 if (collapse) {
3335 free(file);
3336 view->lines--;
3337 continue;
3338 }
3339 }
173d76ea
JF
3340 }
3341
5ba030cf
JF
3342 /* Grab the old name for rename/copy. */
3343 if (!*file->old.name &&
3344 (file->status == 'R' || file->status == 'C')) {
3345 sepsize = sep - buf + 1;
3346 string_ncopy(file->old.name, buf, sepsize);
3347 bufsize -= sepsize;
3348 memmove(buf, sep + 1, bufsize);
3349
3350 sep = memchr(buf, 0, bufsize);
3351 if (!sep)
3352 break;
3353 sepsize = sep - buf + 1;
3354 }
3355
173d76ea
JF
3356 /* git-ls-files just delivers a NUL separated
3357 * list of file names similar to the second half
3358 * of the git-diff-* output. */
5ba030cf
JF
3359 string_ncopy(file->new.name, buf, sepsize);
3360 if (!*file->old.name)
3361 string_copy(file->old.name, file->new.name);
173d76ea
JF
3362 bufsize -= sepsize;
3363 memmove(buf, sep + 1, bufsize);
3364 file = NULL;
3365 }
3366 }
3367
3368 if (ferror(pipe)) {
3369error_out:
3370 pclose(pipe);
3371 return FALSE;
3372 }
3373
3374 if (!view->line[view->lines - 1].data)
3375 add_line_data(view, NULL, LINE_STAT_NONE);
3376
3377 pclose(pipe);
3378 return TRUE;
3379}
3380
b5c18d9d 3381/* Don't show unmerged entries in the staged section. */
5ba030cf 3382#define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
b1744cbe 3383#define STATUS_DIFF_FILES_CMD "git diff-files -z"
173d76ea 3384#define STATUS_LIST_OTHER_CMD \
810f0078 3385 "git ls-files -z --others --exclude-per-directory=.gitignore"
173d76ea 3386
f99c6095 3387#define STATUS_DIFF_INDEX_SHOW_CMD \
5ba030cf 3388 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
f99c6095
JF
3389
3390#define STATUS_DIFF_FILES_SHOW_CMD \
5ba030cf 3391 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
89d917a2 3392
173d76ea
JF
3393/* First parse staged info using git-diff-index(1), then parse unstaged
3394 * info using git-diff-files(1), and finally untracked files using
3395 * git-ls-files(1). */
3396static bool
3397status_open(struct view *view)
3398{
810f0078
JF
3399 struct stat statbuf;
3400 char exclude[SIZEOF_STR];
3401 char cmd[SIZEOF_STR];
12e8c2be 3402 unsigned long prev_lineno = view->lineno;
173d76ea
JF
3403 size_t i;
3404
3405 for (i = 0; i < view->lines; i++)
3406 free(view->line[i].data);
3407 free(view->line);
12e8c2be 3408 view->lines = view->line_size = view->lineno = 0;
173d76ea
JF
3409 view->line = NULL;
3410
3411 if (!realloc_lines(view, view->line_size + 6))
3412 return FALSE;
3413
810f0078
JF
3414 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3415 return FALSE;
3416
3417 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3418
3419 if (stat(exclude, &statbuf) >= 0) {
3420 size_t cmdsize = strlen(cmd);
3421
3422 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3423 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3424 return FALSE;
3425 }
3426
b1744cbe
JF
3427 system("git update-index -q --refresh");
3428
173d76ea
JF
3429 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3430 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
810f0078 3431 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
173d76ea
JF
3432 return FALSE;
3433
12e8c2be
JF
3434 /* If all went well restore the previous line number to stay in
3435 * the context. */
3436 if (prev_lineno < view->lines)
3437 view->lineno = prev_lineno;
3438 else
3439 view->lineno = view->lines - 1;
3440
173d76ea
JF
3441 return TRUE;
3442}
3443
3444static bool
3445status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3446{
3447 struct status *status = line->data;
749cdc92 3448 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
173d76ea
JF
3449
3450 wmove(view->win, lineno, 0);
3451
3452 if (selected) {
3453 wattrset(view->win, get_line_attr(LINE_CURSOR));
3454 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
749cdc92 3455 tilde_attr = -1;
173d76ea
JF
3456
3457 } else if (!status && line->type != LINE_STAT_NONE) {
3458 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3459 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3460
3461 } else {
3462 wattrset(view->win, get_line_attr(line->type));
3463 }
3464
3465 if (!status) {
3466 char *text;
3467
3468 switch (line->type) {
3469 case LINE_STAT_STAGED:
3470 text = "Changes to be committed:";
3471 break;
3472
3473 case LINE_STAT_UNSTAGED:
3474 text = "Changed but not updated:";
3475 break;
3476
3477 case LINE_STAT_UNTRACKED:
3478 text = "Untracked files:";
3479 break;
3480
3481 case LINE_STAT_NONE:
3482 text = " (no files)";
3483 break;
3484
3485 default:
3486 return FALSE;
3487 }
3488
749cdc92 3489 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
173d76ea
JF
3490 return TRUE;
3491 }
3492
3493 waddch(view->win, status->status);
3494 if (!selected)
3495 wattrset(view->win, A_NORMAL);
3496 wmove(view->win, lineno, 4);
749cdc92
JF
3497 if (view->width < 5)
3498 return TRUE;
173d76ea 3499
749cdc92 3500 draw_text(view, status->new.name, view->width - 5, 5, TRUE, tilde_attr);
173d76ea
JF
3501 return TRUE;
3502}
3503
586c423d 3504static enum request
88f66e2d 3505status_enter(struct view *view, struct line *line)
173d76ea 3506{
89d917a2 3507 struct status *status = line->data;
5ba030cf
JF
3508 char oldpath[SIZEOF_STR] = "";
3509 char newpath[SIZEOF_STR] = "";
89d917a2
JF
3510 char *info;
3511 size_t cmdsize = 0;
3512
4e8159cf
JF
3513 if (line->type == LINE_STAT_NONE ||
3514 (!status && line[1].type == LINE_STAT_NONE)) {
3515 report("No file to diff");
586c423d 3516 return REQ_NONE;
89d917a2
JF
3517 }
3518
5ba030cf
JF
3519 if (status) {
3520 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
3521 return REQ_QUIT;
3522 /* Diffs for unmerged entries are empty when pasing the
3523 * new path, so leave it empty. */
3524 if (status->status != 'U' &&
3525 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
3526 return REQ_QUIT;
3527 }
89d917a2
JF
3528
3529 if (opt_cdup[0] &&
3530 line->type != LINE_STAT_UNTRACKED &&
3531 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
586c423d 3532 return REQ_QUIT;
89d917a2
JF
3533
3534 switch (line->type) {
3535 case LINE_STAT_STAGED:
f99c6095 3536 if (!string_format_from(opt_cmd, &cmdsize,
5ba030cf 3537 STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
586c423d 3538 return REQ_QUIT;
4e8159cf
JF
3539 if (status)
3540 info = "Staged changes to %s";
3541 else
3542 info = "Staged changes";
89d917a2
JF
3543 break;
3544
3545 case LINE_STAT_UNSTAGED:
f99c6095 3546 if (!string_format_from(opt_cmd, &cmdsize,
5ba030cf 3547 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
586c423d 3548 return REQ_QUIT;
4e8159cf
JF
3549 if (status)
3550 info = "Unstaged changes to %s";
3551 else
3552 info = "Unstaged changes";
89d917a2
JF
3553 break;
3554
3555 case LINE_STAT_UNTRACKED:
3556 if (opt_pipe)
586c423d
JF
3557 return REQ_QUIT;
3558
4e8159cf
JF
3559
3560 if (!status) {
3561 report("No file to show");
586c423d 3562 return REQ_NONE;
4e8159cf
JF
3563 }
3564
5ba030cf 3565 opt_pipe = fopen(status->new.name, "r");
89d917a2
JF
3566 info = "Untracked file %s";
3567 break;
3568
3569 default:
e77aa656 3570 die("line type %d not handled in switch", line->type);
89d917a2
JF
3571 }
3572
3e634113
JF
3573 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3574 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
b33611d8
JF
3575 if (status) {
3576 stage_status = *status;
3577 } else {
3578 memset(&stage_status, 0, sizeof(stage_status));
3579 }
3580
3581 stage_line_type = line->type;
5ba030cf 3582 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
89d917a2
JF
3583 }
3584
586c423d 3585 return REQ_NONE;
ca1d71ea
JF
3586}
3587
88f66e2d 3588
ca1d71ea
JF
3589static bool
3590status_update_file(struct view *view, struct status *status, enum line_type type)
3591{
91c5d983 3592 char cmd[SIZEOF_STR];
173d76ea 3593 char buf[SIZEOF_STR];
91c5d983 3594 size_t cmdsize = 0;
173d76ea
JF
3595 size_t bufsize = 0;
3596 size_t written = 0;
3597 FILE *pipe;
3598
91c5d983 3599 if (opt_cdup[0] &&
ca1d71ea 3600 type != LINE_STAT_UNTRACKED &&
91c5d983
JF
3601 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3602 return FALSE;
3603
ca1d71ea 3604 switch (type) {
173d76ea
JF
3605 case LINE_STAT_STAGED:
3606 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3607 status->old.mode,
3608 status->old.rev,
5ba030cf 3609 status->old.name, 0))
173d76ea 3610 return FALSE;
91c5d983
JF
3611
3612 string_add(cmd, cmdsize, "git update-index -z --index-info");
173d76ea
JF
3613 break;
3614
3615 case LINE_STAT_UNSTAGED:
3616 case LINE_STAT_UNTRACKED:
5ba030cf 3617 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
173d76ea 3618 return FALSE;
91c5d983
JF
3619
3620 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
173d76ea
JF
3621 break;
3622
3623 default:
e77aa656 3624 die("line type %d not handled in switch", type);
173d76ea
JF
3625 }
3626
3627 pipe = popen(cmd, "w");
3628 if (!pipe)
3629 return FALSE;
3630
3631 while (!ferror(pipe) && written < bufsize) {
3632 written += fwrite(buf + written, 1, bufsize - written, pipe);
3633 }
3634
3635 pclose(pipe);
3636
3637 if (written != bufsize)
3638 return FALSE;
3639
173d76ea
JF
3640 return TRUE;
3641}
3642
3643static void
ca1d71ea
JF
3644status_update(struct view *view)
3645{
dec7b437 3646 struct line *line = &view->line[view->lineno];
351917f8 3647
dec7b437 3648 assert(view->lines);
11359638 3649
dec7b437
JF
3650 if (!line->data) {
3651 while (++line < view->line + view->lines && line->data) {
3652 if (!status_update_file(view, line->data, line->type))
3653 report("Failed to update file status");
3654 }
11359638 3655
dec7b437
JF
3656 if (!line[-1].data) {
3657 report("Nothing to update");
3658 return;
93e4c4f6
JF
3659 }
3660
dec7b437
JF
3661 } else if (!status_update_file(view, line->data, line->type)) {
3662 report("Failed to update file status");
ca1d71ea
JF
3663 }
3664}
3665
88f66e2d
JF
3666static enum request
3667status_request(struct view *view, enum request request, struct line *line)
3668{
0cea0d43
JF
3669 struct status *status = line->data;
3670
88f66e2d
JF
3671 switch (request) {
3672 case REQ_STATUS_UPDATE:
3673 status_update(view);
3674 break;
3675
b5c18d9d 3676 case REQ_STATUS_MERGE:
91521b9d
JF
3677 if (!status || status->status != 'U') {
3678 report("Merging only possible for files with unmerged status ('U').");
3679 return REQ_NONE;
3680 }
5ba030cf 3681 open_mergetool(status->new.name);
b5c18d9d
JF
3682 break;
3683
0cea0d43
JF
3684 case REQ_EDIT:
3685 if (!status)
3686 return request;
3687
5ba030cf 3688 open_editor(status->status != '?', status->new.name);
0cea0d43
JF
3689 break;
3690
88f66e2d 3691 case REQ_ENTER:
17a27c16
JF
3692 /* After returning the status view has been split to
3693 * show the stage view. No further reloading is
3694 * necessary. */
88f66e2d 3695 status_enter(view, line);
17a27c16 3696 return REQ_NONE;
88f66e2d 3697
acaef3b3 3698 case REQ_REFRESH:
17a27c16 3699 /* Simply reload the view. */
acaef3b3
JF
3700 break;
3701
88f66e2d
JF
3702 default:
3703 return request;
3704 }
3705
17a27c16
JF
3706 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3707
88f66e2d
JF
3708 return REQ_NONE;
3709}
3710
ca1d71ea 3711static void
173d76ea
JF
3712status_select(struct view *view, struct line *line)
3713{
11359638
JF
3714 struct status *status = line->data;
3715 char file[SIZEOF_STR] = "all files";
173d76ea 3716 char *text;
b5c18d9d 3717 char *key;
173d76ea 3718
5ba030cf 3719 if (status && !string_format(file, "'%s'", status->new.name))
11359638
JF
3720 return;
3721
3722 if (!status && line[1].type == LINE_STAT_NONE)
3723 line++;
3724
173d76ea
JF
3725 switch (line->type) {
3726 case LINE_STAT_STAGED:
11359638 3727 text = "Press %s to unstage %s for commit";
173d76ea
JF
3728 break;
3729
3730 case LINE_STAT_UNSTAGED:
11359638 3731 text = "Press %s to stage %s for commit";
173d76ea
JF
3732 break;
3733
3734 case LINE_STAT_UNTRACKED:
11359638 3735 text = "Press %s to stage %s for addition";
173d76ea
JF
3736 break;
3737
3738 case LINE_STAT_NONE:
11359638
JF
3739 text = "Nothing to update";
3740 break;
173d76ea
JF
3741
3742 default:
e77aa656 3743 die("line type %d not handled in switch", line->type);
173d76ea
JF
3744 }
3745
b5c18d9d
JF
3746 if (status && status->status == 'U') {
3747 text = "Press %s to resolve conflict in %s";
3748 key = get_key(REQ_STATUS_MERGE);
3749
3750 } else {
3751 key = get_key(REQ_STATUS_UPDATE);
3752 }
3753
3754 string_format(view->ref, text, key, file);
173d76ea
JF
3755}
3756
3757static bool
3758status_grep(struct view *view, struct line *line)
3759{
3760 struct status *status = line->data;
3761 enum { S_STATUS, S_NAME, S_END } state;
3762 char buf[2] = "?";
3763 regmatch_t pmatch;
3764
3765 if (!status)
3766 return FALSE;
3767
3768 for (state = S_STATUS; state < S_END; state++) {
3769 char *text;
3770
3771 switch (state) {
5ba030cf 3772 case S_NAME: text = status->new.name; break;
173d76ea
JF
3773 case S_STATUS:
3774 buf[0] = status->status;
3775 text = buf;
3776 break;
3777
3778 default:
3779 return FALSE;
3780 }
3781
3782 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3783 return TRUE;
3784 }
3785
3786 return FALSE;
3787}
3788
3789static struct view_ops status_ops = {
3790 "file",
3791 status_open,
3792 NULL,
3793 status_draw,
586c423d 3794 status_request,
173d76ea
JF
3795 status_grep,
3796 status_select,
3797};
3798
b33611d8
JF
3799
3800static bool
3801stage_diff_line(FILE *pipe, struct line *line)
3802{
3803 char *buf = line->data;
3804 size_t bufsize = strlen(buf);
3805 size_t written = 0;
3806
3807 while (!ferror(pipe) && written < bufsize) {
3808 written += fwrite(buf + written, 1, bufsize - written, pipe);
3809 }
3810
3811 fputc('\n', pipe);
3812
3813 return written == bufsize;
3814}
3815
3816static struct line *
3817stage_diff_hdr(struct view *view, struct line *line)
3818{
3819 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3820 struct line *diff_hdr;
3821
3822 if (line->type == LINE_DIFF_CHUNK)
3823 diff_hdr = line - 1;
3824 else
3825 diff_hdr = view->line + 1;
3826
3827 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3828 if (diff_hdr->type == LINE_DIFF_HEADER)
3829 return diff_hdr;
3830
3831 diff_hdr += diff_hdr_dir;
3832 }
3833
3834 return NULL;
3835}
3836
3837static bool
3838stage_update_chunk(struct view *view, struct line *line)
3839{
3840 char cmd[SIZEOF_STR];
3841 size_t cmdsize = 0;
3842 struct line *diff_hdr, *diff_chunk, *diff_end;
3843 FILE *pipe;
3844
3845 diff_hdr = stage_diff_hdr(view, line);
3846 if (!diff_hdr)
3847 return FALSE;
3848
3849 if (opt_cdup[0] &&
3850 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3851 return FALSE;
3852
3853 if (!string_format_from(cmd, &cmdsize,
3854 "git apply --cached %s - && "
3855 "git update-index -q --unmerged --refresh 2>/dev/null",
3856 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3857 return FALSE;
3858
3859 pipe = popen(cmd, "w");
3860 if (!pipe)
3861 return FALSE;
3862
3863 diff_end = view->line + view->lines;
3864 if (line->type != LINE_DIFF_CHUNK) {
3865 diff_chunk = diff_hdr;
3866
3867 } else {
3868 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3869 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3870 diff_chunk->type == LINE_DIFF_HEADER)
3871 diff_end = diff_chunk;
3872
3873 diff_chunk = line;
3874
3875 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3876 switch (diff_hdr->type) {
3877 case LINE_DIFF_HEADER:
3878 case LINE_DIFF_INDEX:
3879 case LINE_DIFF_ADD:
3880 case LINE_DIFF_DEL:
3881 break;
3882
3883 default:
3884 diff_hdr++;
3885 continue;
3886 }
3887
3888 if (!stage_diff_line(pipe, diff_hdr++)) {
3889 pclose(pipe);
3890 return FALSE;
3891 }
3892 }
3893 }
3894
3895 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3896 diff_chunk++;
3897
3898 pclose(pipe);
3899
3900 if (diff_chunk != diff_end)
3901 return FALSE;
3902
3903 return TRUE;
3904}
3905
3906static void
3907stage_update(struct view *view, struct line *line)
3908{
3909 if (stage_line_type != LINE_STAT_UNTRACKED &&
3910 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3911 if (!stage_update_chunk(view, line)) {
3912 report("Failed to apply chunk");
3913 return;
3914 }
3915
3916 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3917 report("Failed to update file");
3918 return;
3919 }
3920
3921 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3922
3923 view = VIEW(REQ_VIEW_STATUS);
3924 if (view_is_displayed(view))
3925 status_enter(view, &view->line[view->lineno]);
3926}
3927
3928static enum request
3929stage_request(struct view *view, enum request request, struct line *line)
3930{
3931 switch (request) {
3932 case REQ_STATUS_UPDATE:
3933 stage_update(view, line);
3934 break;
3935
3936 case REQ_EDIT:
5ba030cf 3937 if (!stage_status.new.name[0])
b33611d8
JF
3938 return request;
3939
5ba030cf 3940 open_editor(stage_status.status != '?', stage_status.new.name);
b33611d8
JF
3941 break;
3942
3943 case REQ_ENTER:
3944 pager_request(view, request, line);
3945 break;
3946
3947 default:
3948 return request;
3949 }
3950
3951 return REQ_NONE;
3952}
3953
3e634113
JF
3954static struct view_ops stage_ops = {
3955 "line",
3956 NULL,
3957 pager_read,
3958 pager_draw,
b33611d8 3959 stage_request,
3e634113
JF
3960 pager_grep,
3961 pager_select,
3962};
173d76ea 3963
b33611d8 3964
173d76ea 3965/*
ccc33449 3966 * Revision graph
ff26aa29
JF
3967 */
3968
3969struct commit {
10446330 3970 char id[SIZEOF_REV]; /* SHA1 ID. */
aea510c8 3971 char title[128]; /* First line of the commit message. */
54efb62b
JF
3972 char author[75]; /* Author of the commit. */
3973 struct tm time; /* Date from the author ident. */
3974 struct ref **refs; /* Repository references. */
3975 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3976 size_t graph_size; /* The width of the graph array. */
ff26aa29 3977};
c34d9c9f 3978
ccc33449
JF
3979/* Size of rev graph with no "padding" columns */
3980#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
2b757533 3981
2ce5c87c
JF
3982struct rev_graph {
3983 struct rev_graph *prev, *next, *parents;
2b757533
JF
3984 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3985 size_t size;
88757ebd
JF
3986 struct commit *commit;
3987 size_t pos;
e81e9c2c 3988 unsigned int boundary:1;
2b757533
JF
3989};
3990
2b757533 3991/* Parents of the commit being visualized. */
446a5c36 3992static struct rev_graph graph_parents[4];
c8d60a25 3993
c65a501a 3994/* The current stack of revisions on the graph. */
446a5c36
JF
3995static struct rev_graph graph_stacks[4] = {
3996 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
c65a501a 3997 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
446a5c36
JF
3998 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3999 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
c65a501a
JF
4000};
4001
9e43b9cd 4002static inline bool
2ce5c87c 4003graph_parent_is_merge(struct rev_graph *graph)
9e43b9cd
JF
4004{
4005 return graph->parents->size > 1;
4006}
4007
88757ebd 4008static inline void
2ce5c87c 4009append_to_rev_graph(struct rev_graph *graph, chtype symbol)
88757ebd 4010{
2c27faac
JF
4011 struct commit *commit = graph->commit;
4012
4013 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4014 commit->graph[commit->graph_size++] = symbol;
88757ebd
JF
4015}
4016
2b757533 4017static void
2ce5c87c 4018done_rev_graph(struct rev_graph *graph)
987890af
JF
4019{
4020 if (graph_parent_is_merge(graph) &&
4021 graph->pos < graph->size - 1 &&
4022 graph->next->size == graph->size + graph->parents->size - 1) {
4023 size_t i = graph->pos + graph->parents->size - 1;
4024
4025 graph->commit->graph_size = i * 2;
4026 while (i < graph->next->size - 1) {
4027 append_to_rev_graph(graph, ' ');
4028 append_to_rev_graph(graph, '\\');
4029 i++;
4030 }
4031 }
4032
4033 graph->size = graph->pos = 0;
4034 graph->commit = NULL;
4035 memset(graph->parents, 0, sizeof(*graph->parents));
4036}
4037
4038static void
2ce5c87c 4039push_rev_graph(struct rev_graph *graph, char *parent)
2b757533 4040{
2fe894e6
JF
4041 int i;
4042
4043 /* "Collapse" duplicate parents lines.
4044 *
4045 * FIXME: This needs to also update update the drawn graph but
4046 * for now it just serves as a method for pruning graph lines. */
4047 for (i = 0; i < graph->size; i++)
4048 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4049 return;
2b757533 4050
2ce5c87c 4051 if (graph->size < SIZEOF_REVITEMS) {
739e81de 4052 string_copy_rev(graph->rev[graph->size++], parent);
2b757533
JF
4053 }
4054}
4055
92507a24
JF
4056static chtype
4057get_rev_graph_symbol(struct rev_graph *graph)
2b757533 4058{
92507a24 4059 chtype symbol;
2b757533 4060
e81e9c2c
JF
4061 if (graph->boundary)
4062 symbol = REVGRAPH_BOUND;
4063 else if (graph->parents->size == 0)
c8d60a25 4064 symbol = REVGRAPH_INIT;
18ffaa23 4065 else if (graph_parent_is_merge(graph))
c8d60a25 4066 symbol = REVGRAPH_MERGE;
c65a501a 4067 else if (graph->pos >= graph->size)
c8d60a25 4068 symbol = REVGRAPH_BRANCH;
2b757533 4069 else
c8d60a25 4070 symbol = REVGRAPH_COMMIT;
1dcb3bec 4071
92507a24
JF
4072 return symbol;
4073}
4074
4075static void
4076draw_rev_graph(struct rev_graph *graph)
4077{
e937c2c8
JF
4078 struct rev_filler {
4079 chtype separator, line;
4080 };
4081 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4082 static struct rev_filler fillers[] = {
4083 { ' ', REVGRAPH_LINE },
4084 { '`', '.' },
4085 { '\'', ' ' },
4086 { '/', ' ' },
e937c2c8 4087 };
92507a24 4088 chtype symbol = get_rev_graph_symbol(graph);
e937c2c8 4089 struct rev_filler *filler;
92507a24
JF
4090 size_t i;
4091
e937c2c8 4092 filler = &fillers[DEFAULT];
110e948e 4093
c65a501a 4094 for (i = 0; i < graph->pos; i++) {
e937c2c8 4095 append_to_rev_graph(graph, filler->line);
9e43b9cd 4096 if (graph_parent_is_merge(graph->prev) &&
e937c2c8
JF
4097 graph->prev->pos == i)
4098 filler = &fillers[RSHARP];
4099
4100 append_to_rev_graph(graph, filler->separator);
110e948e
JF
4101 }
4102
92507a24 4103 /* Place the symbol for this revision. */
c65a501a 4104 append_to_rev_graph(graph, symbol);
2b757533 4105
e937c2c8
JF
4106 if (graph->prev->size > graph->size)
4107 filler = &fillers[RDIAG];
4108 else
4109 filler = &fillers[DEFAULT];
4110
c8d60a25 4111 i++;
2b757533 4112
c65a501a 4113 for (; i < graph->size; i++) {
e937c2c8
JF
4114 append_to_rev_graph(graph, filler->separator);
4115 append_to_rev_graph(graph, filler->line);
4116 if (graph_parent_is_merge(graph->prev) &&
4117 i < graph->prev->pos + graph->parents->size)
4118 filler = &fillers[RSHARP];
4119 if (graph->prev->size > graph->size)
4120 filler = &fillers[LDIAG];
c65a501a
JF
4121 }
4122
4123 if (graph->prev->size > graph->size) {
e937c2c8
JF
4124 append_to_rev_graph(graph, filler->separator);
4125 if (filler->line != ' ')
4126 append_to_rev_graph(graph, filler->line);
2b757533 4127 }
b5d8f208
JF
4128}
4129
61eed810
JF
4130/* Prepare the next rev graph */
4131static void
4132prepare_rev_graph(struct rev_graph *graph)
b5d8f208 4133{
b5d8f208
JF
4134 size_t i;
4135
320df4ea 4136 /* First, traverse all lines of revisions up to the active one. */
c65a501a
JF
4137 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4138 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
b5d8f208 4139 break;
b5d8f208 4140
2ce5c87c 4141 push_rev_graph(graph->next, graph->rev[graph->pos]);
b5d8f208
JF
4142 }
4143
320df4ea 4144 /* Interleave the new revision parent(s). */
e81e9c2c 4145 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
2ce5c87c 4146 push_rev_graph(graph->next, graph->parents->rev[i]);
b5d8f208 4147
320df4ea 4148 /* Lastly, put any remaining revisions. */
c65a501a 4149 for (i = graph->pos + 1; i < graph->size; i++)
2ce5c87c 4150 push_rev_graph(graph->next, graph->rev[i]);
61eed810
JF
4151}
4152
4153static void
4154update_rev_graph(struct rev_graph *graph)
4155{
446a5c36
JF
4156 /* If this is the finalizing update ... */
4157 if (graph->commit)
4158 prepare_rev_graph(graph);
4159
4160 /* Graph visualization needs a one rev look-ahead,
4161 * so the first update doesn't visualize anything. */
4162 if (!graph->prev->commit)
4163 return;
c65a501a 4164
61eed810
JF
4165 draw_rev_graph(graph->prev);
4166 done_rev_graph(graph->prev->prev);
2b757533
JF
4167}
4168
ccc33449
JF
4169
4170/*
4171 * Main view backend
4172 */
4173
4174static bool
4175main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4176{
4177 char buf[DATE_COLS + 1];
4178 struct commit *commit = line->data;
4179 enum line_type type;
4180 int col = 0;
4181 size_t timelen;
1c919d68
DV
4182 int tilde_attr;
4183 int space;
ccc33449
JF
4184
4185 if (!*commit->author)
4186 return FALSE;
4187
1c919d68 4188 space = view->width;
ccc33449
JF
4189 wmove(view->win, lineno, col);
4190
4191 if (selected) {
4192 type = LINE_CURSOR;
4193 wattrset(view->win, get_line_attr(type));
4194 wchgat(view->win, -1, 0, type, NULL);
1c919d68 4195 tilde_attr = -1;
ccc33449
JF
4196 } else {
4197 type = LINE_MAIN_COMMIT;
4198 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
1c919d68 4199 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
ccc33449
JF
4200 }
4201
823057f4 4202 if (opt_date) {
1c919d68 4203 int n;
ccc33449 4204
1c919d68
DV
4205 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4206 n = draw_text(
4207 view, buf, view->width - col, col, FALSE, tilde_attr);
4208 draw_text(
4209 view, " ", view->width - col - n, col + n, FALSE,
4210 tilde_attr);
ccc33449 4211
1c919d68
DV
4212 col += DATE_COLS;
4213 wmove(view->win, lineno, col);
4214 if (col >= view->width)
4215 return TRUE;
ccc33449 4216 }
1c919d68
DV
4217 if (type != LINE_CURSOR)
4218 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
ccc33449 4219
823057f4 4220 if (opt_author) {
1c919d68
DV
4221 int max_len;
4222
4223 max_len = view->width - col;
4224 if (max_len > AUTHOR_COLS - 1)
4225 max_len = AUTHOR_COLS - 1;
4226 draw_text(
4227 view, commit->author, max_len, col, TRUE, tilde_attr);
4228 col += AUTHOR_COLS;
4229 if (col >= view->width)
4230 return TRUE;
ccc33449
JF
4231 }
4232
ccc33449 4233 if (opt_rev_graph && commit->graph_size) {
749cdc92 4234 size_t graph_size = view->width - col;
ccc33449
JF
4235 size_t i;
4236
f83b1c33
DV
4237 if (type != LINE_CURSOR)
4238 wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
ccc33449 4239 wmove(view->win, lineno, col);
749cdc92
JF
4240 if (graph_size > commit->graph_size)
4241 graph_size = commit->graph_size;
ccc33449
JF
4242 /* Using waddch() instead of waddnstr() ensures that
4243 * they'll be rendered correctly for the cursor line. */
749cdc92 4244 for (i = 0; i < graph_size; i++)
ccc33449
JF
4245 waddch(view->win, commit->graph[i]);
4246
4247 col += commit->graph_size + 1;
1c919d68
DV
4248 if (col >= view->width)
4249 return TRUE;
749cdc92 4250 waddch(view->win, ' ');
ccc33449 4251 }
f83b1c33
DV
4252 if (type != LINE_CURSOR)
4253 wattrset(view->win, A_NORMAL);
ccc33449
JF
4254
4255 wmove(view->win, lineno, col);
4256
823057f4 4257 if (opt_show_refs && commit->refs) {
ccc33449
JF
4258 size_t i = 0;
4259
4260 do {
4261 if (type == LINE_CURSOR)
4262 ;
4263 else if (commit->refs[i]->tag)
4264 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
e15ec88e
JF
4265 else if (commit->refs[i]->remote)
4266 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
ccc33449
JF
4267 else
4268 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1c919d68
DV
4269
4270 col += draw_text(
4271 view, "[", view->width - col, col, TRUE,
4272 tilde_attr);
4273 col += draw_text(
4274 view, commit->refs[i]->name, view->width - col,
4275 col, TRUE, tilde_attr);
4276 col += draw_text(
4277 view, "]", view->width - col, col, TRUE,
4278 tilde_attr);
ccc33449
JF
4279 if (type != LINE_CURSOR)
4280 wattrset(view->win, A_NORMAL);
1c919d68
DV
4281 col += draw_text(
4282 view, " ", view->width - col, col, TRUE,
4283 tilde_attr);
4284 if (col >= view->width)
4285 return TRUE;
ccc33449
JF
4286 } while (commit->refs[i++]->next);
4287 }
4288
4289 if (type != LINE_CURSOR)
4290 wattrset(view->win, get_line_attr(type));
4291
1c919d68
DV
4292 col += draw_text(
4293 view, commit->title, view->width - col, col, TRUE, tilde_attr);
ccc33449
JF
4294
4295 return TRUE;
4296}
4297
4c6fabc2 4298/* Reads git log --pretty=raw output and parses it into the commit struct. */
6b161b31 4299static bool
701e4f5d 4300main_read(struct view *view, char *line)
22f66b0a 4301{
2ce5c87c 4302 static struct rev_graph *graph = graph_stacks;
be04d936 4303 enum line_type type;
0ff3b97c 4304 struct commit *commit;
22f66b0a 4305
be04d936 4306 if (!line) {
446a5c36 4307 update_rev_graph(graph);
be04d936
JF
4308 return TRUE;
4309 }
4310
4311 type = get_line_type(line);
0ff3b97c 4312 if (type == LINE_COMMIT) {
22f66b0a
JF
4313 commit = calloc(1, sizeof(struct commit));
4314 if (!commit)
4315 return FALSE;
4316
e81e9c2c
JF
4317 line += STRING_SIZE("commit ");
4318 if (*line == '-') {
4319 graph->boundary = 1;
4320 line++;
4321 }
4322
4323 string_copy_rev(commit->id, line);
c34d9c9f 4324 commit->refs = get_refs(commit->id);
c65a501a 4325 graph->commit = commit;
e314c36d 4326 add_line_data(view, commit, LINE_MAIN_COMMIT);
0ff3b97c
JF
4327 return TRUE;
4328 }
2b757533 4329
0ff3b97c
JF
4330 if (!view->lines)
4331 return TRUE;
4332 commit = view->line[view->lines - 1].data;
4333
4334 switch (type) {
2b757533 4335 case LINE_PARENT:
0ff3b97c 4336 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
78c70acd 4337 break;
22f66b0a 4338
8855ada4 4339 case LINE_AUTHOR:
b76c2afc 4340 {
19c3ac60
JF
4341 /* Parse author lines where the name may be empty:
4342 * author <email@address.tld> 1138474660 +0100
4343 */
4c6fabc2 4344 char *ident = line + STRING_SIZE("author ");
19c3ac60
JF
4345 char *nameend = strchr(ident, '<');
4346 char *emailend = strchr(ident, '>');
b76c2afc 4347
0ff3b97c 4348 if (!nameend || !emailend)
fe7233c3
JF
4349 break;
4350
c65a501a
JF
4351 update_rev_graph(graph);
4352 graph = graph->next;
2b757533 4353
19c3ac60
JF
4354 *nameend = *emailend = 0;
4355 ident = chomp_string(ident);
4356 if (!*ident) {
4357 ident = chomp_string(nameend + 1);
4358 if (!*ident)
4359 ident = "Unknown";
b76c2afc
JF
4360 }
4361
739e81de 4362 string_ncopy(commit->author, ident, strlen(ident));
b76c2afc 4363
4c6fabc2 4364 /* Parse epoch and timezone */
19c3ac60
JF
4365 if (emailend[1] == ' ') {
4366 char *secs = emailend + 2;
4367 char *zone = strchr(secs, ' ');
4368 time_t time = (time_t) atol(secs);
b76c2afc 4369
4c6fabc2 4370 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
b76c2afc
JF
4371 long tz;
4372
4373 zone++;
4374 tz = ('0' - zone[1]) * 60 * 60 * 10;
4375 tz += ('0' - zone[2]) * 60 * 60;
4376 tz += ('0' - zone[3]) * 60;
4377 tz += ('0' - zone[4]) * 60;
4378
4379 if (zone[0] == '-')
4380 tz = -tz;
4381
4382 time -= tz;
4383 }
19c3ac60 4384
b76c2afc
JF
4385 gmtime_r(&time, &commit->time);
4386 }
4387 break;
4388 }
78c70acd 4389 default:
2e8488b4 4390 /* Fill in the commit title if it has not already been set. */
2e8488b4
JF
4391 if (commit->title[0])
4392 break;
4393
4394 /* Require titles to start with a non-space character at the
4395 * offset used by git log. */
9073c64a
JF
4396 if (strncmp(line, " ", 4))
4397 break;
4398 line += 4;
4399 /* Well, if the title starts with a whitespace character,
4400 * try to be forgiving. Otherwise we end up with no title. */
4401 while (isspace(*line))
4402 line++;
4403 if (*line == '\0')
82e78006 4404 break;
9073c64a
JF
4405 /* FIXME: More graceful handling of titles; append "..." to
4406 * shortened titles, etc. */
82e78006 4407
739e81de 4408 string_ncopy(commit->title, line, strlen(line));
22f66b0a
JF
4409 }
4410
4411 return TRUE;
4412}
4413
586c423d
JF
4414static enum request
4415main_request(struct view *view, enum request request, struct line *line)
b801d8b2 4416{
b3a54cba
JF
4417 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4418
586c423d
JF
4419 if (request == REQ_ENTER)
4420 open_view(view, REQ_VIEW_DIFF, flags);
4421 else
4422 return request;
4423
4424 return REQ_NONE;
b801d8b2
JF
4425}
4426
4af34daa
JF
4427static bool
4428main_grep(struct view *view, struct line *line)
4429{
4430 struct commit *commit = line->data;
4431 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4432 char buf[DATE_COLS + 1];
4433 regmatch_t pmatch;
4434
4435 for (state = S_TITLE; state < S_END; state++) {
4436 char *text;
4437
4438 switch (state) {
4439 case S_TITLE: text = commit->title; break;
4440 case S_AUTHOR: text = commit->author; break;
4441 case S_DATE:
4442 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4443 continue;
4444 text = buf;
4445 break;
4446
4447 default:
4448 return FALSE;
4449 }
4450
b77b2cb8 4451 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4af34daa
JF
4452 return TRUE;
4453 }
4454
4455 return FALSE;
4456}
4457
d720de4b
JF
4458static void
4459main_select(struct view *view, struct line *line)
4460{
4461 struct commit *commit = line->data;
4462
2463b4ea
JF
4463 string_copy_rev(view->ref, commit->id);
4464 string_copy_rev(ref_commit, view->ref);
d720de4b
JF
4465}
4466
6b161b31 4467static struct view_ops main_ops = {
6734f6b9 4468 "commit",
f098944b 4469 NULL,
6b161b31 4470 main_read,
f098944b 4471 main_draw,
586c423d 4472 main_request,
4af34daa 4473 main_grep,
d720de4b 4474 main_select,
6b161b31 4475};
2e8488b4 4476
c34d9c9f 4477
6b161b31 4478/*
10e290ee
JF
4479 * Unicode / UTF-8 handling
4480 *
4481 * NOTE: Much of the following code for dealing with unicode is derived from
4482 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4483 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4484 */
4485
4486/* I've (over)annotated a lot of code snippets because I am not entirely
4487 * confident that the approach taken by this small UTF-8 interface is correct.
4488 * --jonas */
4489
4490static inline int
4491unicode_width(unsigned long c)
4492{
4493 if (c >= 0x1100 &&
4494 (c <= 0x115f /* Hangul Jamo */
4495 || c == 0x2329
4496 || c == 0x232a
4497 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
f97f4012 4498 /* CJK ... Yi */
10e290ee
JF
4499 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4500 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4501 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4502 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4503 || (c >= 0xffe0 && c <= 0xffe6)
4504 || (c >= 0x20000 && c <= 0x2fffd)
4505 || (c >= 0x30000 && c <= 0x3fffd)))
4506 return 2;
4507
749cdc92
JF
4508 if (c == '\t')
4509 return opt_tab_size;
4510
10e290ee
JF
4511 return 1;
4512}
4513
4514/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4515 * Illegal bytes are set one. */
4516static const unsigned char utf8_bytes[256] = {
4517 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,
4518 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,
4519 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,
4520 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,
4521 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,
4522 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,
4523 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,
4524 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,
4525};
4526
4527/* Decode UTF-8 multi-byte representation into a unicode character. */
4528static inline unsigned long
4529utf8_to_unicode(const char *string, size_t length)
4530{
4531 unsigned long unicode;
4532
4533 switch (length) {
4534 case 1:
4535 unicode = string[0];
4536 break;
4537 case 2:
4538 unicode = (string[0] & 0x1f) << 6;
4539 unicode += (string[1] & 0x3f);
4540 break;
4541 case 3:
4542 unicode = (string[0] & 0x0f) << 12;
4543 unicode += ((string[1] & 0x3f) << 6);
4544 unicode += (string[2] & 0x3f);
4545 break;
4546 case 4:
4547 unicode = (string[0] & 0x0f) << 18;
4548 unicode += ((string[1] & 0x3f) << 12);
4549 unicode += ((string[2] & 0x3f) << 6);
4550 unicode += (string[3] & 0x3f);
4551 break;
4552 case 5:
4553 unicode = (string[0] & 0x0f) << 24;
4554 unicode += ((string[1] & 0x3f) << 18);
4555 unicode += ((string[2] & 0x3f) << 12);
4556 unicode += ((string[3] & 0x3f) << 6);
4557 unicode += (string[4] & 0x3f);
4558 break;
68b6e0eb 4559 case 6:
10e290ee
JF
4560 unicode = (string[0] & 0x01) << 30;
4561 unicode += ((string[1] & 0x3f) << 24);
4562 unicode += ((string[2] & 0x3f) << 18);
4563 unicode += ((string[3] & 0x3f) << 12);
4564 unicode += ((string[4] & 0x3f) << 6);
4565 unicode += (string[5] & 0x3f);
4566 break;
4567 default:
4568 die("Invalid unicode length");
4569 }
4570
4571 /* Invalid characters could return the special 0xfffd value but NUL
4572 * should be just as good. */
4573 return unicode > 0xffff ? 0 : unicode;
4574}
4575
4576/* Calculates how much of string can be shown within the given maximum width
4577 * and sets trimmed parameter to non-zero value if all of string could not be
012e76e9
JF
4578 * shown. If the reserve flag is TRUE, it will reserve at least one
4579 * trailing character, which can be useful when drawing a delimiter.
10e290ee
JF
4580 *
4581 * Returns the number of bytes to output from string to satisfy max_width. */
4582static size_t
012e76e9 4583utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
10e290ee
JF
4584{
4585 const char *start = string;
4586 const char *end = strchr(string, '\0');
012e76e9 4587 unsigned char last_bytes = 0;
10e290ee
JF
4588 size_t width = 0;
4589
4590 *trimmed = 0;
4591
4592 while (string < end) {
4593 int c = *(unsigned char *) string;
4594 unsigned char bytes = utf8_bytes[c];
4595 size_t ucwidth;
4596 unsigned long unicode;
4597
4598 if (string + bytes > end)
4599 break;
4600
4601 /* Change representation to figure out whether
4602 * it is a single- or double-width character. */
4603
4604 unicode = utf8_to_unicode(string, bytes);
4605 /* FIXME: Graceful handling of invalid unicode character. */
4606 if (!unicode)
4607 break;
4608
4609 ucwidth = unicode_width(unicode);
4610 width += ucwidth;
4611 if (width > max_width) {
4612 *trimmed = 1;
012e76e9
JF
4613 if (reserve && width - ucwidth == max_width) {
4614 string -= last_bytes;
4615 }
10e290ee
JF
4616 break;
4617 }
4618
10e290ee 4619 string += bytes;
012e76e9 4620 last_bytes = bytes;
10e290ee
JF
4621 }
4622
10e290ee
JF
4623 return string - start;
4624}
4625
4626
4627/*
6b161b31
JF
4628 * Status management
4629 */
2e8488b4 4630
8855ada4 4631/* Whether or not the curses interface has been initialized. */
68b6e0eb 4632static bool cursed = FALSE;
8855ada4 4633
6b161b31
JF
4634/* The status window is used for polling keystrokes. */
4635static WINDOW *status_win;
4a2909a7 4636
21be28fb
JF
4637static bool status_empty = TRUE;
4638
2e8488b4 4639/* Update status and title window. */
4a2909a7
JF
4640static void
4641report(const char *msg, ...)
4642{
6706b2ba 4643 struct view *view = display[current_view];
b76c2afc 4644
ab4af23e
JF
4645 if (input_mode)
4646 return;
4647
c38c64bb
JF
4648 if (!view) {
4649 char buf[SIZEOF_STR];
4650 va_list args;
4651
4652 va_start(args, msg);
4653 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4654 buf[sizeof(buf) - 1] = 0;
4655 buf[sizeof(buf) - 2] = '.';
4656 buf[sizeof(buf) - 3] = '.';
4657 buf[sizeof(buf) - 4] = '.';
4658 }
4659 va_end(args);
4660 die("%s", buf);
4661 }
4662
21be28fb 4663 if (!status_empty || *msg) {
6706b2ba 4664 va_list args;
4a2909a7 4665
6706b2ba 4666 va_start(args, msg);
4b76734f 4667
6706b2ba
JF
4668 wmove(status_win, 0, 0);
4669 if (*msg) {
4670 vwprintw(status_win, msg, args);
21be28fb 4671 status_empty = FALSE;
6706b2ba 4672 } else {
21be28fb 4673 status_empty = TRUE;
6706b2ba 4674 }
390a8262 4675 wclrtoeol(status_win);
6706b2ba 4676 wrefresh(status_win);
b801d8b2 4677
6706b2ba
JF
4678 va_end(args);
4679 }
4680
4681 update_view_title(view);
2bee3bde 4682 update_display_cursor(view);
b801d8b2
JF
4683}
4684
6b161b31
JF
4685/* Controls when nodelay should be in effect when polling user input. */
4686static void
1ba2ae4b 4687set_nonblocking_input(bool loading)
b801d8b2 4688{
6706b2ba 4689 static unsigned int loading_views;
b801d8b2 4690
6706b2ba
JF
4691 if ((loading == FALSE && loading_views-- == 1) ||
4692 (loading == TRUE && loading_views++ == 0))
1ba2ae4b 4693 nodelay(status_win, loading);
6b161b31
JF
4694}
4695
4696static void
4697init_display(void)
4698{
4699 int x, y;
b76c2afc 4700
6908bdbd
JF
4701 /* Initialize the curses library */
4702 if (isatty(STDIN_FILENO)) {
8855ada4 4703 cursed = !!initscr();
6908bdbd
JF
4704 } else {
4705 /* Leave stdin and stdout alone when acting as a pager. */
4706 FILE *io = fopen("/dev/tty", "r+");
4707
e6f60674
JF
4708 if (!io)
4709 die("Failed to open /dev/tty");
8855ada4 4710 cursed = !!newterm(NULL, io, io);
6908bdbd
JF
4711 }
4712
8855ada4
JF
4713 if (!cursed)
4714 die("Failed to initialize curses");
4715
2e8488b4
JF
4716 nonl(); /* Tell curses not to do NL->CR/NL on output */
4717 cbreak(); /* Take input chars one at a time, no wait for \n */
4718 noecho(); /* Don't echo input */
b801d8b2 4719 leaveok(stdscr, TRUE);
b801d8b2
JF
4720
4721 if (has_colors())
4722 init_colors();
4723
4724 getmaxyx(stdscr, y, x);
4725 status_win = newwin(1, 0, y - 1, 0);
4726 if (!status_win)
4727 die("Failed to create status window");
4728
4729 /* Enable keyboard mapping */
4730 keypad(status_win, TRUE);
78c70acd 4731 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6b161b31
JF
4732}
4733
4af34daa 4734static char *
cb9e48c1 4735read_prompt(const char *prompt)
ef5404a4
JF
4736{
4737 enum { READING, STOP, CANCEL } status = READING;
9e21ce5c 4738 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
ef5404a4
JF
4739 int pos = 0;
4740
4741 while (status == READING) {
4742 struct view *view;
4743 int i, key;
4744
ab4af23e
JF
4745 input_mode = TRUE;
4746
699ae55b 4747 foreach_view (view, i)
ef5404a4
JF
4748 update_view(view);
4749
ab4af23e
JF
4750 input_mode = FALSE;
4751
4752 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4753 wclrtoeol(status_win);
4754
ef5404a4
JF
4755 /* Refresh, accept single keystroke of input */
4756 key = wgetch(status_win);
4757 switch (key) {
4758 case KEY_RETURN:
4759 case KEY_ENTER:
4760 case '\n':
4761 status = pos ? STOP : CANCEL;
4762 break;
4763
4764 case KEY_BACKSPACE:
4765 if (pos > 0)
4766 pos--;
4767 else
4768 status = CANCEL;
4769 break;
4770
4771 case KEY_ESC:
4772 status = CANCEL;
4773 break;
4774
4775 case ERR:
4776 break;
4777
4778 default:
4779 if (pos >= sizeof(buf)) {
4780 report("Input string too long");
9e21ce5c 4781 return NULL;
ef5404a4
JF
4782 }
4783
4784 if (isprint(key))
4785 buf[pos++] = (char) key;
4786 }
4787 }
4788
7a06ebdf
JF
4789 /* Clear the status window */
4790 status_empty = FALSE;
4791 report("");
4792
4793 if (status == CANCEL)
9e21ce5c 4794 return NULL;
ef5404a4
JF
4795
4796 buf[pos++] = 0;
ef5404a4 4797
9e21ce5c 4798 return buf;
ef5404a4 4799}
c34d9c9f
JF
4800
4801/*
4802 * Repository references
4803 */
4804
4805static struct ref *refs;
3a91b75e 4806static size_t refs_size;
c34d9c9f 4807
1307df1a
JF
4808/* Id <-> ref store */
4809static struct ref ***id_refs;
4810static size_t id_refs_size;
4811
c34d9c9f
JF
4812static struct ref **
4813get_refs(char *id)
4814{
1307df1a
JF
4815 struct ref ***tmp_id_refs;
4816 struct ref **ref_list = NULL;
4817 size_t ref_list_size = 0;
c34d9c9f
JF
4818 size_t i;
4819
1307df1a
JF
4820 for (i = 0; i < id_refs_size; i++)
4821 if (!strcmp(id, id_refs[i][0]->id))
4822 return id_refs[i];
4823
4824 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4825 if (!tmp_id_refs)
4826 return NULL;
4827
4828 id_refs = tmp_id_refs;
4829
c34d9c9f
JF
4830 for (i = 0; i < refs_size; i++) {
4831 struct ref **tmp;
4832
4833 if (strcmp(id, refs[i].id))
4834 continue;
4835
1307df1a 4836 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
c34d9c9f 4837 if (!tmp) {
1307df1a
JF
4838 if (ref_list)
4839 free(ref_list);
c34d9c9f
JF
4840 return NULL;
4841 }
4842
1307df1a
JF
4843 ref_list = tmp;
4844 if (ref_list_size > 0)
4845 ref_list[ref_list_size - 1]->next = 1;
4846 ref_list[ref_list_size] = &refs[i];
3af8774e
JF
4847
4848 /* XXX: The properties of the commit chains ensures that we can
4849 * safely modify the shared ref. The repo references will
4850 * always be similar for the same id. */
1307df1a
JF
4851 ref_list[ref_list_size]->next = 0;
4852 ref_list_size++;
c34d9c9f
JF
4853 }
4854
1307df1a
JF
4855 if (ref_list)
4856 id_refs[id_refs_size++] = ref_list;
4857
4858 return ref_list;
c34d9c9f
JF
4859}
4860
4861static int
5699e0cf 4862read_ref(char *id, size_t idlen, char *name, size_t namelen)
c34d9c9f 4863{
d0cea5f9
JF
4864 struct ref *ref;
4865 bool tag = FALSE;
e15ec88e 4866 bool remote = FALSE;
d0cea5f9 4867
8b0297ae
JF
4868 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4869 /* Commits referenced by tags has "^{}" appended. */
4870 if (name[namelen - 1] != '}')
4871 return OK;
4872
d0cea5f9
JF
4873 while (namelen > 0 && name[namelen] != '^')
4874 namelen--;
c34d9c9f 4875
d0cea5f9 4876 tag = TRUE;
8b0297ae
JF
4877 namelen -= STRING_SIZE("refs/tags/");
4878 name += STRING_SIZE("refs/tags/");
c34d9c9f 4879
e15ec88e
JF
4880 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4881 remote = TRUE;
4882 namelen -= STRING_SIZE("refs/remotes/");
4883 name += STRING_SIZE("refs/remotes/");
4884
d0cea5f9 4885 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
8b0297ae
JF
4886 namelen -= STRING_SIZE("refs/heads/");
4887 name += STRING_SIZE("refs/heads/");
c34d9c9f 4888
d0cea5f9
JF
4889 } else if (!strcmp(name, "HEAD")) {
4890 return OK;
4891 }
6706b2ba 4892
d0cea5f9
JF
4893 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4894 if (!refs)
4895 return ERR;
c34d9c9f 4896
d0cea5f9 4897 ref = &refs[refs_size++];
8b0297ae 4898 ref->name = malloc(namelen + 1);
d0cea5f9
JF
4899 if (!ref->name)
4900 return ERR;
3af8774e 4901
8b0297ae
JF
4902 strncpy(ref->name, name, namelen);
4903 ref->name[namelen] = 0;
d0cea5f9 4904 ref->tag = tag;
e15ec88e 4905 ref->remote = remote;
2463b4ea 4906 string_copy_rev(ref->id, id);
3af8774e 4907
d0cea5f9
JF
4908 return OK;
4909}
c34d9c9f 4910
d0cea5f9
JF
4911static int
4912load_refs(void)
4913{
4914 const char *cmd_env = getenv("TIG_LS_REMOTE");
4915 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
c34d9c9f 4916
4a63c884 4917 return read_properties(popen(cmd, "r"), "\t", read_ref);
d0cea5f9 4918}
c34d9c9f 4919
d0cea5f9 4920static int
5699e0cf 4921read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
d0cea5f9 4922{
22913179 4923 if (!strcmp(name, "i18n.commitencoding"))
739e81de 4924 string_ncopy(opt_encoding, value, valuelen);
c34d9c9f 4925
0cea0d43
JF
4926 if (!strcmp(name, "core.editor"))
4927 string_ncopy(opt_editor, value, valuelen);
4928
c34d9c9f
JF
4929 return OK;
4930}
4931
4670cf89 4932static int
14c778a6 4933load_repo_config(void)
4670cf89 4934{
96e58f5b 4935 return read_properties(popen(GIT_CONFIG " --list", "r"),
14c778a6 4936 "=", read_repo_config_option);
d0cea5f9
JF
4937}
4938
4939static int
5699e0cf 4940read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
91c5d983 4941{
c38c64bb 4942 if (!opt_git_dir[0]) {
810f0078 4943 string_ncopy(opt_git_dir, name, namelen);
c38c64bb
JF
4944
4945 } else if (opt_is_inside_work_tree == -1) {
4946 /* This can be 3 different values depending on the
4947 * version of git being used. If git-rev-parse does not
4948 * understand --is-inside-work-tree it will simply echo
4949 * the option else either "true" or "false" is printed.
4950 * Default to true for the unknown case. */
4951 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4952
4953 } else {
739e81de 4954 string_ncopy(opt_cdup, name, namelen);
c38c64bb
JF
4955 }
4956
91c5d983
JF
4957 return OK;
4958}
4959
810f0078
JF
4960/* XXX: The line outputted by "--show-cdup" can be empty so the option
4961 * must be the last one! */
91c5d983
JF
4962static int
4963load_repo_info(void)
4964{
c38c64bb 4965 return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
91c5d983
JF
4966 "=", read_repo_info);
4967}
4968
4969static int
4a63c884 4970read_properties(FILE *pipe, const char *separators,
5699e0cf 4971 int (*read_property)(char *, size_t, char *, size_t))
d0cea5f9 4972{
4670cf89
JF
4973 char buffer[BUFSIZ];
4974 char *name;
d0cea5f9 4975 int state = OK;
4670cf89
JF
4976
4977 if (!pipe)
4978 return ERR;
4979
d0cea5f9 4980 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4a63c884
JF
4981 char *value;
4982 size_t namelen;
4983 size_t valuelen;
4670cf89 4984
4a63c884
JF
4985 name = chomp_string(name);
4986 namelen = strcspn(name, separators);
4987
4988 if (name[namelen]) {
4989 name[namelen] = 0;
4990 value = chomp_string(name + namelen + 1);
d0cea5f9 4991 valuelen = strlen(value);
4670cf89 4992
d0cea5f9 4993 } else {
d0cea5f9
JF
4994 value = "";
4995 valuelen = 0;
4670cf89 4996 }
d0cea5f9 4997
3c3801c2 4998 state = read_property(name, namelen, value, valuelen);
4670cf89
JF
4999 }
5000
d0cea5f9
JF
5001 if (state != ERR && ferror(pipe))
5002 state = ERR;
4670cf89
JF
5003
5004 pclose(pipe);
5005
d0cea5f9 5006 return state;
4670cf89
JF
5007}
5008
d0cea5f9 5009
6b161b31
JF
5010/*
5011 * Main
5012 */
5013
b5c9e67f 5014static void __NORETURN
6b161b31
JF
5015quit(int sig)
5016{
8855ada4
JF
5017 /* XXX: Restore tty modes and let the OS cleanup the rest! */
5018 if (cursed)
5019 endwin();
6b161b31
JF
5020 exit(0);
5021}
5022
c6704a4e
JF
5023static void __NORETURN
5024die(const char *err, ...)
6b161b31
JF
5025{
5026 va_list args;
5027
5028 endwin();
5029
5030 va_start(args, err);
5031 fputs("tig: ", stderr);
5032 vfprintf(stderr, err, args);
5033 fputs("\n", stderr);
5034 va_end(args);
5035
5036 exit(1);
5037}
5038
77452abc
JF
5039static void
5040warn(const char *msg, ...)
5041{
5042 va_list args;
5043
5044 va_start(args, msg);
5045 fputs("tig warning: ", stderr);
5046 vfprintf(stderr, msg, args);
5047 fputs("\n", stderr);
5048 va_end(args);
5049}
5050
6b161b31
JF
5051int
5052main(int argc, char *argv[])
5053{
1ba2ae4b 5054 struct view *view;
6b161b31 5055 enum request request;
1ba2ae4b 5056 size_t i;
6b161b31
JF
5057
5058 signal(SIGINT, quit);
5059
6b68fd24 5060 if (setlocale(LC_ALL, "")) {
739e81de
JF
5061 char *codeset = nl_langinfo(CODESET);
5062
5063 string_ncopy(opt_codeset, codeset, strlen(codeset));
6b68fd24
JF
5064 }
5065
e0f50df0
JF
5066 if (load_repo_info() == ERR)
5067 die("Failed to load repo info.");
5068
660e09ad
JF
5069 if (load_options() == ERR)
5070 die("Failed to load user config.");
5071
5072 /* Load the repo config file so options can be overwritten from
739e81de 5073 * the command line. */
14c778a6 5074 if (load_repo_config() == ERR)
afdc35b3 5075 die("Failed to load repo config.");
91c5d983 5076
8855ada4 5077 if (!parse_options(argc, argv))
6b161b31
JF
5078 return 0;
5079
58a5e4ea
JF
5080 /* Require a git repository unless when running in pager mode. */
5081 if (!opt_git_dir[0])
5082 die("Not a git repository");
5083
6b68fd24
JF
5084 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5085 opt_iconv = iconv_open(opt_codeset, opt_encoding);
20f4b4a3 5086 if (opt_iconv == ICONV_NONE)
6b68fd24
JF
5087 die("Failed to initialize character set conversion");
5088 }
5089
c34d9c9f
JF
5090 if (load_refs() == ERR)
5091 die("Failed to load refs.");
5092
1ba2ae4b
JF
5093 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5094 view->cmd_env = getenv(view->cmd_env);
5095
6b161b31
JF
5096 request = opt_request;
5097
5098 init_display();
b801d8b2
JF
5099
5100 while (view_driver(display[current_view], request)) {
6b161b31 5101 int key;
b801d8b2
JF
5102 int i;
5103
699ae55b 5104 foreach_view (view, i)
6b161b31 5105 update_view(view);
b801d8b2
JF
5106
5107 /* Refresh, accept single keystroke of input */
6b161b31 5108 key = wgetch(status_win);
04e2b7b2 5109
cf4d82e6
JF
5110 /* wgetch() with nodelay() enabled returns ERR when there's no
5111 * input. */
5112 if (key == ERR) {
5113 request = REQ_NONE;
8b534a13 5114 continue;
cf4d82e6 5115 }
04e2b7b2
JF
5116
5117 request = get_keybinding(display[current_view]->keymap, key);
03a93dbb 5118
6706b2ba 5119 /* Some low-level request handling. This keeps access to
fac7db6c
JF
5120 * status_win restricted. */
5121 switch (request) {
5122 case REQ_PROMPT:
9e21ce5c
JF
5123 {
5124 char *cmd = read_prompt(":");
5125
5126 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5127 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5128 opt_request = REQ_VIEW_DIFF;
5129 } else {
5130 opt_request = REQ_VIEW_PAGER;
5131 }
5132 break;
5133 }
fac7db6c 5134
1d754561 5135 request = REQ_NONE;
9e21ce5c
JF
5136 break;
5137 }
4af34daa
JF
5138 case REQ_SEARCH:
5139 case REQ_SEARCH_BACK:
5140 {
5141 const char *prompt = request == REQ_SEARCH
5142 ? "/" : "?";
5143 char *search = read_prompt(prompt);
5144
5145 if (search)
739e81de 5146 string_ncopy(opt_search, search, strlen(search));
4af34daa
JF
5147 else
5148 request = REQ_NONE;
5149 break;
5150 }
fac7db6c
JF
5151 case REQ_SCREEN_RESIZE:
5152 {
5153 int height, width;
5154
5155 getmaxyx(stdscr, height, width);
5156
5157 /* Resize the status view and let the view driver take
5158 * care of resizing the displayed views. */
5159 wresize(status_win, 1, width);
5160 mvwin(status_win, height - 1, 0);
5161 wrefresh(status_win);
5162 break;
5163 }
5164 default:
5165 break;
03a93dbb 5166 }
b801d8b2
JF
5167 }
5168
5169 quit(0);
5170
5171 return 0;
5172}