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