Add support for refreshing of the stage view
[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), \
2a67fb2a 589LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
6b161b31
JF
590LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
591LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
a28bcc22
JF
592LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
593LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
594LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
c34d9c9f 595LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
8f0bd8d8 596LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
8b3475e6
JF
597LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
598LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
8f0bd8d8
JF
599LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
600LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
f83b1c33 601LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
e733ee54 602LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
173d76ea 603LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
f5a5e640 604LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
53924375 605LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
173d76ea 606LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
53924375
JF
607LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
608LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
8a680988
JF
609LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
610LINE(BLAME_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
611LINE(BLAME_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
612LINE(BLAME_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
2a67fb2a 613LINE(BLAME_ID, "", COLOR_MAGENTA, 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
2a67fb2a
JF
1510 if (!selected)
1511 wattrset(view->win, get_line_attr(LINE_LINE_NUMBER));
f4de14c5
JF
1512 col = draw_text(view, number, max_number, showtrimmed, selected);
1513 if (col < max) {
1514 if (!selected)
1515 wattrset(view->win, A_NORMAL);
1516 waddch(view->win, ACS_VLINE);
1517 col++;
1518 }
1519 if (col < max) {
1520 waddch(view->win, ' ');
1521 col++;
1522 }
1523
1524 return col;
1525}
1526
fe7233c3
JF
1527static bool
1528draw_view_line(struct view *view, unsigned int lineno)
1529{
d720de4b 1530 struct line *line;
5dcf8064 1531 bool selected = (view->offset + lineno == view->lineno);
4887d44e 1532 bool draw_ok;
d720de4b 1533
699ae55b
JF
1534 assert(view_is_displayed(view));
1535
fe7233c3
JF
1536 if (view->offset + lineno >= view->lines)
1537 return FALSE;
1538
d720de4b
JF
1539 line = &view->line[view->offset + lineno];
1540
3c571d67
JF
1541 if (selected) {
1542 line->selected = TRUE;
d720de4b 1543 view->ops->select(view, line);
3c571d67
JF
1544 } else if (line->selected) {
1545 line->selected = FALSE;
1546 wmove(view->win, lineno, 0);
1547 wclrtoeol(view->win);
1548 }
d720de4b 1549
4887d44e
JF
1550 scrollok(view->win, FALSE);
1551 draw_ok = view->ops->draw(view, line, lineno, selected);
1552 scrollok(view->win, TRUE);
1553
1554 return draw_ok;
fe7233c3
JF
1555}
1556
b801d8b2 1557static void
8a680988
JF
1558redraw_view_dirty(struct view *view)
1559{
1560 bool dirty = FALSE;
1561 int lineno;
1562
1563 for (lineno = 0; lineno < view->height; lineno++) {
1564 struct line *line = &view->line[view->offset + lineno];
1565
1566 if (!line->dirty)
1567 continue;
1568 line->dirty = 0;
1569 dirty = TRUE;
1570 if (!draw_view_line(view, lineno))
1571 break;
1572 }
1573
1574 if (!dirty)
1575 return;
1576 redrawwin(view->win);
1577 if (input_mode)
1578 wnoutrefresh(view->win);
1579 else
1580 wrefresh(view->win);
1581}
1582
1583static void
82e78006 1584redraw_view_from(struct view *view, int lineno)
b801d8b2 1585{
82e78006 1586 assert(0 <= lineno && lineno < view->height);
b801d8b2 1587
82e78006 1588 for (; lineno < view->height; lineno++) {
fe7233c3 1589 if (!draw_view_line(view, lineno))
fd85fef1 1590 break;
b801d8b2
JF
1591 }
1592
1593 redrawwin(view->win);
ab4af23e
JF
1594 if (input_mode)
1595 wnoutrefresh(view->win);
1596 else
1597 wrefresh(view->win);
b801d8b2
JF
1598}
1599
b76c2afc 1600static void
82e78006
JF
1601redraw_view(struct view *view)
1602{
1603 wclear(view->win);
1604 redraw_view_from(view, 0);
1605}
1606
c2124ccd 1607
6b161b31 1608static void
81030ec8
JF
1609update_view_title(struct view *view)
1610{
3c112a88 1611 char buf[SIZEOF_STR];
71d1c7db
JF
1612 char state[SIZEOF_STR];
1613 size_t bufpos = 0, statelen = 0;
81030ec8 1614
3c112a88 1615 assert(view_is_displayed(view));
81030ec8 1616
249016a6 1617 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
6d9c07af 1618 unsigned int view_lines = view->offset + view->height;
c19f8017 1619 unsigned int lines = view->lines
6d9c07af 1620 ? MIN(view_lines, view->lines) * 100 / view->lines
c19f8017
JF
1621 : 0;
1622
71d1c7db 1623 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
3c112a88
JF
1624 view->ops->type,
1625 view->lineno + 1,
1626 view->lines,
1627 lines);
81030ec8 1628
5becf244
JF
1629 if (view->pipe) {
1630 time_t secs = time(NULL) - view->start_time;
f97f4012 1631
5becf244
JF
1632 /* Three git seconds are a long time ... */
1633 if (secs > 2)
71d1c7db 1634 string_format_from(state, &statelen, " %lds", secs);
5becf244 1635 }
81030ec8
JF
1636 }
1637
71d1c7db
JF
1638 string_format_from(buf, &bufpos, "[%s]", view->name);
1639 if (*view->ref && bufpos < view->width) {
1640 size_t refsize = strlen(view->ref);
1641 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1642
1643 if (minsize < view->width)
1644 refsize = view->width - minsize + 7;
d1858deb 1645 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
71d1c7db 1646 }
f97f4012 1647
71d1c7db
JF
1648 if (statelen && bufpos < view->width) {
1649 string_format_from(buf, &bufpos, " %s", state);
f97f4012
JF
1650 }
1651
3c112a88
JF
1652 if (view == display[current_view])
1653 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1654 else
1655 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1656
3c112a88 1657 mvwaddnstr(view->title, 0, 0, buf, bufpos);
390a8262 1658 wclrtoeol(view->title);
976447f8 1659 wmove(view->title, 0, view->width - 1);
ab4af23e
JF
1660
1661 if (input_mode)
1662 wnoutrefresh(view->title);
1663 else
1664 wrefresh(view->title);
81030ec8
JF
1665}
1666
1667static void
6b161b31 1668resize_display(void)
b76c2afc 1669{
03a93dbb 1670 int offset, i;
6b161b31
JF
1671 struct view *base = display[0];
1672 struct view *view = display[1] ? display[1] : display[0];
b76c2afc 1673
6b161b31 1674 /* Setup window dimensions */
b76c2afc 1675
03a93dbb 1676 getmaxyx(stdscr, base->height, base->width);
b76c2afc 1677
6b161b31 1678 /* Make room for the status window. */
03a93dbb 1679 base->height -= 1;
6b161b31
JF
1680
1681 if (view != base) {
03a93dbb
JF
1682 /* Horizontal split. */
1683 view->width = base->width;
6b161b31
JF
1684 view->height = SCALE_SPLIT_VIEW(base->height);
1685 base->height -= view->height;
1686
1687 /* Make room for the title bar. */
1688 view->height -= 1;
1689 }
1690
1691 /* Make room for the title bar. */
1692 base->height -= 1;
1693
1694 offset = 0;
1695
33c4f9ea 1696 foreach_displayed_view (view, i) {
b76c2afc 1697 if (!view->win) {
c19f8017 1698 view->win = newwin(view->height, 0, offset, 0);
6b161b31
JF
1699 if (!view->win)
1700 die("Failed to create %s view", view->name);
1701
1702 scrollok(view->win, TRUE);
1703
1704 view->title = newwin(1, 0, offset + view->height, 0);
1705 if (!view->title)
1706 die("Failed to create title window");
1707
1708 } else {
c19f8017 1709 wresize(view->win, view->height, view->width);
6b161b31
JF
1710 mvwin(view->win, offset, 0);
1711 mvwin(view->title, offset + view->height, 0);
a28bcc22 1712 }
a28bcc22 1713
6b161b31 1714 offset += view->height + 1;
b76c2afc 1715 }
6b161b31 1716}
b76c2afc 1717
6b161b31 1718static void
20bb5e18
JF
1719redraw_display(void)
1720{
1721 struct view *view;
1722 int i;
1723
33c4f9ea 1724 foreach_displayed_view (view, i) {
20bb5e18
JF
1725 redraw_view(view);
1726 update_view_title(view);
1727 }
1728}
1729
85af6284 1730static void
2bee3bde 1731update_display_cursor(struct view *view)
85af6284 1732{
85af6284
JF
1733 /* Move the cursor to the right-most column of the cursor line.
1734 *
1735 * XXX: This could turn out to be a bit expensive, but it ensures that
1736 * the cursor does not jump around. */
1737 if (view->lines) {
1738 wmove(view->win, view->lineno - view->offset, view->width - 1);
1739 wrefresh(view->win);
1740 }
1741}
20bb5e18 1742
2e8488b4
JF
1743/*
1744 * Navigation
1745 */
1746
4a2909a7 1747/* Scrolling backend */
b801d8b2 1748static void
8c317212 1749do_scroll_view(struct view *view, int lines)
b801d8b2 1750{
a0087dd5
JF
1751 bool redraw_current_line = FALSE;
1752
fd85fef1
JF
1753 /* The rendering expects the new offset. */
1754 view->offset += lines;
1755
1756 assert(0 <= view->offset && view->offset < view->lines);
1757 assert(lines);
b801d8b2 1758
a0087dd5
JF
1759 /* Move current line into the view. */
1760 if (view->lineno < view->offset) {
1761 view->lineno = view->offset;
1762 redraw_current_line = TRUE;
1763 } else if (view->lineno >= view->offset + view->height) {
1764 view->lineno = view->offset + view->height - 1;
1765 redraw_current_line = TRUE;
1766 }
1767
1768 assert(view->offset <= view->lineno && view->lineno < view->lines);
1769
82e78006 1770 /* Redraw the whole screen if scrolling is pointless. */
4c6fabc2 1771 if (view->height < ABS(lines)) {
b76c2afc
JF
1772 redraw_view(view);
1773
1774 } else {
22f66b0a 1775 int line = lines > 0 ? view->height - lines : 0;
82e78006 1776 int end = line + ABS(lines);
fd85fef1
JF
1777
1778 wscrl(view->win, lines);
1779
22f66b0a 1780 for (; line < end; line++) {
fe7233c3 1781 if (!draw_view_line(view, line))
fd85fef1
JF
1782 break;
1783 }
fd85fef1 1784
a0087dd5
JF
1785 if (redraw_current_line)
1786 draw_view_line(view, view->lineno - view->offset);
fd85fef1
JF
1787 }
1788
fd85fef1
JF
1789 redrawwin(view->win);
1790 wrefresh(view->win);
9d3f5834 1791 report("");
fd85fef1 1792}
78c70acd 1793
4a2909a7 1794/* Scroll frontend */
fd85fef1 1795static void
6b161b31 1796scroll_view(struct view *view, enum request request)
fd85fef1
JF
1797{
1798 int lines = 1;
b801d8b2 1799
8c317212
JF
1800 assert(view_is_displayed(view));
1801
b801d8b2 1802 switch (request) {
4a2909a7 1803 case REQ_SCROLL_PAGE_DOWN:
fd85fef1 1804 lines = view->height;
4a2909a7 1805 case REQ_SCROLL_LINE_DOWN:
b801d8b2 1806 if (view->offset + lines > view->lines)
bde3653a 1807 lines = view->lines - view->offset;
b801d8b2 1808
fd85fef1 1809 if (lines == 0 || view->offset + view->height >= view->lines) {
eb98559e 1810 report("Cannot scroll beyond the last line");
b801d8b2
JF
1811 return;
1812 }
1813 break;
1814
4a2909a7 1815 case REQ_SCROLL_PAGE_UP:
fd85fef1 1816 lines = view->height;
4a2909a7 1817 case REQ_SCROLL_LINE_UP:
b801d8b2
JF
1818 if (lines > view->offset)
1819 lines = view->offset;
1820
1821 if (lines == 0) {
eb98559e 1822 report("Cannot scroll beyond the first line");
b801d8b2
JF
1823 return;
1824 }
1825
fd85fef1 1826 lines = -lines;
b801d8b2 1827 break;
03a93dbb 1828
6b161b31
JF
1829 default:
1830 die("request %d not handled in switch", request);
b801d8b2
JF
1831 }
1832
8c317212 1833 do_scroll_view(view, lines);
fd85fef1 1834}
b801d8b2 1835
4a2909a7 1836/* Cursor moving */
fd85fef1 1837static void
8522ecc7 1838move_view(struct view *view, enum request request)
fd85fef1 1839{
dfaa6c81 1840 int scroll_steps = 0;
fd85fef1 1841 int steps;
b801d8b2 1842
fd85fef1 1843 switch (request) {
4a2909a7 1844 case REQ_MOVE_FIRST_LINE:
78c70acd
JF
1845 steps = -view->lineno;
1846 break;
1847
4a2909a7 1848 case REQ_MOVE_LAST_LINE:
78c70acd
JF
1849 steps = view->lines - view->lineno - 1;
1850 break;
1851
4a2909a7 1852 case REQ_MOVE_PAGE_UP:
78c70acd
JF
1853 steps = view->height > view->lineno
1854 ? -view->lineno : -view->height;
1855 break;
1856
4a2909a7 1857 case REQ_MOVE_PAGE_DOWN:
78c70acd
JF
1858 steps = view->lineno + view->height >= view->lines
1859 ? view->lines - view->lineno - 1 : view->height;
1860 break;
1861
4a2909a7 1862 case REQ_MOVE_UP:
fd85fef1
JF
1863 steps = -1;
1864 break;
b801d8b2 1865
4a2909a7 1866 case REQ_MOVE_DOWN:
fd85fef1
JF
1867 steps = 1;
1868 break;
6b161b31
JF
1869
1870 default:
1871 die("request %d not handled in switch", request);
78c70acd 1872 }
b801d8b2 1873
4c6fabc2 1874 if (steps <= 0 && view->lineno == 0) {
eb98559e 1875 report("Cannot move beyond the first line");
78c70acd 1876 return;
b801d8b2 1877
6908bdbd 1878 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
eb98559e 1879 report("Cannot move beyond the last line");
78c70acd 1880 return;
fd85fef1
JF
1881 }
1882
4c6fabc2 1883 /* Move the current line */
fd85fef1 1884 view->lineno += steps;
4c6fabc2
JF
1885 assert(0 <= view->lineno && view->lineno < view->lines);
1886
4c6fabc2 1887 /* Check whether the view needs to be scrolled */
fd85fef1
JF
1888 if (view->lineno < view->offset ||
1889 view->lineno >= view->offset + view->height) {
dfaa6c81 1890 scroll_steps = steps;
fd85fef1 1891 if (steps < 0 && -steps > view->offset) {
dfaa6c81 1892 scroll_steps = -view->offset;
b76c2afc
JF
1893
1894 } else if (steps > 0) {
1895 if (view->lineno == view->lines - 1 &&
1896 view->lines > view->height) {
dfaa6c81
JF
1897 scroll_steps = view->lines - view->offset - 1;
1898 if (scroll_steps >= view->height)
1899 scroll_steps -= view->height - 1;
b76c2afc 1900 }
b801d8b2 1901 }
8522ecc7
JF
1902 }
1903
1904 if (!view_is_displayed(view)) {
a3965365
JF
1905 view->offset += scroll_steps;
1906 assert(0 <= view->offset && view->offset < view->lines);
8522ecc7
JF
1907 view->ops->select(view, &view->line[view->lineno]);
1908 return;
1909 }
1910
1911 /* Repaint the old "current" line if we be scrolling */
1912 if (ABS(steps) < view->height)
1913 draw_view_line(view, view->lineno - steps - view->offset);
1914
dfaa6c81
JF
1915 if (scroll_steps) {
1916 do_scroll_view(view, scroll_steps);
fd85fef1 1917 return;
b801d8b2
JF
1918 }
1919
4c6fabc2 1920 /* Draw the current line */
fe7233c3 1921 draw_view_line(view, view->lineno - view->offset);
fd85fef1 1922
b801d8b2
JF
1923 redrawwin(view->win);
1924 wrefresh(view->win);
9d3f5834 1925 report("");
b801d8b2
JF
1926}
1927
b801d8b2 1928
2e8488b4 1929/*
4af34daa
JF
1930 * Searching
1931 */
1932
c02d8fce 1933static void search_view(struct view *view, enum request request);
4af34daa
JF
1934
1935static bool
1936find_next_line(struct view *view, unsigned long lineno, struct line *line)
1937{
699ae55b
JF
1938 assert(view_is_displayed(view));
1939
4af34daa
JF
1940 if (!view->ops->grep(view, line))
1941 return FALSE;
1942
1943 if (lineno - view->offset >= view->height) {
1944 view->offset = lineno;
1945 view->lineno = lineno;
1946 redraw_view(view);
1947
1948 } else {
1949 unsigned long old_lineno = view->lineno - view->offset;
1950
1951 view->lineno = lineno;
4af34daa
JF
1952 draw_view_line(view, old_lineno);
1953
1954 draw_view_line(view, view->lineno - view->offset);
1955 redrawwin(view->win);
1956 wrefresh(view->win);
1957 }
1958
1959 report("Line %ld matches '%s'", lineno + 1, view->grep);
1960 return TRUE;
1961}
1962
1963static void
1964find_next(struct view *view, enum request request)
1965{
1966 unsigned long lineno = view->lineno;
1967 int direction;
1968
1969 if (!*view->grep) {
1970 if (!*opt_search)
1971 report("No previous search");
1972 else
c02d8fce 1973 search_view(view, request);
4af34daa
JF
1974 return;
1975 }
1976
1977 switch (request) {
1978 case REQ_SEARCH:
1979 case REQ_FIND_NEXT:
1980 direction = 1;
1981 break;
1982
1983 case REQ_SEARCH_BACK:
1984 case REQ_FIND_PREV:
1985 direction = -1;
1986 break;
1987
1988 default:
1989 return;
1990 }
1991
1992 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1993 lineno += direction;
1994
1995 /* Note, lineno is unsigned long so will wrap around in which case it
1996 * will become bigger than view->lines. */
1997 for (; lineno < view->lines; lineno += direction) {
1998 struct line *line = &view->line[lineno];
1999
2000 if (find_next_line(view, lineno, line))
2001 return;
2002 }
2003
2004 report("No match found for '%s'", view->grep);
2005}
2006
2007static void
c02d8fce 2008search_view(struct view *view, enum request request)
4af34daa
JF
2009{
2010 int regex_err;
2011
b77b2cb8
JF
2012 if (view->regex) {
2013 regfree(view->regex);
4af34daa 2014 *view->grep = 0;
b77b2cb8
JF
2015 } else {
2016 view->regex = calloc(1, sizeof(*view->regex));
2017 if (!view->regex)
2018 return;
4af34daa
JF
2019 }
2020
c02d8fce 2021 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
4af34daa
JF
2022 if (regex_err != 0) {
2023 char buf[SIZEOF_STR] = "unknown error";
2024
b77b2cb8 2025 regerror(regex_err, view->regex, buf, sizeof(buf));
e9cacd58 2026 report("Search failed: %s", buf);
4af34daa
JF
2027 return;
2028 }
2029
c02d8fce 2030 string_copy(view->grep, opt_search);
4af34daa
JF
2031
2032 find_next(view, request);
2033}
2034
2035/*
2e8488b4
JF
2036 * Incremental updating
2037 */
b801d8b2 2038
199d1288
JF
2039static void
2040end_update(struct view *view)
2041{
2042 if (!view->pipe)
2043 return;
2044 set_nonblocking_input(FALSE);
2045 if (view->pipe == stdin)
2046 fclose(view->pipe);
2047 else
2048 pclose(view->pipe);
2049 view->pipe = NULL;
2050}
2051
03a93dbb 2052static bool
b801d8b2
JF
2053begin_update(struct view *view)
2054{
199d1288
JF
2055 if (view->pipe)
2056 end_update(view);
2057
03a93dbb
JF
2058 if (opt_cmd[0]) {
2059 string_copy(view->cmd, opt_cmd);
2060 opt_cmd[0] = 0;
035ba11f
JF
2061 /* When running random commands, initially show the
2062 * command in the title. However, it maybe later be
2063 * overwritten if a commit line is selected. */
809a9f48
JF
2064 if (view == VIEW(REQ_VIEW_PAGER))
2065 string_copy(view->ref, view->cmd);
2066 else
2067 view->ref[0] = 0;
e733ee54
JF
2068
2069 } else if (view == VIEW(REQ_VIEW_TREE)) {
2070 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
f0f114ac 2071 char path[SIZEOF_STR];
e733ee54
JF
2072
2073 if (strcmp(view->vid, view->id))
f0f114ac
JF
2074 opt_path[0] = path[0] = 0;
2075 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2076 return FALSE;
e733ee54 2077
739e81de 2078 if (!string_format(view->cmd, format, view->id, path))
e733ee54
JF
2079 return FALSE;
2080
03a93dbb 2081 } else {
4685845e 2082 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
739e81de 2083 const char *id = view->id;
1ba2ae4b 2084
cc2d1364 2085 if (!string_format(view->cmd, format, id, id, id, id, id))
03a93dbb 2086 return FALSE;
035ba11f
JF
2087
2088 /* Put the current ref_* value to the view title ref
2089 * member. This is needed by the blob view. Most other
2090 * views sets it automatically after loading because the
2091 * first line is a commit line. */
739e81de 2092 string_copy_rev(view->ref, view->id);
03a93dbb 2093 }
b801d8b2 2094
6908bdbd
JF
2095 /* Special case for the pager view. */
2096 if (opt_pipe) {
2097 view->pipe = opt_pipe;
2098 opt_pipe = NULL;
2099 } else {
2100 view->pipe = popen(view->cmd, "r");
2101 }
2102
2e8488b4
JF
2103 if (!view->pipe)
2104 return FALSE;
b801d8b2 2105
6b161b31 2106 set_nonblocking_input(TRUE);
b801d8b2
JF
2107
2108 view->offset = 0;
2109 view->lines = 0;
2110 view->lineno = 0;
739e81de 2111 string_copy_rev(view->vid, view->id);
b801d8b2 2112
2e8488b4
JF
2113 if (view->line) {
2114 int i;
2115
2116 for (i = 0; i < view->lines; i++)
fe7233c3
JF
2117 if (view->line[i].data)
2118 free(view->line[i].data);
2e8488b4
JF
2119
2120 free(view->line);
2121 view->line = NULL;
2122 }
2123
2124 view->start_time = time(NULL);
2125
b801d8b2
JF
2126 return TRUE;
2127}
2128
518234f1
DV
2129#define ITEM_CHUNK_SIZE 256
2130static void *
2131realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2132{
2133 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2134 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2135
2136 if (mem == NULL || num_chunks != num_chunks_new) {
2137 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2138 mem = realloc(mem, *size * item_size);
2139 }
2140
2141 return mem;
2142}
2143
e2c01617
JF
2144static struct line *
2145realloc_lines(struct view *view, size_t line_size)
2146{
518234f1
DV
2147 size_t alloc = view->line_alloc;
2148 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2149 sizeof(*view->line));
e2c01617
JF
2150
2151 if (!tmp)
2152 return NULL;
2153
2154 view->line = tmp;
518234f1 2155 view->line_alloc = alloc;
e2c01617
JF
2156 view->line_size = line_size;
2157 return view->line;
2158}
2159
03a93dbb 2160static bool
b801d8b2
JF
2161update_view(struct view *view)
2162{
6b68fd24
JF
2163 char in_buffer[BUFSIZ];
2164 char out_buffer[BUFSIZ * 2];
b801d8b2 2165 char *line;
82e78006
JF
2166 /* The number of lines to read. If too low it will cause too much
2167 * redrawing (and possible flickering), if too high responsiveness
2168 * will suffer. */
8855ada4 2169 unsigned long lines = view->height;
82e78006 2170 int redraw_from = -1;
b801d8b2
JF
2171
2172 if (!view->pipe)
2173 return TRUE;
2174
82e78006
JF
2175 /* Only redraw if lines are visible. */
2176 if (view->offset + view->height >= view->lines)
2177 redraw_from = view->lines - view->offset;
b801d8b2 2178
699ae55b 2179 /* FIXME: This is probably not perfect for backgrounded views. */
e2c01617 2180 if (!realloc_lines(view, view->lines + lines))
b801d8b2
JF
2181 goto alloc_error;
2182
6b68fd24
JF
2183 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2184 size_t linelen = strlen(line);
b801d8b2 2185
b801d8b2
JF
2186 if (linelen)
2187 line[linelen - 1] = 0;
2188
6b68fd24 2189 if (opt_iconv != ICONV_NONE) {
e47afdf2 2190 ICONV_CONST char *inbuf = line;
6b68fd24
JF
2191 size_t inlen = linelen;
2192
2193 char *outbuf = out_buffer;
2194 size_t outlen = sizeof(out_buffer);
2195
2196 size_t ret;
2197
7361622d 2198 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
6b68fd24
JF
2199 if (ret != (size_t) -1) {
2200 line = out_buffer;
2201 linelen = strlen(out_buffer);
2202 }
2203 }
2204
701e4f5d 2205 if (!view->ops->read(view, line))
b801d8b2 2206 goto alloc_error;
fd85fef1
JF
2207
2208 if (lines-- == 1)
2209 break;
b801d8b2
JF
2210 }
2211
8855ada4
JF
2212 {
2213 int digits;
2214
2215 lines = view->lines;
2216 for (digits = 0; lines; digits++)
2217 lines /= 10;
2218
2219 /* Keep the displayed view in sync with line number scaling. */
2220 if (digits != view->digits) {
2221 view->digits = digits;
2222 redraw_from = 0;
2223 }
2224 }
2225
699ae55b
JF
2226 if (!view_is_displayed(view))
2227 goto check_pipe;
2228
e733ee54
JF
2229 if (view == VIEW(REQ_VIEW_TREE)) {
2230 /* Clear the view and redraw everything since the tree sorting
2231 * might have rearranged things. */
2232 redraw_view(view);
2233
2234 } else if (redraw_from >= 0) {
82e78006 2235 /* If this is an incremental update, redraw the previous line
a28bcc22
JF
2236 * since for commits some members could have changed when
2237 * loading the main view. */
82e78006
JF
2238 if (redraw_from > 0)
2239 redraw_from--;
2240
9eded379
JF
2241 /* Since revision graph visualization requires knowledge
2242 * about the parent commit, it causes a further one-off
2243 * needed to be redrawn for incremental updates. */
2244 if (redraw_from > 0 && opt_rev_graph)
2245 redraw_from--;
2246
82e78006
JF
2247 /* Incrementally draw avoids flickering. */
2248 redraw_view_from(view, redraw_from);
4c6fabc2 2249 }
b801d8b2 2250
8a680988
JF
2251 if (view == VIEW(REQ_VIEW_BLAME))
2252 redraw_view_dirty(view);
2253
eb98559e
JF
2254 /* Update the title _after_ the redraw so that if the redraw picks up a
2255 * commit reference in view->ref it'll be available here. */
2256 update_view_title(view);
2257
699ae55b 2258check_pipe:
b801d8b2 2259 if (ferror(view->pipe)) {
03a93dbb 2260 report("Failed to read: %s", strerror(errno));
b801d8b2
JF
2261 goto end;
2262
2263 } else if (feof(view->pipe)) {
f97f4012 2264 report("");
b801d8b2
JF
2265 goto end;
2266 }
2267
2268 return TRUE;
2269
2270alloc_error:
2e8488b4 2271 report("Allocation failure");
b801d8b2
JF
2272
2273end:
4ed67514
JF
2274 if (view->ops->read(view, NULL))
2275 end_update(view);
b801d8b2
JF
2276 return FALSE;
2277}
2278
0a0d8910 2279static struct line *
e314c36d 2280add_line_data(struct view *view, void *data, enum line_type type)
0a0d8910 2281{
e314c36d 2282 struct line *line = &view->line[view->lines++];
0a0d8910 2283
e314c36d 2284 memset(line, 0, sizeof(*line));
0a0d8910 2285 line->type = type;
e314c36d 2286 line->data = data;
0a0d8910
JF
2287
2288 return line;
2289}
2290
e314c36d
JF
2291static struct line *
2292add_line_text(struct view *view, char *data, enum line_type type)
2293{
2294 if (data)
2295 data = strdup(data);
2296
2297 return data ? add_line_data(view, data, type) : NULL;
2298}
2299
79d445ca 2300
e10154d5
JF
2301/*
2302 * View opening
2303 */
2304
49f2b43f
JF
2305enum open_flags {
2306 OPEN_DEFAULT = 0, /* Use default view switching. */
2307 OPEN_SPLIT = 1, /* Split current view. */
2308 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2309 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
61e39818 2310 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
49f2b43f
JF
2311};
2312
6b161b31 2313static void
49f2b43f 2314open_view(struct view *prev, enum request request, enum open_flags flags)
b801d8b2 2315{
49f2b43f
JF
2316 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2317 bool split = !!(flags & OPEN_SPLIT);
2318 bool reload = !!(flags & OPEN_RELOAD);
61e39818 2319 bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
a28bcc22 2320 struct view *view = VIEW(request);
9f41488f 2321 int nviews = displayed_views();
6e950a52 2322 struct view *base_view = display[0];
b801d8b2 2323
49f2b43f 2324 if (view == prev && nviews == 1 && !reload) {
6b161b31
JF
2325 report("Already in %s view", view->name);
2326 return;
2327 }
b801d8b2 2328
d7e6b0e8
JF
2329 if (view->git_dir && !opt_git_dir[0]) {
2330 report("The %s view is disabled in pager view", view->name);
2331 return;
2332 }
2333
6b161b31 2334 if (split) {
8d741c06 2335 display[1] = view;
6b161b31 2336 if (!backgrounded)
8d741c06 2337 current_view = 1;
61e39818 2338 } else if (!nomaximize) {
6b161b31
JF
2339 /* Maximize the current view. */
2340 memset(display, 0, sizeof(display));
2341 current_view = 0;
2342 display[current_view] = view;
a28bcc22 2343 }
b801d8b2 2344
6e950a52
JF
2345 /* Resize the view when switching between split- and full-screen,
2346 * or when switching between two different full-screen views. */
2347 if (nviews != displayed_views() ||
2348 (nviews == 1 && base_view != display[0]))
a006db63 2349 resize_display();
b801d8b2 2350
31862819
JF
2351 if (view->ops->open) {
2352 if (!view->ops->open(view)) {
2353 report("Failed to load %s view", view->name);
2354 return;
2355 }
2356
2357 } else if ((reload || strcmp(view->vid, view->id)) &&
2358 !begin_update(view)) {
2359 report("Failed to load %s view", view->name);
2360 return;
2361 }
2362
a8891802 2363 if (split && prev->lineno - prev->offset >= prev->height) {
03a93dbb 2364 /* Take the title line into account. */
eb98559e 2365 int lines = prev->lineno - prev->offset - prev->height + 1;
03a93dbb
JF
2366
2367 /* Scroll the view that was split if the current line is
2368 * outside the new limited view. */
8c317212 2369 do_scroll_view(prev, lines);
03a93dbb
JF
2370 }
2371
6b161b31 2372 if (prev && view != prev) {
9b995f0c 2373 if (split && !backgrounded) {
f0b3ab80
JF
2374 /* "Blur" the previous view. */
2375 update_view_title(prev);
9f396969 2376 }
f0b3ab80 2377
f6da0b66 2378 view->parent = prev;
b801d8b2
JF
2379 }
2380
9f396969 2381 if (view->pipe && view->lines == 0) {
03a93dbb
JF
2382 /* Clear the old view and let the incremental updating refill
2383 * the screen. */
cc73d2e1 2384 werase(view->win);
f97f4012 2385 report("");
03a93dbb
JF
2386 } else {
2387 redraw_view(view);
24b5b3e0 2388 report("");
03a93dbb 2389 }
6706b2ba
JF
2390
2391 /* If the view is backgrounded the above calls to report()
2392 * won't redraw the view title. */
2393 if (backgrounded)
2394 update_view_title(view);
b801d8b2
JF
2395}
2396
0cea0d43 2397static void
d24ef76c
JF
2398open_external_viewer(const char *cmd)
2399{
2400 def_prog_mode(); /* save current tty modes */
2401 endwin(); /* restore original tty modes */
2402 system(cmd);
2403 fprintf(stderr, "Press Enter to continue");
2404 getc(stdin);
2405 reset_prog_mode();
2406 redraw_display();
2407}
2408
2409static void
b5c18d9d
JF
2410open_mergetool(const char *file)
2411{
2412 char cmd[SIZEOF_STR];
2413 char file_sq[SIZEOF_STR];
2414
2415 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2416 string_format(cmd, "git mergetool %s", file_sq)) {
2417 open_external_viewer(cmd);
2418 }
2419}
2420
2421static void
d24ef76c 2422open_editor(bool from_root, const char *file)
0cea0d43
JF
2423{
2424 char cmd[SIZEOF_STR];
2425 char file_sq[SIZEOF_STR];
2426 char *editor;
7d31b059 2427 char *prefix = from_root ? opt_cdup : "";
0cea0d43
JF
2428
2429 editor = getenv("GIT_EDITOR");
2430 if (!editor && *opt_editor)
2431 editor = opt_editor;
2432 if (!editor)
2433 editor = getenv("VISUAL");
2434 if (!editor)
2435 editor = getenv("EDITOR");
2436 if (!editor)
2437 editor = "vi";
2438
2439 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
7d31b059 2440 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
d24ef76c 2441 open_external_viewer(cmd);
0cea0d43
JF
2442 }
2443}
b801d8b2 2444
9eb14b72
JF
2445static void
2446open_run_request(enum request request)
2447{
2448 struct run_request *req = get_run_request(request);
2449 char buf[SIZEOF_STR * 2];
2450 size_t bufpos;
2451 char *cmd;
2452
2453 if (!req) {
2454 report("Unknown run request");
2455 return;
2456 }
2457
2458 bufpos = 0;
2459 cmd = req->cmd;
2460
2461 while (cmd) {
2462 char *next = strstr(cmd, "%(");
2463 int len = next - cmd;
2464 char *value;
2465
2466 if (!next) {
2467 len = strlen(cmd);
2468 value = "";
2469
2470 } else if (!strncmp(next, "%(head)", 7)) {
2471 value = ref_head;
2472
2473 } else if (!strncmp(next, "%(commit)", 9)) {
2474 value = ref_commit;
2475
2476 } else if (!strncmp(next, "%(blob)", 7)) {
2477 value = ref_blob;
2478
2479 } else {
2480 report("Unknown replacement in run request: `%s`", req->cmd);
2481 return;
2482 }
2483
2484 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2485 return;
2486
2487 if (next)
2488 next = strchr(next, ')') + 1;
2489 cmd = next;
2490 }
2491
2492 open_external_viewer(buf);
2493}
2494
6b161b31
JF
2495/*
2496 * User request switch noodle
2497 */
2498
b801d8b2 2499static int
6b161b31 2500view_driver(struct view *view, enum request request)
b801d8b2 2501{
b801d8b2
JF
2502 int i;
2503
1bace428
JF
2504 if (request == REQ_NONE) {
2505 doupdate();
2506 return TRUE;
2507 }
2508
9eb14b72
JF
2509 if (request > REQ_NONE) {
2510 open_run_request(request);
2511 return TRUE;
2512 }
2513
586c423d
JF
2514 if (view && view->lines) {
2515 request = view->ops->request(view, request, &view->line[view->lineno]);
2516 if (request == REQ_NONE)
2517 return TRUE;
2518 }
2519
b801d8b2 2520 switch (request) {
4a2909a7
JF
2521 case REQ_MOVE_UP:
2522 case REQ_MOVE_DOWN:
2523 case REQ_MOVE_PAGE_UP:
2524 case REQ_MOVE_PAGE_DOWN:
2525 case REQ_MOVE_FIRST_LINE:
2526 case REQ_MOVE_LAST_LINE:
8522ecc7 2527 move_view(view, request);
fd85fef1
JF
2528 break;
2529
4a2909a7
JF
2530 case REQ_SCROLL_LINE_DOWN:
2531 case REQ_SCROLL_LINE_UP:
2532 case REQ_SCROLL_PAGE_DOWN:
2533 case REQ_SCROLL_PAGE_UP:
a28bcc22 2534 scroll_view(view, request);
b801d8b2
JF
2535 break;
2536
8a680988 2537 case REQ_VIEW_BLAME:
a2d5d9ef 2538 if (!opt_file[0]) {
8a680988
JF
2539 report("No file chosen, press %s to open tree view",
2540 get_key(REQ_VIEW_TREE));
2541 break;
2542 }
8a680988
JF
2543 open_view(view, request, OPEN_DEFAULT);
2544 break;
2545
e733ee54
JF
2546 case REQ_VIEW_BLOB:
2547 if (!ref_blob[0]) {
550cd4b5
JF
2548 report("No file chosen, press %s to open tree view",
2549 get_key(REQ_VIEW_TREE));
e733ee54
JF
2550 break;
2551 }
5c4358d1
JF
2552 open_view(view, request, OPEN_DEFAULT);
2553 break;
2554
2555 case REQ_VIEW_PAGER:
b64c5b75 2556 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
5c4358d1
JF
2557 report("No pager content, press %s to run command from prompt",
2558 get_key(REQ_PROMPT));
2559 break;
2560 }
2561 open_view(view, request, OPEN_DEFAULT);
2562 break;
2563
3e634113
JF
2564 case REQ_VIEW_STAGE:
2565 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2566 report("No stage content, press %s to open the status view and choose file",
2567 get_key(REQ_VIEW_STATUS));
2568 break;
2569 }
2570 open_view(view, request, OPEN_DEFAULT);
2571 break;
2572
c38c64bb
JF
2573 case REQ_VIEW_STATUS:
2574 if (opt_is_inside_work_tree == FALSE) {
2575 report("The status view requires a working tree");
2576 break;
2577 }
2578 open_view(view, request, OPEN_DEFAULT);
2579 break;
2580
4a2909a7 2581 case REQ_VIEW_MAIN:
4a2909a7 2582 case REQ_VIEW_DIFF:
2e8488b4 2583 case REQ_VIEW_LOG:
e733ee54 2584 case REQ_VIEW_TREE:
2e8488b4 2585 case REQ_VIEW_HELP:
49f2b43f 2586 open_view(view, request, OPEN_DEFAULT);
b801d8b2
JF
2587 break;
2588
b3a54cba
JF
2589 case REQ_NEXT:
2590 case REQ_PREVIOUS:
2591 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2592
e733ee54
JF
2593 if ((view == VIEW(REQ_VIEW_DIFF) &&
2594 view->parent == VIEW(REQ_VIEW_MAIN)) ||
8a680988
JF
2595 (view == VIEW(REQ_VIEW_DIFF) &&
2596 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3e634113 2597 (view == VIEW(REQ_VIEW_STAGE) &&
b9b5b4cd 2598 view->parent == VIEW(REQ_VIEW_STATUS)) ||
e733ee54
JF
2599 (view == VIEW(REQ_VIEW_BLOB) &&
2600 view->parent == VIEW(REQ_VIEW_TREE))) {
03400136
WF
2601 int line;
2602
b3a54cba 2603 view = view->parent;
03400136 2604 line = view->lineno;
8522ecc7
JF
2605 move_view(view, request);
2606 if (view_is_displayed(view))
f0b3ab80 2607 update_view_title(view);
328d27f7
JF
2608 if (line != view->lineno)
2609 view->ops->request(view, REQ_ENTER,
2610 &view->line[view->lineno]);
2611
b3a54cba 2612 } else {
8522ecc7 2613 move_view(view, request);
b3a54cba 2614 }
328d27f7 2615 break;
6b161b31 2616
03a93dbb
JF
2617 case REQ_VIEW_NEXT:
2618 {
9f41488f 2619 int nviews = displayed_views();
03a93dbb
JF
2620 int next_view = (current_view + 1) % nviews;
2621
2622 if (next_view == current_view) {
2623 report("Only one view is displayed");
2624 break;
2625 }
2626
2627 current_view = next_view;
2628 /* Blur out the title of the previous view. */
2629 update_view_title(view);
6734f6b9 2630 report("");
03a93dbb
JF
2631 break;
2632 }
acaef3b3
JF
2633 case REQ_REFRESH:
2634 report("Refreshing is not yet supported for the %s view", view->name);
2635 break;
2636
47dd652b
JF
2637 case REQ_MAXIMIZE:
2638 if (displayed_views() == 2)
2639 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2640 break;
2641
24b5b3e0 2642 case REQ_TOGGLE_LINENO:
b76c2afc 2643 opt_line_number = !opt_line_number;
d79f1577 2644 redraw_view(view);
b801d8b2
JF
2645 break;
2646
823057f4
DV
2647 case REQ_TOGGLE_DATE:
2648 opt_date = !opt_date;
d79f1577 2649 redraw_view(view);
823057f4
DV
2650 break;
2651
2652 case REQ_TOGGLE_AUTHOR:
2653 opt_author = !opt_author;
d79f1577 2654 redraw_view(view);
823057f4
DV
2655 break;
2656
54efb62b
JF
2657 case REQ_TOGGLE_REV_GRAPH:
2658 opt_rev_graph = !opt_rev_graph;
d79f1577 2659 redraw_view(view);
54efb62b
JF
2660 break;
2661
823057f4
DV
2662 case REQ_TOGGLE_REFS:
2663 opt_show_refs = !opt_show_refs;
d79f1577 2664 redraw_view(view);
823057f4
DV
2665 break;
2666
03a93dbb 2667 case REQ_PROMPT:
8855ada4 2668 /* Always reload^Wrerun commands from the prompt. */
49f2b43f 2669 open_view(view, opt_request, OPEN_RELOAD);
03a93dbb
JF
2670 break;
2671
4af34daa
JF
2672 case REQ_SEARCH:
2673 case REQ_SEARCH_BACK:
c02d8fce 2674 search_view(view, request);
4af34daa
JF
2675 break;
2676
2677 case REQ_FIND_NEXT:
2678 case REQ_FIND_PREV:
2679 find_next(view, request);
2680 break;
2681
4a2909a7 2682 case REQ_STOP_LOADING:
59a45d3a
JF
2683 for (i = 0; i < ARRAY_SIZE(views); i++) {
2684 view = &views[i];
2e8488b4 2685 if (view->pipe)
6a7bb912 2686 report("Stopped loading the %s view", view->name),
03a93dbb
JF
2687 end_update(view);
2688 }
b801d8b2
JF
2689 break;
2690
4a2909a7 2691 case REQ_SHOW_VERSION:
ec31d0d0 2692 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
b801d8b2
JF
2693 return TRUE;
2694
fac7db6c
JF
2695 case REQ_SCREEN_RESIZE:
2696 resize_display();
2697 /* Fall-through */
4a2909a7 2698 case REQ_SCREEN_REDRAW:
20bb5e18 2699 redraw_display();
4a2909a7
JF
2700 break;
2701
0cea0d43
JF
2702 case REQ_EDIT:
2703 report("Nothing to edit");
531c6c69
JF
2704 break;
2705
226da94b 2706
531c6c69
JF
2707 case REQ_ENTER:
2708 report("Nothing to enter");
2709 break;
ca1d71ea 2710
b801d8b2 2711
4f9b667a 2712 case REQ_VIEW_CLOSE:
2fcf5401
JF
2713 /* XXX: Mark closed views by letting view->parent point to the
2714 * view itself. Parents to closed view should never be
2715 * followed. */
2716 if (view->parent &&
2717 view->parent->parent != view->parent) {
4f9b667a
JF
2718 memset(display, 0, sizeof(display));
2719 current_view = 0;
f6da0b66 2720 display[current_view] = view->parent;
2fcf5401 2721 view->parent = view;
4f9b667a
JF
2722 resize_display();
2723 redraw_display();
2724 break;
2725 }
2726 /* Fall-through */
b801d8b2
JF
2727 case REQ_QUIT:
2728 return FALSE;
2729
2730 default:
2e8488b4 2731 /* An unknown key will show most commonly used commands. */
468876c9 2732 report("Unknown key, press 'h' for help");
b801d8b2
JF
2733 return TRUE;
2734 }
2735
2736 return TRUE;
2737}
2738
2739
2740/*
ff26aa29 2741 * Pager backend
b801d8b2
JF
2742 */
2743
6b161b31 2744static bool
5dcf8064 2745pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
b801d8b2 2746{
f4de14c5 2747 static char spaces[] = " ";
fe7233c3
JF
2748 char *text = line->data;
2749 enum line_type type = line->type;
f4de14c5
JF
2750 int attr = A_NORMAL;
2751 int col = 0;
b801d8b2 2752
6706b2ba
JF
2753 wmove(view->win, lineno, 0);
2754
5dcf8064 2755 if (selected) {
78c70acd 2756 type = LINE_CURSOR;
6706b2ba 2757 wchgat(view->win, -1, 0, type, NULL);
f4de14c5 2758 attr = get_line_attr(type);
fd85fef1 2759 }
b801d8b2 2760 wattrset(view->win, attr);
b76c2afc 2761
f4de14c5
JF
2762 if (opt_line_number) {
2763 col += draw_lineno(view, lineno, view->width, selected);
2764 if (col >= view->width)
2765 return TRUE;
2766 }
8855ada4 2767
f4de14c5
JF
2768 if (!selected) {
2769 attr = get_line_attr(type);
2770 wattrset(view->win, attr);
2771 }
2772 if (opt_tab_size < TABSIZE) {
2773 int col_offset = col;
8855ada4 2774
f4de14c5 2775 col = 0;
fe7233c3 2776 while (text && col_offset + col < view->width) {
6706b2ba 2777 int cols_max = view->width - col_offset - col;
fe7233c3 2778 char *pos = text;
6706b2ba 2779 int cols;
4c6fabc2 2780
fe7233c3
JF
2781 if (*text == '\t') {
2782 text++;
6706b2ba 2783 assert(sizeof(spaces) > TABSIZE);
fe7233c3 2784 pos = spaces;
6706b2ba 2785 cols = opt_tab_size - (col % opt_tab_size);
82e78006 2786
b76c2afc 2787 } else {
fe7233c3
JF
2788 text = strchr(text, '\t');
2789 cols = line ? text - pos : strlen(pos);
b76c2afc 2790 }
6706b2ba 2791
fe7233c3 2792 waddnstr(view->win, pos, MIN(cols, cols_max));
6706b2ba 2793 col += cols;
b76c2afc 2794 }
b76c2afc
JF
2795
2796 } else {
f4de14c5 2797 draw_text(view, text, view->width - col, TRUE, selected);
6706b2ba 2798 }
2e8488b4 2799
b801d8b2
JF
2800 return TRUE;
2801}
2802
dc23c0e3 2803static bool
d65ced0d 2804add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
dc23c0e3 2805{
17482b11 2806 char refbuf[SIZEOF_STR];
dc23c0e3
JF
2807 char *ref = NULL;
2808 FILE *pipe;
2809
d3c345f7 2810 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
dc23c0e3
JF
2811 return TRUE;
2812
2813 pipe = popen(refbuf, "r");
2814 if (!pipe)
2815 return TRUE;
2816
2817 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2818 ref = chomp_string(ref);
2819 pclose(pipe);
2820
2821 if (!ref || !*ref)
2822 return TRUE;
2823
2824 /* This is the only fatal call, since it can "corrupt" the buffer. */
17482b11 2825 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
dc23c0e3
JF
2826 return FALSE;
2827
2828 return TRUE;
2829}
2830
7b99a34c
JF
2831static void
2832add_pager_refs(struct view *view, struct line *line)
2833{
17482b11 2834 char buf[SIZEOF_STR];
9295982a 2835 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
7b99a34c 2836 struct ref **refs;
d65ced0d 2837 size_t bufpos = 0, refpos = 0;
7b99a34c 2838 const char *sep = "Refs: ";
dc23c0e3 2839 bool is_tag = FALSE;
7b99a34c
JF
2840
2841 assert(line->type == LINE_COMMIT);
2842
c9ca1ec3 2843 refs = get_refs(commit_id);
dc23c0e3
JF
2844 if (!refs) {
2845 if (view == VIEW(REQ_VIEW_DIFF))
2846 goto try_add_describe_ref;
7b99a34c 2847 return;
dc23c0e3 2848 }
7b99a34c
JF
2849
2850 do {
cc2d1364 2851 struct ref *ref = refs[refpos];
e15ec88e
JF
2852 char *fmt = ref->tag ? "%s[%s]" :
2853 ref->remote ? "%s<%s>" : "%s%s";
7b99a34c 2854
cc2d1364
JF
2855 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2856 return;
7b99a34c 2857 sep = ", ";
dc23c0e3
JF
2858 if (ref->tag)
2859 is_tag = TRUE;
7b99a34c
JF
2860 } while (refs[refpos++]->next);
2861
dc23c0e3
JF
2862 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2863try_add_describe_ref:
d42c8a35 2864 /* Add <tag>-g<commit_id> "fake" reference. */
dc23c0e3
JF
2865 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2866 return;
2867 }
2868
d42c8a35
JF
2869 if (bufpos == 0)
2870 return;
2871
cc2d1364 2872 if (!realloc_lines(view, view->line_size + 1))
7b99a34c
JF
2873 return;
2874
0a0d8910 2875 add_line_text(view, buf, LINE_PP_REFS);
7b99a34c
JF
2876}
2877
6b161b31 2878static bool
701e4f5d 2879pager_read(struct view *view, char *data)
22f66b0a 2880{
0a0d8910 2881 struct line *line;
22f66b0a 2882
be04d936
JF
2883 if (!data)
2884 return TRUE;
2885
0a0d8910
JF
2886 line = add_line_text(view, data, get_line_type(data));
2887 if (!line)
7b99a34c 2888 return FALSE;
fe7233c3 2889
7b99a34c
JF
2890 if (line->type == LINE_COMMIT &&
2891 (view == VIEW(REQ_VIEW_DIFF) ||
2892 view == VIEW(REQ_VIEW_LOG)))
2893 add_pager_refs(view, line);
2894
22f66b0a
JF
2895 return TRUE;
2896}
2897
586c423d
JF
2898static enum request
2899pager_request(struct view *view, enum request request, struct line *line)
6b161b31 2900{
91e8e277 2901 int split = 0;
6b161b31 2902
586c423d
JF
2903 if (request != REQ_ENTER)
2904 return request;
2905
9fbbd28f
JF
2906 if (line->type == LINE_COMMIT &&
2907 (view == VIEW(REQ_VIEW_LOG) ||
2908 view == VIEW(REQ_VIEW_PAGER))) {
91e8e277
JF
2909 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2910 split = 1;
67e48ac5
JF
2911 }
2912
91e8e277
JF
2913 /* Always scroll the view even if it was split. That way
2914 * you can use Enter to scroll through the log view and
2915 * split open each commit diff. */
2916 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2917
2918 /* FIXME: A minor workaround. Scrolling the view will call report("")
9d82d824
JF
2919 * but if we are scrolling a non-current view this won't properly
2920 * update the view title. */
91e8e277
JF
2921 if (split)
2922 update_view_title(view);
6b161b31 2923
586c423d 2924 return REQ_NONE;
6b161b31
JF
2925}
2926
4af34daa
JF
2927static bool
2928pager_grep(struct view *view, struct line *line)
2929{
2930 regmatch_t pmatch;
2931 char *text = line->data;
2932
2933 if (!*text)
2934 return FALSE;
2935
b77b2cb8 2936 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
4af34daa
JF
2937 return FALSE;
2938
2939 return TRUE;
2940}
2941
d720de4b
JF
2942static void
2943pager_select(struct view *view, struct line *line)
2944{
2945 if (line->type == LINE_COMMIT) {
9295982a 2946 char *text = (char *)line->data + STRING_SIZE("commit ");
d720de4b 2947
035ba11f 2948 if (view != VIEW(REQ_VIEW_PAGER))
2463b4ea
JF
2949 string_copy_rev(view->ref, text);
2950 string_copy_rev(ref_commit, text);
d720de4b
JF
2951 }
2952}
2953
6b161b31 2954static struct view_ops pager_ops = {
6734f6b9 2955 "line",
f098944b 2956 NULL,
6b161b31 2957 pager_read,
f098944b 2958 pager_draw,
586c423d 2959 pager_request,
f098944b
JF
2960 pager_grep,
2961 pager_select,
2962};
2963
2964
2965/*
2966 * Help backend
2967 */
2968
2969static bool
2970help_open(struct view *view)
2971{
2972 char buf[BUFSIZ];
2973 int lines = ARRAY_SIZE(req_info) + 2;
2974 int i;
2975
2976 if (view->lines > 0)
2977 return TRUE;
2978
2979 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2980 if (!req_info[i].request)
2981 lines++;
2982
9eb14b72
JF
2983 lines += run_requests + 1;
2984
f098944b
JF
2985 view->line = calloc(lines, sizeof(*view->line));
2986 if (!view->line)
2987 return FALSE;
2988
2989 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2990
2991 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2992 char *key;
2993
0e4360b6
JF
2994 if (req_info[i].request == REQ_NONE)
2995 continue;
2996
f098944b
JF
2997 if (!req_info[i].request) {
2998 add_line_text(view, "", LINE_DEFAULT);
2999 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3000 continue;
3001 }
3002
3003 key = get_key(req_info[i].request);
0e4360b6
JF
3004 if (!*key)
3005 key = "(no key defined)";
3006
f098944b
JF
3007 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3008 continue;
3009
3010 add_line_text(view, buf, LINE_DEFAULT);
3011 }
3012
9eb14b72
JF
3013 if (run_requests) {
3014 add_line_text(view, "", LINE_DEFAULT);
3015 add_line_text(view, "External commands:", LINE_DEFAULT);
3016 }
3017
3018 for (i = 0; i < run_requests; i++) {
3019 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3020 char *key;
3021
3022 if (!req)
3023 continue;
3024
3025 key = get_key_name(req->key);
3026 if (!*key)
3027 key = "(no key defined)";
3028
3029 if (!string_format(buf, " %-10s %-14s `%s`",
3030 keymap_table[req->keymap].name,
3031 key, req->cmd))
3032 continue;
3033
3034 add_line_text(view, buf, LINE_DEFAULT);
3035 }
3036
f098944b
JF
3037 return TRUE;
3038}
3039
3040static struct view_ops help_ops = {
3041 "line",
3042 help_open,
3043 NULL,
3044 pager_draw,
586c423d 3045 pager_request,
4af34daa 3046 pager_grep,
d720de4b 3047 pager_select,
6b161b31
JF
3048};
3049
80ce96ea 3050
ff26aa29 3051/*
e733ee54
JF
3052 * Tree backend
3053 */
3054
69efc854
JF
3055struct tree_stack_entry {
3056 struct tree_stack_entry *prev; /* Entry below this in the stack */
3057 unsigned long lineno; /* Line number to restore */
3058 char *name; /* Position of name in opt_path */
3059};
3060
3061/* The top of the path stack. */
3062static struct tree_stack_entry *tree_stack = NULL;
3063unsigned long tree_lineno = 0;
3064
3065static void
3066pop_tree_stack_entry(void)
3067{
3068 struct tree_stack_entry *entry = tree_stack;
3069
3070 tree_lineno = entry->lineno;
3071 entry->name[0] = 0;
3072 tree_stack = entry->prev;
3073 free(entry);
3074}
3075
3076static void
3077push_tree_stack_entry(char *name, unsigned long lineno)
3078{
3079 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3080 size_t pathlen = strlen(opt_path);
3081
3082 if (!entry)
3083 return;
3084
3085 entry->prev = tree_stack;
3086 entry->name = opt_path + pathlen;
3087 tree_stack = entry;
3088
3089 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3090 pop_tree_stack_entry();
3091 return;
3092 }
3093
3094 /* Move the current line to the first tree entry. */
3095 tree_lineno = 1;
3096 entry->lineno = lineno;
3097}
3098
4795d620 3099/* Parse output from git-ls-tree(1):
e733ee54
JF
3100 *
3101 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3102 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3103 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3104 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3105 */
3106
3107#define SIZEOF_TREE_ATTR \
3108 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3109
3110#define TREE_UP_FORMAT "040000 tree %s\t.."
3111
3112static int
3113tree_compare_entry(enum line_type type1, char *name1,
3114 enum line_type type2, char *name2)
3115{
3116 if (type1 != type2) {
3117 if (type1 == LINE_TREE_DIR)
3118 return -1;
3119 return 1;
3120 }
3121
3122 return strcmp(name1, name2);
3123}
3124
a2d5d9ef
JF
3125static char *
3126tree_path(struct line *line)
3127{
3128 char *path = line->data;
3129
3130 return path + SIZEOF_TREE_ATTR;
3131}
3132
e733ee54
JF
3133static bool
3134tree_read(struct view *view, char *text)
3135{
be04d936 3136 size_t textlen = text ? strlen(text) : 0;
e733ee54
JF
3137 char buf[SIZEOF_STR];
3138 unsigned long pos;
3139 enum line_type type;
f88a5319 3140 bool first_read = view->lines == 0;
e733ee54 3141
4ed67514
JF
3142 if (!text)
3143 return TRUE;
e733ee54
JF
3144 if (textlen <= SIZEOF_TREE_ATTR)
3145 return FALSE;
3146
3147 type = text[STRING_SIZE("100644 ")] == 't'
3148 ? LINE_TREE_DIR : LINE_TREE_FILE;
3149
f88a5319 3150 if (first_read) {
e733ee54 3151 /* Add path info line */
0a0d8910
JF
3152 if (!string_format(buf, "Directory path /%s", opt_path) ||
3153 !realloc_lines(view, view->line_size + 1) ||
3154 !add_line_text(view, buf, LINE_DEFAULT))
e733ee54
JF
3155 return FALSE;
3156
3157 /* Insert "link" to parent directory. */
0a0d8910
JF
3158 if (*opt_path) {
3159 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3160 !realloc_lines(view, view->line_size + 1) ||
3161 !add_line_text(view, buf, LINE_TREE_DIR))
3162 return FALSE;
3163 }
e733ee54
JF
3164 }
3165
3166 /* Strip the path part ... */
3167 if (*opt_path) {
3168 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3169 size_t striplen = strlen(opt_path);
3170 char *path = text + SIZEOF_TREE_ATTR;
3171
3172 if (pathlen > striplen)
3173 memmove(path, path + striplen,
3174 pathlen - striplen + 1);
3175 }
3176
3177 /* Skip "Directory ..." and ".." line. */
3178 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3179 struct line *line = &view->line[pos];
a2d5d9ef 3180 char *path1 = tree_path(line);
e733ee54
JF
3181 char *path2 = text + SIZEOF_TREE_ATTR;
3182 int cmp = tree_compare_entry(line->type, path1, type, path2);
3183
3184 if (cmp <= 0)
3185 continue;
3186
3187 text = strdup(text);
3188 if (!text)
3189 return FALSE;
3190
3191 if (view->lines > pos)
3192 memmove(&view->line[pos + 1], &view->line[pos],
3193 (view->lines - pos) * sizeof(*line));
3194
3195 line = &view->line[pos];
3196 line->data = text;
3197 line->type = type;
3198 view->lines++;
3199 return TRUE;
3200 }
3201
0a0d8910 3202 if (!add_line_text(view, text, type))
e733ee54
JF
3203 return FALSE;
3204
69efc854
JF
3205 if (tree_lineno > view->lineno) {
3206 view->lineno = tree_lineno;
3207 tree_lineno = 0;
3208 }
f88a5319 3209
e733ee54
JF
3210 return TRUE;
3211}
3212
586c423d
JF
3213static enum request
3214tree_request(struct view *view, enum request request, struct line *line)
e733ee54 3215{
aac64c17 3216 enum open_flags flags;
586c423d 3217
a2d5d9ef
JF
3218 if (request == REQ_VIEW_BLAME) {
3219 char *filename = tree_path(line);
3220
3221 if (line->type == LINE_TREE_DIR) {
3222 report("Cannot show blame for directory %s", opt_path);
3223 return REQ_NONE;
3224 }
3225
8f298f3e
JF
3226 string_copy(opt_ref, view->vid);
3227 string_format(opt_file, "%s%s", opt_path, filename);
a2d5d9ef
JF
3228 return request;
3229 }
c509eed2
DV
3230 if (request == REQ_TREE_PARENT) {
3231 if (*opt_path) {
3232 /* fake 'cd ..' */
3233 request = REQ_ENTER;
3234 line = &view->line[1];
3235 } else {
3236 /* quit view if at top of tree */
3237 return REQ_VIEW_CLOSE;
3238 }
3239 }
586c423d
JF
3240 if (request != REQ_ENTER)
3241 return request;
e733ee54 3242
69efc854
JF
3243 /* Cleanup the stack if the tree view is at a different tree. */
3244 while (!*opt_path && tree_stack)
3245 pop_tree_stack_entry();
3246
e733ee54
JF
3247 switch (line->type) {
3248 case LINE_TREE_DIR:
3249 /* Depending on whether it is a subdir or parent (updir?) link
3250 * mangle the path buffer. */
3251 if (line == &view->line[1] && *opt_path) {
69efc854 3252 pop_tree_stack_entry();
e733ee54
JF
3253
3254 } else {
a2d5d9ef 3255 char *basename = tree_path(line);
e733ee54 3256
69efc854 3257 push_tree_stack_entry(basename, view->lineno);
e733ee54
JF
3258 }
3259
3260 /* Trees and subtrees share the same ID, so they are not not
3261 * unique like blobs. */
aac64c17 3262 flags = OPEN_RELOAD;
e733ee54
JF
3263 request = REQ_VIEW_TREE;
3264 break;
3265
3266 case LINE_TREE_FILE:
aac64c17 3267 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
e733ee54
JF
3268 request = REQ_VIEW_BLOB;
3269 break;
3270
3271 default:
3272 return TRUE;
3273 }
3274
3275 open_view(view, request, flags);
69efc854
JF
3276 if (request == REQ_VIEW_TREE) {
3277 view->lineno = tree_lineno;
3278 }
e733ee54 3279
586c423d 3280 return REQ_NONE;
e733ee54
JF
3281}
3282
d720de4b
JF
3283static void
3284tree_select(struct view *view, struct line *line)
3285{
9295982a 3286 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
73c76ef5
JF
3287
3288 if (line->type == LINE_TREE_FILE) {
2463b4ea 3289 string_copy_rev(ref_blob, text);
d720de4b 3290
ebbaf4fe
JF
3291 } else if (line->type != LINE_TREE_DIR) {
3292 return;
d720de4b 3293 }
ebbaf4fe 3294
2463b4ea 3295 string_copy_rev(view->ref, text);
d720de4b
JF
3296}
3297
e733ee54
JF
3298static struct view_ops tree_ops = {
3299 "file",
f098944b 3300 NULL,
e733ee54 3301 tree_read,
f098944b 3302 pager_draw,
586c423d 3303 tree_request,
e733ee54 3304 pager_grep,
d720de4b 3305 tree_select,
e733ee54
JF
3306};
3307
3308static bool
3309blob_read(struct view *view, char *line)
3310{
a2d5d9ef
JF
3311 if (!line)
3312 return TRUE;
c115e7ac 3313 return add_line_text(view, line, LINE_DEFAULT) != NULL;
e733ee54
JF
3314}
3315
3316static struct view_ops blob_ops = {
3317 "line",
f098944b 3318 NULL,
e733ee54 3319 blob_read,
f098944b 3320 pager_draw,
586c423d 3321 pager_request,
e733ee54 3322 pager_grep,
d720de4b 3323 pager_select,
e733ee54
JF
3324};
3325
8a680988
JF
3326/*
3327 * Blame backend
3328 *
3329 * Loading the blame view is a two phase job:
3330 *
a2d5d9ef 3331 * 1. File content is read either using opt_file from the
8a680988
JF
3332 * filesystem or using git-cat-file.
3333 * 2. Then blame information is incrementally added by
3334 * reading output from git-blame.
3335 */
3336
3337struct blame_commit {
3338 char id[SIZEOF_REV]; /* SHA1 ID. */
3339 char title[128]; /* First line of the commit message. */
3340 char author[75]; /* Author of the commit. */
3341 struct tm time; /* Date from the author ident. */
3342 char filename[128]; /* Name of file. */
3343};
3344
3345struct blame {
3346 struct blame_commit *commit;
3347 unsigned int header:1;
3348 char text[1];
3349};
3350
3351#define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3352#define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3353
3354static bool
3355blame_open(struct view *view)
3356{
3357 char path[SIZEOF_STR];
3358 char ref[SIZEOF_STR] = "";
3359
a2d5d9ef 3360 if (sq_quote(path, 0, opt_file) >= sizeof(path))
8a680988
JF
3361 return FALSE;
3362
3363 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3364 return FALSE;
3365
3366 if (*opt_ref) {
3367 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3368 return FALSE;
3369 } else {
a2d5d9ef 3370 view->pipe = fopen(opt_file, "r");
8a680988
JF
3371 if (!view->pipe &&
3372 !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3373 return FALSE;
3374 }
3375
3376 if (!view->pipe)
3377 view->pipe = popen(view->cmd, "r");
3378 if (!view->pipe)
3379 return FALSE;
3380
3381 if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3382 return FALSE;
3383
a2d5d9ef
JF
3384 string_format(view->ref, "%s ...", opt_file);
3385 string_copy_rev(view->vid, opt_file);
8a680988
JF
3386 set_nonblocking_input(TRUE);
3387
3388 if (view->line) {
3389 int i;
3390
3391 for (i = 0; i < view->lines; i++)
3392 free(view->line[i].data);
3393 free(view->line);
3394 }
3395
3396 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3397 view->offset = view->lines = view->lineno = 0;
3398 view->line = NULL;
3399 view->start_time = time(NULL);
442cbee3
JF
3400
3401 return TRUE;
8a680988
JF
3402}
3403
3404static struct blame_commit *
3405get_blame_commit(struct view *view, const char *id)
3406{
3407 size_t i;
3408
3409 for (i = 0; i < view->lines; i++) {
3410 struct blame *blame = view->line[i].data;
3411
3412 if (!blame->commit)
3413 continue;
3414
3415 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3416 return blame->commit;
3417 }
3418
3419 {
3420 struct blame_commit *commit = calloc(1, sizeof(*commit));
3421
3422 if (commit)
3423 string_ncopy(commit->id, id, SIZEOF_REV);
3424 return commit;
3425 }
3426}
3427
3428static bool
3429parse_number(char **posref, size_t *number, size_t min, size_t max)
3430{
3431 char *pos = *posref;
3432
3433 *posref = NULL;
3434 pos = strchr(pos + 1, ' ');
3435 if (!pos || !isdigit(pos[1]))
3436 return FALSE;
3437 *number = atoi(pos + 1);
3438 if (*number < min || *number > max)
3439 return FALSE;
3440
3441 *posref = pos;
3442 return TRUE;
3443}
3444
3445static struct blame_commit *
3446parse_blame_commit(struct view *view, char *text, int *blamed)
3447{
3448 struct blame_commit *commit;
3449 struct blame *blame;
3450 char *pos = text + SIZEOF_REV - 1;
3451 size_t lineno;
3452 size_t group;
8a680988
JF
3453
3454 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3455 return NULL;
3456
3457 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3458 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3459 return NULL;
3460
3461 commit = get_blame_commit(view, text);
3462 if (!commit)
3463 return NULL;
3464
3465 *blamed += group;
3466 while (group--) {
3467 struct line *line = &view->line[lineno + group - 1];
3468
3469 blame = line->data;
3470 blame->commit = commit;
76724178 3471 blame->header = !group;
8a680988
JF
3472 line->dirty = 1;
3473 }
8a680988
JF
3474
3475 return commit;
3476}
3477
3478static bool
3479blame_read_file(struct view *view, char *line)
3480{
3481 if (!line) {
3482 FILE *pipe = NULL;
3483
3484 if (view->lines > 0)
3485 pipe = popen(view->cmd, "r");
3486 view->cmd[0] = 0;
3487 if (!pipe) {
3488 report("Failed to load blame data");
3489 return TRUE;
3490 }
3491
3492 fclose(view->pipe);
3493 view->pipe = pipe;
3494 return FALSE;
3495
3496 } else {
3497 size_t linelen = strlen(line);
3498 struct blame *blame = malloc(sizeof(*blame) + linelen);
3499
3500 if (!line)
3501 return FALSE;
3502
3503 blame->commit = NULL;
3504 strncpy(blame->text, line, linelen);
3505 blame->text[linelen] = 0;
3506 return add_line_data(view, blame, LINE_BLAME_COMMIT) != NULL;
3507 }
3508}
3509
3510static bool
3511match_blame_header(const char *name, char **line)
3512{
3513 size_t namelen = strlen(name);
3514 bool matched = !strncmp(name, *line, namelen);
3515
3516 if (matched)
3517 *line += namelen;
3518
3519 return matched;
3520}
3521
3522static bool
3523blame_read(struct view *view, char *line)
3524{
3525 static struct blame_commit *commit = NULL;
3526 static int blamed = 0;
3527 static time_t author_time;
3528
3529 if (*view->cmd)
3530 return blame_read_file(view, line);
3531
3532 if (!line) {
3533 /* Reset all! */
3534 commit = NULL;
3535 blamed = 0;
3536 string_format(view->ref, "%s", view->vid);
3537 if (view_is_displayed(view)) {
3538 update_view_title(view);
3539 redraw_view_from(view, 0);
3540 }
3541 return TRUE;
3542 }
3543
3544 if (!commit) {
3545 commit = parse_blame_commit(view, line, &blamed);
3546 string_format(view->ref, "%s %2d%%", view->vid,
3547 blamed * 100 / view->lines);
3548
3549 } else if (match_blame_header("author ", &line)) {
3550 string_ncopy(commit->author, line, strlen(line));
3551
3552 } else if (match_blame_header("author-time ", &line)) {
3553 author_time = (time_t) atol(line);
3554
3555 } else if (match_blame_header("author-tz ", &line)) {
3556 long tz;
3557
3558 tz = ('0' - line[1]) * 60 * 60 * 10;
3559 tz += ('0' - line[2]) * 60 * 60;
3560 tz += ('0' - line[3]) * 60;
3561 tz += ('0' - line[4]) * 60;
3562
3563 if (line[0] == '-')
3564 tz = -tz;
3565
3566 author_time -= tz;
3567 gmtime_r(&author_time, &commit->time);
3568
3569 } else if (match_blame_header("summary ", &line)) {
3570 string_ncopy(commit->title, line, strlen(line));
3571
3572 } else if (match_blame_header("filename ", &line)) {
3573 string_ncopy(commit->filename, line, strlen(line));
3574 commit = NULL;
3575 }
3576
3577 return TRUE;
3578}
3579
3580static bool
3581blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3582{
8a680988
JF
3583 struct blame *blame = line->data;
3584 int col = 0;
3585
3586 wmove(view->win, lineno, 0);
3587
3588 if (selected) {
3589 wattrset(view->win, get_line_attr(LINE_CURSOR));
3590 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3591 } else {
3592 wattrset(view->win, A_NORMAL);
8a680988
JF
3593 }
3594
3595 if (opt_date) {
3596 int n;
3597
3598 if (!selected)
3599 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3600 if (blame->commit) {
3601 char buf[DATE_COLS + 1];
3602 int timelen;
3603
3604 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &blame->commit->time);
de46362f
JF
3605 n = draw_text(view, buf, view->width - col, FALSE, selected);
3606 draw_text(view, " ", view->width - col - n, FALSE, selected);
8a680988
JF
3607 }
3608
3609 col += DATE_COLS;
3610 wmove(view->win, lineno, col);
3611 if (col >= view->width)
3612 return TRUE;
3613 }
3614
3615 if (opt_author) {
3616 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3617
3618 if (!selected)
3619 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3620 if (blame->commit)
de46362f 3621 draw_text(view, blame->commit->author, max, TRUE, selected);
8a680988
JF
3622 col += AUTHOR_COLS;
3623 if (col >= view->width)
3624 return TRUE;
3625 wmove(view->win, lineno, col);
3626 }
3627
3628 {
3629 int max = MIN(ID_COLS - 1, view->width - col);
3630
3631 if (!selected)
3632 wattrset(view->win, get_line_attr(LINE_BLAME_ID));
3633 if (blame->commit)
3634 draw_text(view, blame->commit->id, max, FALSE, -1);
3635 col += ID_COLS;
3636 if (col >= view->width)
3637 return TRUE;
3638 wmove(view->win, lineno, col);
3639 }
3640
3641 {
f4de14c5 3642 col += draw_lineno(view, lineno, view->width - col, selected);
8a680988
JF
3643 if (col >= view->width)
3644 return TRUE;
3645 }
3646
de46362f 3647 col += draw_text(view, blame->text, view->width - col, TRUE, selected);
8a680988
JF
3648
3649 return TRUE;
3650}
3651
3652static enum request
3653blame_request(struct view *view, enum request request, struct line *line)
3654{
3655 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3656 struct blame *blame = line->data;
3657
3658 switch (request) {
3659 case REQ_ENTER:
3660 if (!blame->commit) {
3661 report("No commit loaded yet");
3662 break;
3663 }
3664
22d9b77c 3665 if (!strcmp(blame->commit->id, NULL_ID)) {
8a680988
JF
3666 char path[SIZEOF_STR];
3667
3668 if (sq_quote(path, 0, view->vid) >= sizeof(path))
3669 break;
3670 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3671 }
3672
3673 open_view(view, REQ_VIEW_DIFF, flags);
3674 break;
3675
3676 default:
3677 return request;
3678 }
3679
3680 return REQ_NONE;
3681}
3682
3683static bool
3684blame_grep(struct view *view, struct line *line)
3685{
3686 struct blame *blame = line->data;
3687 struct blame_commit *commit = blame->commit;
3688 regmatch_t pmatch;
3689
3690#define MATCH(text) \
3691 (*text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3692
3693 if (commit) {
3694 char buf[DATE_COLS + 1];
3695
3696 if (MATCH(commit->title) ||
3697 MATCH(commit->author) ||
3698 MATCH(commit->id))
3699 return TRUE;
3700
3701 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3702 MATCH(buf))
3703 return TRUE;
3704 }
3705
3706 return MATCH(blame->text);
3707
3708#undef MATCH
3709}
3710
3711static void
3712blame_select(struct view *view, struct line *line)
3713{
3714 struct blame *blame = line->data;
3715 struct blame_commit *commit = blame->commit;
3716
3717 if (!commit)
3718 return;
3719
22d9b77c 3720 if (!strcmp(commit->id, NULL_ID))
8a680988
JF
3721 string_ncopy(ref_commit, "HEAD", 4);
3722 else
3723 string_copy_rev(ref_commit, commit->id);
3724}
3725
3726static struct view_ops blame_ops = {
3727 "line",
3728 blame_open,
3729 blame_read,
3730 blame_draw,
3731 blame_request,
3732 blame_grep,
3733 blame_select,
3734};
e733ee54
JF
3735
3736/*
173d76ea
JF
3737 * Status backend
3738 */
3739
3740struct status {
3741 char status;
3742 struct {
3743 mode_t mode;
3744 char rev[SIZEOF_REV];
5ba030cf 3745 char name[SIZEOF_STR];
173d76ea
JF
3746 } old;
3747 struct {
3748 mode_t mode;
3749 char rev[SIZEOF_REV];
5ba030cf 3750 char name[SIZEOF_STR];
173d76ea 3751 } new;
173d76ea
JF
3752};
3753
f5a5e640 3754static char status_onbranch[SIZEOF_STR];
b33611d8
JF
3755static struct status stage_status;
3756static enum line_type stage_line_type;
3757
173d76ea
JF
3758/* Get fields from the diff line:
3759 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3760 */
3761static inline bool
3762status_get_diff(struct status *file, char *buf, size_t bufsize)
3763{
3764 char *old_mode = buf + 1;
3765 char *new_mode = buf + 8;
3766 char *old_rev = buf + 15;
3767 char *new_rev = buf + 56;
3768 char *status = buf + 97;
3769
5ba030cf 3770 if (bufsize < 99 ||
173d76ea
JF
3771 old_mode[-1] != ':' ||
3772 new_mode[-1] != ' ' ||
3773 old_rev[-1] != ' ' ||
3774 new_rev[-1] != ' ' ||
3775 status[-1] != ' ')
3776 return FALSE;
3777
3778 file->status = *status;
3779
3780 string_copy_rev(file->old.rev, old_rev);
3781 string_copy_rev(file->new.rev, new_rev);
3782
3783 file->old.mode = strtoul(old_mode, NULL, 8);
3784 file->new.mode = strtoul(new_mode, NULL, 8);
3785
5ba030cf 3786 file->old.name[0] = file->new.name[0] = 0;
173d76ea
JF
3787
3788 return TRUE;
3789}
3790
3791static bool
22d9b77c 3792status_run(struct view *view, const char cmd[], char status, enum line_type type)
173d76ea
JF
3793{
3794 struct status *file = NULL;
b5c18d9d 3795 struct status *unmerged = NULL;
173d76ea
JF
3796 char buf[SIZEOF_STR * 4];
3797 size_t bufsize = 0;
3798 FILE *pipe;
3799
3800 pipe = popen(cmd, "r");
3801 if (!pipe)
3802 return FALSE;
3803
3804 add_line_data(view, NULL, type);
3805
3806 while (!feof(pipe) && !ferror(pipe)) {
3807 char *sep;
3808 size_t readsize;
3809
3810 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3811 if (!readsize)
3812 break;
3813 bufsize += readsize;
3814
3815 /* Process while we have NUL chars. */
3816 while ((sep = memchr(buf, 0, bufsize))) {
3817 size_t sepsize = sep - buf + 1;
3818
3819 if (!file) {
3820 if (!realloc_lines(view, view->line_size + 1))
3821 goto error_out;
3822
3823 file = calloc(1, sizeof(*file));
3824 if (!file)
3825 goto error_out;
3826
3827 add_line_data(view, file, type);
3828 }
3829
3830 /* Parse diff info part. */
22d9b77c
JF
3831 if (status) {
3832 file->status = status;
3833 if (status == 'A')
3834 string_copy(file->old.rev, NULL_ID);
173d76ea
JF
3835
3836 } else if (!file->status) {
3837 if (!status_get_diff(file, buf, sepsize))
3838 goto error_out;
3839
3840 bufsize -= sepsize;
3841 memmove(buf, sep + 1, bufsize);
3842
3843 sep = memchr(buf, 0, bufsize);
3844 if (!sep)
3845 break;
3846 sepsize = sep - buf + 1;
b5c18d9d
JF
3847
3848 /* Collapse all 'M'odified entries that
3849 * follow a associated 'U'nmerged entry.
3850 */
3851 if (file->status == 'U') {
3852 unmerged = file;
3853
3854 } else if (unmerged) {
5ba030cf 3855 int collapse = !strcmp(buf, unmerged->new.name);
b5c18d9d
JF
3856
3857 unmerged = NULL;
3858 if (collapse) {
3859 free(file);
3860 view->lines--;
3861 continue;
3862 }
3863 }
173d76ea
JF
3864 }
3865
5ba030cf
JF
3866 /* Grab the old name for rename/copy. */
3867 if (!*file->old.name &&
3868 (file->status == 'R' || file->status == 'C')) {
3869 sepsize = sep - buf + 1;
3870 string_ncopy(file->old.name, buf, sepsize);
3871 bufsize -= sepsize;
3872 memmove(buf, sep + 1, bufsize);
3873
3874 sep = memchr(buf, 0, bufsize);
3875 if (!sep)
3876 break;
3877 sepsize = sep - buf + 1;
3878 }
3879
173d76ea
JF
3880 /* git-ls-files just delivers a NUL separated
3881 * list of file names similar to the second half
3882 * of the git-diff-* output. */
5ba030cf
JF
3883 string_ncopy(file->new.name, buf, sepsize);
3884 if (!*file->old.name)
3885 string_copy(file->old.name, file->new.name);
173d76ea
JF
3886 bufsize -= sepsize;
3887 memmove(buf, sep + 1, bufsize);
3888 file = NULL;
3889 }
3890 }
3891
3892 if (ferror(pipe)) {
3893error_out:
3894 pclose(pipe);
3895 return FALSE;
3896 }
3897
3898 if (!view->line[view->lines - 1].data)
3899 add_line_data(view, NULL, LINE_STAT_NONE);
3900
3901 pclose(pipe);
3902 return TRUE;
3903}
3904
b5c18d9d 3905/* Don't show unmerged entries in the staged section. */
5ba030cf 3906#define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
b1744cbe 3907#define STATUS_DIFF_FILES_CMD "git diff-files -z"
173d76ea 3908#define STATUS_LIST_OTHER_CMD \
810f0078 3909 "git ls-files -z --others --exclude-per-directory=.gitignore"
22d9b77c
JF
3910#define STATUS_LIST_NO_HEAD_CMD \
3911 "git ls-files -z --cached --exclude-per-directory=.gitignore"
173d76ea 3912
f99c6095 3913#define STATUS_DIFF_INDEX_SHOW_CMD \
5ba030cf 3914 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
f99c6095
JF
3915
3916#define STATUS_DIFF_FILES_SHOW_CMD \
5ba030cf 3917 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
89d917a2 3918
22d9b77c
JF
3919#define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3920 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3921
173d76ea
JF
3922/* First parse staged info using git-diff-index(1), then parse unstaged
3923 * info using git-diff-files(1), and finally untracked files using
3924 * git-ls-files(1). */
3925static bool
3926status_open(struct view *view)
3927{
810f0078
JF
3928 struct stat statbuf;
3929 char exclude[SIZEOF_STR];
22d9b77c
JF
3930 char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3931 char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
12e8c2be 3932 unsigned long prev_lineno = view->lineno;
22d9b77c 3933 char indexstatus = 0;
173d76ea
JF
3934 size_t i;
3935
3936 for (i = 0; i < view->lines; i++)
3937 free(view->line[i].data);
3938 free(view->line);
518234f1 3939 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
173d76ea
JF
3940 view->line = NULL;
3941
f5a5e640
JF
3942 if (!realloc_lines(view, view->line_size + 7))
3943 return FALSE;
3944
3945 add_line_data(view, NULL, LINE_STAT_HEAD);
22d9b77c
JF
3946 if (opt_no_head)
3947 string_copy(status_onbranch, "Initial commit");
3948 else if (!*opt_head)
f5a5e640
JF
3949 string_copy(status_onbranch, "Not currently on any branch");
3950 else if (!string_format(status_onbranch, "On branch %s", opt_head))
173d76ea
JF
3951 return FALSE;
3952
22d9b77c
JF
3953 if (opt_no_head) {
3954 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3955 indexstatus = 'A';
3956 }
3957
810f0078
JF
3958 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3959 return FALSE;
3960
810f0078 3961 if (stat(exclude, &statbuf) >= 0) {
22d9b77c
JF
3962 size_t cmdsize = strlen(othercmd);
3963
3964 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
3965 sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
3966 return FALSE;
810f0078 3967
22d9b77c
JF
3968 cmdsize = strlen(indexcmd);
3969 if (opt_no_head &&
3970 (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
3971 sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
810f0078
JF
3972 return FALSE;
3973 }
3974
b1744cbe
JF
3975 system("git update-index -q --refresh");
3976
22d9b77c
JF
3977 if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
3978 !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
3979 !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
173d76ea
JF
3980 return FALSE;
3981
12e8c2be 3982 /* If all went well restore the previous line number to stay in
f5a5e640
JF
3983 * the context or select a line with something that can be
3984 * updated. */
3985 if (prev_lineno >= view->lines)
3986 prev_lineno = view->lines - 1;
3987 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
3988 prev_lineno++;
31862819
JF
3989 while (prev_lineno > 0 && !view->line[prev_lineno].data)
3990 prev_lineno--;
f5a5e640
JF
3991
3992 /* If the above fails, always skip the "On branch" line. */
12e8c2be
JF
3993 if (prev_lineno < view->lines)
3994 view->lineno = prev_lineno;
3995 else
f5a5e640 3996 view->lineno = 1;
12e8c2be 3997
31862819
JF
3998 if (view->lineno < view->offset)
3999 view->offset = view->lineno;
4000 else if (view->offset + view->height <= view->lineno)
4001 view->offset = view->lineno - view->height + 1;
4002
173d76ea
JF
4003 return TRUE;
4004}
4005
4006static bool
4007status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4008{
4009 struct status *status = line->data;
f2cd17cd
JF
4010 char *text;
4011 int col = 0;
173d76ea
JF
4012
4013 wmove(view->win, lineno, 0);
4014
4015 if (selected) {
4016 wattrset(view->win, get_line_attr(LINE_CURSOR));
4017 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
4018
f5a5e640
JF
4019 } else if (line->type == LINE_STAT_HEAD) {
4020 wattrset(view->win, get_line_attr(LINE_STAT_HEAD));
4021 wchgat(view->win, -1, 0, LINE_STAT_HEAD, NULL);
4022
173d76ea
JF
4023 } else if (!status && line->type != LINE_STAT_NONE) {
4024 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
4025 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
4026
4027 } else {
4028 wattrset(view->win, get_line_attr(line->type));
4029 }
4030
4031 if (!status) {
173d76ea
JF
4032 switch (line->type) {
4033 case LINE_STAT_STAGED:
4034 text = "Changes to be committed:";
4035 break;
4036
4037 case LINE_STAT_UNSTAGED:
4038 text = "Changed but not updated:";
4039 break;
4040
4041 case LINE_STAT_UNTRACKED:
4042 text = "Untracked files:";
4043 break;
4044
4045 case LINE_STAT_NONE:
4046 text = " (no files)";
4047 break;
4048
f5a5e640
JF
4049 case LINE_STAT_HEAD:
4050 text = status_onbranch;
4051 break;
4052
173d76ea
JF
4053 default:
4054 return FALSE;
4055 }
f2cd17cd
JF
4056 } else {
4057 char buf[] = { status->status, ' ', ' ', ' ', 0 };
173d76ea 4058
f2cd17cd
JF
4059 col += draw_text(view, buf, view->width, TRUE, selected);
4060 if (!selected)
4061 wattrset(view->win, A_NORMAL);
4062 text = status->new.name;
173d76ea
JF
4063 }
4064
f2cd17cd 4065 draw_text(view, text, view->width - col, TRUE, selected);
173d76ea
JF
4066 return TRUE;
4067}
4068
586c423d 4069static enum request
88f66e2d 4070status_enter(struct view *view, struct line *line)
173d76ea 4071{
89d917a2 4072 struct status *status = line->data;
5ba030cf
JF
4073 char oldpath[SIZEOF_STR] = "";
4074 char newpath[SIZEOF_STR] = "";
89d917a2
JF
4075 char *info;
4076 size_t cmdsize = 0;
4077
4e8159cf
JF
4078 if (line->type == LINE_STAT_NONE ||
4079 (!status && line[1].type == LINE_STAT_NONE)) {
4080 report("No file to diff");
586c423d 4081 return REQ_NONE;
89d917a2
JF
4082 }
4083
5ba030cf
JF
4084 if (status) {
4085 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4086 return REQ_QUIT;
4087 /* Diffs for unmerged entries are empty when pasing the
4088 * new path, so leave it empty. */
4089 if (status->status != 'U' &&
4090 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4091 return REQ_QUIT;
4092 }
89d917a2
JF
4093
4094 if (opt_cdup[0] &&
4095 line->type != LINE_STAT_UNTRACKED &&
4096 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
586c423d 4097 return REQ_QUIT;
89d917a2
JF
4098
4099 switch (line->type) {
4100 case LINE_STAT_STAGED:
22d9b77c
JF
4101 if (opt_no_head) {
4102 if (!string_format_from(opt_cmd, &cmdsize,
4103 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4104 newpath))
4105 return REQ_QUIT;
4106 } else {
4107 if (!string_format_from(opt_cmd, &cmdsize,
4108 STATUS_DIFF_INDEX_SHOW_CMD,
4109 oldpath, newpath))
4110 return REQ_QUIT;
4111 }
4112
4e8159cf
JF
4113 if (status)
4114 info = "Staged changes to %s";
4115 else
4116 info = "Staged changes";
89d917a2
JF
4117 break;
4118
4119 case LINE_STAT_UNSTAGED:
f99c6095 4120 if (!string_format_from(opt_cmd, &cmdsize,
5ba030cf 4121 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
586c423d 4122 return REQ_QUIT;
4e8159cf
JF
4123 if (status)
4124 info = "Unstaged changes to %s";
4125 else
4126 info = "Unstaged changes";
89d917a2
JF
4127 break;
4128
4129 case LINE_STAT_UNTRACKED:
4130 if (opt_pipe)
586c423d
JF
4131 return REQ_QUIT;
4132
4e8159cf
JF
4133 if (!status) {
4134 report("No file to show");
586c423d 4135 return REQ_NONE;
4e8159cf
JF
4136 }
4137
5ba030cf 4138 opt_pipe = fopen(status->new.name, "r");
89d917a2
JF
4139 info = "Untracked file %s";
4140 break;
4141
f5a5e640
JF
4142 case LINE_STAT_HEAD:
4143 return REQ_NONE;
4144
89d917a2 4145 default:
e77aa656 4146 die("line type %d not handled in switch", line->type);
89d917a2
JF
4147 }
4148
3e634113
JF
4149 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
4150 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
b33611d8
JF
4151 if (status) {
4152 stage_status = *status;
4153 } else {
4154 memset(&stage_status, 0, sizeof(stage_status));
4155 }
4156
4157 stage_line_type = line->type;
5ba030cf 4158 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
89d917a2
JF
4159 }
4160
586c423d 4161 return REQ_NONE;
ca1d71ea
JF
4162}
4163
61e39818
JF
4164static bool
4165status_exists(struct status *status, enum line_type type)
4166{
4167 struct view *view = VIEW(REQ_VIEW_STATUS);
4168 struct line *line;
4169
4170 for (line = view->line; line < view->line + view->lines; line++) {
4171 struct status *pos = line->data;
4172
4173 if (line->type == type && pos &&
4174 !strcmp(status->new.name, pos->new.name))
4175 return TRUE;
4176 }
4177
4178 return FALSE;
4179}
4180
88f66e2d 4181
cbbd7f62
JF
4182static FILE *
4183status_update_prepare(enum line_type type)
ca1d71ea 4184{
91c5d983 4185 char cmd[SIZEOF_STR];
91c5d983 4186 size_t cmdsize = 0;
173d76ea 4187
91c5d983 4188 if (opt_cdup[0] &&
ca1d71ea 4189 type != LINE_STAT_UNTRACKED &&
91c5d983 4190 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
cbbd7f62
JF
4191 return NULL;
4192
4193 switch (type) {
4194 case LINE_STAT_STAGED:
4195 string_add(cmd, cmdsize, "git update-index -z --index-info");
4196 break;
4197
4198 case LINE_STAT_UNSTAGED:
4199 case LINE_STAT_UNTRACKED:
4200 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4201 break;
4202
4203 default:
4204 die("line type %d not handled in switch", type);
4205 }
4206
4207 return popen(cmd, "w");
4208}
4209
4210static bool
4211status_update_write(FILE *pipe, struct status *status, enum line_type type)
4212{
4213 char buf[SIZEOF_STR];
4214 size_t bufsize = 0;
4215 size_t written = 0;
91c5d983 4216
ca1d71ea 4217 switch (type) {
173d76ea
JF
4218 case LINE_STAT_STAGED:
4219 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
cbbd7f62 4220 status->old.mode,
173d76ea 4221 status->old.rev,
5ba030cf 4222 status->old.name, 0))
173d76ea 4223 return FALSE;
173d76ea
JF
4224 break;
4225
4226 case LINE_STAT_UNSTAGED:
4227 case LINE_STAT_UNTRACKED:
5ba030cf 4228 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
173d76ea 4229 return FALSE;
173d76ea
JF
4230 break;
4231
4232 default:
e77aa656 4233 die("line type %d not handled in switch", type);
173d76ea
JF
4234 }
4235
173d76ea
JF
4236 while (!ferror(pipe) && written < bufsize) {
4237 written += fwrite(buf + written, 1, bufsize - written, pipe);
4238 }
4239
cbbd7f62
JF
4240 return written == bufsize;
4241}
4242
4243static bool
4244status_update_file(struct status *status, enum line_type type)
4245{
4246 FILE *pipe = status_update_prepare(type);
4247 bool result;
4248
4249 if (!pipe)
4250 return FALSE;
4251
4252 result = status_update_write(pipe, status, type);
173d76ea 4253 pclose(pipe);
cbbd7f62
JF
4254 return result;
4255}
4256
4257static bool
4258status_update_files(struct view *view, struct line *line)
4259{
4260 FILE *pipe = status_update_prepare(line->type);
4261 bool result = TRUE;
4262 struct line *pos = view->line + view->lines;
4263 int files = 0;
4264 int file, done;
173d76ea 4265
cbbd7f62 4266 if (!pipe)
173d76ea
JF
4267 return FALSE;
4268
cbbd7f62
JF
4269 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4270 files++;
4271
4272 for (file = 0, done = 0; result && file < files; line++, file++) {
4273 int almost_done = file * 100 / files;
4274
4275 if (almost_done > done) {
4276 done = almost_done;
4277 string_format(view->ref, "updating file %u of %u (%d%% done)",
4278 file, files, done);
4279 update_view_title(view);
4280 }
4281 result = status_update_write(pipe, line->data, line->type);
4282 }
4283
4284 pclose(pipe);
4285 return result;
173d76ea
JF
4286}
4287
7be144b4 4288static bool
ca1d71ea
JF
4289status_update(struct view *view)
4290{
dec7b437 4291 struct line *line = &view->line[view->lineno];
351917f8 4292
dec7b437 4293 assert(view->lines);
11359638 4294
dec7b437 4295 if (!line->data) {
cbbd7f62
JF
4296 /* This should work even for the "On branch" line. */
4297 if (line < view->line + view->lines && !line[1].data) {
dec7b437 4298 report("Nothing to update");
7be144b4 4299 return FALSE;
93e4c4f6
JF
4300 }
4301
cbbd7f62
JF
4302 if (!status_update_files(view, line + 1))
4303 report("Failed to update file status");
4304
4305 } else if (!status_update_file(line->data, line->type)) {
dec7b437 4306 report("Failed to update file status");
ca1d71ea 4307 }
7be144b4
JF
4308
4309 return TRUE;
ca1d71ea
JF
4310}
4311
88f66e2d
JF
4312static enum request
4313status_request(struct view *view, enum request request, struct line *line)
4314{
0cea0d43
JF
4315 struct status *status = line->data;
4316
88f66e2d
JF
4317 switch (request) {
4318 case REQ_STATUS_UPDATE:
7be144b4
JF
4319 if (!status_update(view))
4320 return REQ_NONE;
88f66e2d
JF
4321 break;
4322
b5c18d9d 4323 case REQ_STATUS_MERGE:
91521b9d
JF
4324 if (!status || status->status != 'U') {
4325 report("Merging only possible for files with unmerged status ('U').");
4326 return REQ_NONE;
4327 }
5ba030cf 4328 open_mergetool(status->new.name);
b5c18d9d
JF
4329 break;
4330
0cea0d43
JF
4331 case REQ_EDIT:
4332 if (!status)
4333 return request;
4334
5ba030cf 4335 open_editor(status->status != '?', status->new.name);
0cea0d43
JF
4336 break;
4337
8a680988
JF
4338 case REQ_VIEW_BLAME:
4339 if (status) {
a2d5d9ef 4340 string_copy(opt_file, status->new.name);
8a680988
JF
4341 opt_ref[0] = 0;
4342 }
4343 return request;
4344
88f66e2d 4345 case REQ_ENTER:
17a27c16
JF
4346 /* After returning the status view has been split to
4347 * show the stage view. No further reloading is
4348 * necessary. */
88f66e2d 4349 status_enter(view, line);
17a27c16 4350 return REQ_NONE;
88f66e2d 4351
acaef3b3 4352 case REQ_REFRESH:
17a27c16 4353 /* Simply reload the view. */
acaef3b3
JF
4354 break;
4355
88f66e2d
JF
4356 default:
4357 return request;
4358 }
4359
17a27c16
JF
4360 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4361
88f66e2d
JF
4362 return REQ_NONE;
4363}
4364
ca1d71ea 4365static void
173d76ea
JF
4366status_select(struct view *view, struct line *line)
4367{
11359638
JF
4368 struct status *status = line->data;
4369 char file[SIZEOF_STR] = "all files";
173d76ea 4370 char *text;
b5c18d9d 4371 char *key;
173d76ea 4372
5ba030cf 4373 if (status && !string_format(file, "'%s'", status->new.name))
11359638
JF
4374 return;
4375
4376 if (!status && line[1].type == LINE_STAT_NONE)
4377 line++;
4378
173d76ea
JF
4379 switch (line->type) {
4380 case LINE_STAT_STAGED:
11359638 4381 text = "Press %s to unstage %s for commit";
173d76ea
JF
4382 break;
4383
4384 case LINE_STAT_UNSTAGED:
11359638 4385 text = "Press %s to stage %s for commit";
173d76ea
JF
4386 break;
4387
4388 case LINE_STAT_UNTRACKED:
11359638 4389 text = "Press %s to stage %s for addition";
173d76ea
JF
4390 break;
4391
f5a5e640 4392 case LINE_STAT_HEAD:
173d76ea 4393 case LINE_STAT_NONE:
11359638
JF
4394 text = "Nothing to update";
4395 break;
173d76ea
JF
4396
4397 default:
e77aa656 4398 die("line type %d not handled in switch", line->type);
173d76ea
JF
4399 }
4400
b5c18d9d
JF
4401 if (status && status->status == 'U') {
4402 text = "Press %s to resolve conflict in %s";
4403 key = get_key(REQ_STATUS_MERGE);
4404
4405 } else {
4406 key = get_key(REQ_STATUS_UPDATE);
4407 }
4408
4409 string_format(view->ref, text, key, file);
173d76ea
JF
4410}
4411
4412static bool
4413status_grep(struct view *view, struct line *line)
4414{
4415 struct status *status = line->data;
4416 enum { S_STATUS, S_NAME, S_END } state;
4417 char buf[2] = "?";
4418 regmatch_t pmatch;
4419
4420 if (!status)
4421 return FALSE;
4422
4423 for (state = S_STATUS; state < S_END; state++) {
4424 char *text;
4425
4426 switch (state) {
5ba030cf 4427 case S_NAME: text = status->new.name; break;
173d76ea
JF
4428 case S_STATUS:
4429 buf[0] = status->status;
4430 text = buf;
4431 break;
4432
4433 default:
4434 return FALSE;
4435 }
4436
4437 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4438 return TRUE;
4439 }
4440
4441 return FALSE;
4442}
4443
4444static struct view_ops status_ops = {
4445 "file",
4446 status_open,
4447 NULL,
4448 status_draw,
586c423d 4449 status_request,
173d76ea
JF
4450 status_grep,
4451 status_select,
4452};
4453
b33611d8
JF
4454
4455static bool
4456stage_diff_line(FILE *pipe, struct line *line)
4457{
4458 char *buf = line->data;
4459 size_t bufsize = strlen(buf);
4460 size_t written = 0;
4461
4462 while (!ferror(pipe) && written < bufsize) {
4463 written += fwrite(buf + written, 1, bufsize - written, pipe);
4464 }
4465
4466 fputc('\n', pipe);
4467
4468 return written == bufsize;
4469}
4470
4471static struct line *
4472stage_diff_hdr(struct view *view, struct line *line)
4473{
4474 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
4475 struct line *diff_hdr;
4476
4477 if (line->type == LINE_DIFF_CHUNK)
4478 diff_hdr = line - 1;
4479 else
4480 diff_hdr = view->line + 1;
4481
4482 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
4483 if (diff_hdr->type == LINE_DIFF_HEADER)
4484 return diff_hdr;
4485
4486 diff_hdr += diff_hdr_dir;
4487 }
4488
4489 return NULL;
4490}
4491
4492static bool
4493stage_update_chunk(struct view *view, struct line *line)
4494{
4495 char cmd[SIZEOF_STR];
4496 size_t cmdsize = 0;
4497 struct line *diff_hdr, *diff_chunk, *diff_end;
4498 FILE *pipe;
4499
4500 diff_hdr = stage_diff_hdr(view, line);
4501 if (!diff_hdr)
4502 return FALSE;
4503
4504 if (opt_cdup[0] &&
4505 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4506 return FALSE;
4507
4508 if (!string_format_from(cmd, &cmdsize,
bc02ff85 4509 "git apply --whitespace=nowarn --cached %s - && "
b33611d8
JF
4510 "git update-index -q --unmerged --refresh 2>/dev/null",
4511 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4512 return FALSE;
4513
4514 pipe = popen(cmd, "w");
4515 if (!pipe)
4516 return FALSE;
4517
4518 diff_end = view->line + view->lines;
4519 if (line->type != LINE_DIFF_CHUNK) {
4520 diff_chunk = diff_hdr;
4521
4522 } else {
4523 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
4524 if (diff_chunk->type == LINE_DIFF_CHUNK ||
4525 diff_chunk->type == LINE_DIFF_HEADER)
4526 diff_end = diff_chunk;
4527
4528 diff_chunk = line;
4529
4530 while (diff_hdr->type != LINE_DIFF_CHUNK) {
4531 switch (diff_hdr->type) {
4532 case LINE_DIFF_HEADER:
4533 case LINE_DIFF_INDEX:
4534 case LINE_DIFF_ADD:
4535 case LINE_DIFF_DEL:
4536 break;
4537
4538 default:
4539 diff_hdr++;
4540 continue;
4541 }
4542
4543 if (!stage_diff_line(pipe, diff_hdr++)) {
4544 pclose(pipe);
4545 return FALSE;
4546 }
4547 }
4548 }
4549
4550 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
4551 diff_chunk++;
4552
4553 pclose(pipe);
4554
4555 if (diff_chunk != diff_end)
4556 return FALSE;
4557
4558 return TRUE;
4559}
4560
61e39818 4561static bool
b33611d8
JF
4562stage_update(struct view *view, struct line *line)
4563{
22d9b77c 4564 if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED &&
b33611d8
JF
4565 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
4566 if (!stage_update_chunk(view, line)) {
4567 report("Failed to apply chunk");
61e39818 4568 return FALSE;
b33611d8
JF
4569 }
4570
cbbd7f62 4571 } else if (!status_update_file(&stage_status, stage_line_type)) {
b33611d8 4572 report("Failed to update file");
61e39818 4573 return FALSE;
b33611d8
JF
4574 }
4575
61e39818 4576 return TRUE;
b33611d8
JF
4577}
4578
4579static enum request
4580stage_request(struct view *view, enum request request, struct line *line)
4581{
4582 switch (request) {
4583 case REQ_STATUS_UPDATE:
4584 stage_update(view, line);
4585 break;
4586
4587 case REQ_EDIT:
5ba030cf 4588 if (!stage_status.new.name[0])
b33611d8
JF
4589 return request;
4590
5ba030cf 4591 open_editor(stage_status.status != '?', stage_status.new.name);
b33611d8
JF
4592 break;
4593
61e39818
JF
4594 case REQ_REFRESH:
4595 /* Reload everything ... */
4596 break;
4597
8a680988
JF
4598 case REQ_VIEW_BLAME:
4599 if (stage_status.new.name[0]) {
a2d5d9ef 4600 string_copy(opt_file, stage_status.new.name);
8a680988
JF
4601 opt_ref[0] = 0;
4602 }
4603 return request;
4604
b33611d8 4605 case REQ_ENTER:
61e39818 4606 return pager_request(view, request, line);
b33611d8
JF
4607
4608 default:
4609 return request;
4610 }
4611
61e39818
JF
4612 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4613
4614 /* Check whether the staged entry still exists, and close the
4615 * stage view if it doesn't. */
4616 if (!status_exists(&stage_status, stage_line_type))
4617 return REQ_VIEW_CLOSE;
4618
4619 if (stage_line_type == LINE_STAT_UNTRACKED)
4620 opt_pipe = fopen(stage_status.new.name, "r");
4621 else
4622 string_copy(opt_cmd, view->cmd);
4623 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4624
b33611d8
JF
4625 return REQ_NONE;
4626}
4627
3e634113
JF
4628static struct view_ops stage_ops = {
4629 "line",
4630 NULL,
4631 pager_read,
4632 pager_draw,
b33611d8 4633 stage_request,
3e634113
JF
4634 pager_grep,
4635 pager_select,
4636};
173d76ea 4637
b33611d8 4638
173d76ea 4639/*
ccc33449 4640 * Revision graph
ff26aa29
JF
4641 */
4642
4643struct commit {
10446330 4644 char id[SIZEOF_REV]; /* SHA1 ID. */
aea510c8 4645 char title[128]; /* First line of the commit message. */
54efb62b
JF
4646 char author[75]; /* Author of the commit. */
4647 struct tm time; /* Date from the author ident. */
4648 struct ref **refs; /* Repository references. */
4649 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
4650 size_t graph_size; /* The width of the graph array. */
d3b19ca4 4651 bool has_parents; /* Rewritten --parents seen. */
ff26aa29 4652};
c34d9c9f 4653
ccc33449
JF
4654/* Size of rev graph with no "padding" columns */
4655#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
2b757533 4656
2ce5c87c
JF
4657struct rev_graph {
4658 struct rev_graph *prev, *next, *parents;
2b757533
JF
4659 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4660 size_t size;
88757ebd
JF
4661 struct commit *commit;
4662 size_t pos;
e81e9c2c 4663 unsigned int boundary:1;
2b757533
JF
4664};
4665
2b757533 4666/* Parents of the commit being visualized. */
446a5c36 4667static struct rev_graph graph_parents[4];
c8d60a25 4668
c65a501a 4669/* The current stack of revisions on the graph. */
446a5c36
JF
4670static struct rev_graph graph_stacks[4] = {
4671 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
c65a501a 4672 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
446a5c36
JF
4673 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4674 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
c65a501a
JF
4675};
4676
9e43b9cd 4677static inline bool
2ce5c87c 4678graph_parent_is_merge(struct rev_graph *graph)
9e43b9cd
JF
4679{
4680 return graph->parents->size > 1;
4681}
4682
88757ebd 4683static inline void
2ce5c87c 4684append_to_rev_graph(struct rev_graph *graph, chtype symbol)
88757ebd 4685{
2c27faac
JF
4686 struct commit *commit = graph->commit;
4687
4688 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4689 commit->graph[commit->graph_size++] = symbol;
88757ebd
JF
4690}
4691
2b757533 4692static void
2ce5c87c 4693done_rev_graph(struct rev_graph *graph)
987890af
JF
4694{
4695 if (graph_parent_is_merge(graph) &&
4696 graph->pos < graph->size - 1 &&
4697 graph->next->size == graph->size + graph->parents->size - 1) {
4698 size_t i = graph->pos + graph->parents->size - 1;
4699
4700 graph->commit->graph_size = i * 2;
4701 while (i < graph->next->size - 1) {
4702 append_to_rev_graph(graph, ' ');
4703 append_to_rev_graph(graph, '\\');
4704 i++;
4705 }
4706 }
4707
4708 graph->size = graph->pos = 0;
4709 graph->commit = NULL;
4710 memset(graph->parents, 0, sizeof(*graph->parents));
4711}
4712
4713static void
2ce5c87c 4714push_rev_graph(struct rev_graph *graph, char *parent)
2b757533 4715{
2fe894e6
JF
4716 int i;
4717
4718 /* "Collapse" duplicate parents lines.
4719 *
4720 * FIXME: This needs to also update update the drawn graph but
4721 * for now it just serves as a method for pruning graph lines. */
4722 for (i = 0; i < graph->size; i++)
4723 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4724 return;
2b757533 4725
2ce5c87c 4726 if (graph->size < SIZEOF_REVITEMS) {
739e81de 4727 string_copy_rev(graph->rev[graph->size++], parent);
2b757533
JF
4728 }
4729}
4730
92507a24
JF
4731static chtype
4732get_rev_graph_symbol(struct rev_graph *graph)
2b757533 4733{
92507a24 4734 chtype symbol;
2b757533 4735
e81e9c2c
JF
4736 if (graph->boundary)
4737 symbol = REVGRAPH_BOUND;
4738 else if (graph->parents->size == 0)
c8d60a25 4739 symbol = REVGRAPH_INIT;
18ffaa23 4740 else if (graph_parent_is_merge(graph))
c8d60a25 4741 symbol = REVGRAPH_MERGE;
c65a501a 4742 else if (graph->pos >= graph->size)
c8d60a25 4743 symbol = REVGRAPH_BRANCH;
2b757533 4744 else
c8d60a25 4745 symbol = REVGRAPH_COMMIT;
1dcb3bec 4746
92507a24
JF
4747 return symbol;
4748}
4749
4750static void
4751draw_rev_graph(struct rev_graph *graph)
4752{
e937c2c8
JF
4753 struct rev_filler {
4754 chtype separator, line;
4755 };
4756 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4757 static struct rev_filler fillers[] = {
4758 { ' ', REVGRAPH_LINE },
4759 { '`', '.' },
4760 { '\'', ' ' },
4761 { '/', ' ' },
e937c2c8 4762 };
92507a24 4763 chtype symbol = get_rev_graph_symbol(graph);
e937c2c8 4764 struct rev_filler *filler;
92507a24
JF
4765 size_t i;
4766
e937c2c8 4767 filler = &fillers[DEFAULT];
110e948e 4768
c65a501a 4769 for (i = 0; i < graph->pos; i++) {
e937c2c8 4770 append_to_rev_graph(graph, filler->line);
9e43b9cd 4771 if (graph_parent_is_merge(graph->prev) &&
e937c2c8
JF
4772 graph->prev->pos == i)
4773 filler = &fillers[RSHARP];
4774
4775 append_to_rev_graph(graph, filler->separator);
110e948e
JF
4776 }
4777
92507a24 4778 /* Place the symbol for this revision. */
c65a501a 4779 append_to_rev_graph(graph, symbol);
2b757533 4780
e937c2c8
JF
4781 if (graph->prev->size > graph->size)
4782 filler = &fillers[RDIAG];
4783 else
4784 filler = &fillers[DEFAULT];
4785
c8d60a25 4786 i++;
2b757533 4787
c65a501a 4788 for (; i < graph->size; i++) {
e937c2c8
JF
4789 append_to_rev_graph(graph, filler->separator);
4790 append_to_rev_graph(graph, filler->line);
4791 if (graph_parent_is_merge(graph->prev) &&
4792 i < graph->prev->pos + graph->parents->size)
4793 filler = &fillers[RSHARP];
4794 if (graph->prev->size > graph->size)
4795 filler = &fillers[LDIAG];
c65a501a
JF
4796 }
4797
4798 if (graph->prev->size > graph->size) {
e937c2c8
JF
4799 append_to_rev_graph(graph, filler->separator);
4800 if (filler->line != ' ')
4801 append_to_rev_graph(graph, filler->line);
2b757533 4802 }
b5d8f208
JF
4803}
4804
61eed810
JF
4805/* Prepare the next rev graph */
4806static void
4807prepare_rev_graph(struct rev_graph *graph)
b5d8f208 4808{
b5d8f208
JF
4809 size_t i;
4810
320df4ea 4811 /* First, traverse all lines of revisions up to the active one. */
c65a501a
JF
4812 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4813 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
b5d8f208 4814 break;
b5d8f208 4815
2ce5c87c 4816 push_rev_graph(graph->next, graph->rev[graph->pos]);
b5d8f208
JF
4817 }
4818
320df4ea 4819 /* Interleave the new revision parent(s). */
e81e9c2c 4820 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
2ce5c87c 4821 push_rev_graph(graph->next, graph->parents->rev[i]);
b5d8f208 4822
320df4ea 4823 /* Lastly, put any remaining revisions. */
c65a501a 4824 for (i = graph->pos + 1; i < graph->size; i++)
2ce5c87c 4825 push_rev_graph(graph->next, graph->rev[i]);
61eed810
JF
4826}
4827
4828static void
4829update_rev_graph(struct rev_graph *graph)
4830{
446a5c36
JF
4831 /* If this is the finalizing update ... */
4832 if (graph->commit)
4833 prepare_rev_graph(graph);
4834
4835 /* Graph visualization needs a one rev look-ahead,
4836 * so the first update doesn't visualize anything. */
4837 if (!graph->prev->commit)
4838 return;
c65a501a 4839
61eed810
JF
4840 draw_rev_graph(graph->prev);
4841 done_rev_graph(graph->prev->prev);
2b757533
JF
4842}
4843
ccc33449
JF
4844
4845/*
4846 * Main view backend
4847 */
4848
4849static bool
4850main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4851{
4852 char buf[DATE_COLS + 1];
4853 struct commit *commit = line->data;
4854 enum line_type type;
4855 int col = 0;
4856 size_t timelen;
1c919d68 4857 int space;
ccc33449
JF
4858
4859 if (!*commit->author)
4860 return FALSE;
4861
1c919d68 4862 space = view->width;
ccc33449
JF
4863 wmove(view->win, lineno, col);
4864
4865 if (selected) {
4866 type = LINE_CURSOR;
4867 wattrset(view->win, get_line_attr(type));
4868 wchgat(view->win, -1, 0, type, NULL);
ccc33449
JF
4869 } else {
4870 type = LINE_MAIN_COMMIT;
4871 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4872 }
4873
823057f4 4874 if (opt_date) {
1c919d68 4875 int n;
ccc33449 4876
1c919d68 4877 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
de46362f
JF
4878 n = draw_text(view, buf, view->width - col, FALSE, selected);
4879 draw_text(view, " ", view->width - col - n, FALSE, selected);
ccc33449 4880
1c919d68
DV
4881 col += DATE_COLS;
4882 wmove(view->win, lineno, col);
4883 if (col >= view->width)
4884 return TRUE;
ccc33449 4885 }
1c919d68
DV
4886 if (type != LINE_CURSOR)
4887 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
ccc33449 4888
823057f4 4889 if (opt_author) {
1c919d68
DV
4890 int max_len;
4891
4892 max_len = view->width - col;
4893 if (max_len > AUTHOR_COLS - 1)
4894 max_len = AUTHOR_COLS - 1;
de46362f 4895 draw_text(view, commit->author, max_len, TRUE, selected);
1c919d68
DV
4896 col += AUTHOR_COLS;
4897 if (col >= view->width)
4898 return TRUE;
ccc33449
JF
4899 }
4900
ccc33449 4901 if (opt_rev_graph && commit->graph_size) {
749cdc92 4902 size_t graph_size = view->width - col;
ccc33449
JF
4903 size_t i;
4904
f83b1c33
DV
4905 if (type != LINE_CURSOR)
4906 wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
ccc33449 4907 wmove(view->win, lineno, col);
749cdc92
JF
4908 if (graph_size > commit->graph_size)
4909 graph_size = commit->graph_size;
ccc33449
JF
4910 /* Using waddch() instead of waddnstr() ensures that
4911 * they'll be rendered correctly for the cursor line. */
749cdc92 4912 for (i = 0; i < graph_size; i++)
ccc33449
JF
4913 waddch(view->win, commit->graph[i]);
4914
4915 col += commit->graph_size + 1;
1c919d68
DV
4916 if (col >= view->width)
4917 return TRUE;
749cdc92 4918 waddch(view->win, ' ');
ccc33449 4919 }
f83b1c33
DV
4920 if (type != LINE_CURSOR)
4921 wattrset(view->win, A_NORMAL);
ccc33449
JF
4922
4923 wmove(view->win, lineno, col);
4924
823057f4 4925 if (opt_show_refs && commit->refs) {
ccc33449
JF
4926 size_t i = 0;
4927
4928 do {
4929 if (type == LINE_CURSOR)
4930 ;
70ea8175
JF
4931 else if (commit->refs[i]->head)
4932 wattrset(view->win, get_line_attr(LINE_MAIN_HEAD));
2384880b
DV
4933 else if (commit->refs[i]->ltag)
4934 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
ccc33449
JF
4935 else if (commit->refs[i]->tag)
4936 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
8b3475e6
JF
4937 else if (commit->refs[i]->tracked)
4938 wattrset(view->win, get_line_attr(LINE_MAIN_TRACKED));
e15ec88e
JF
4939 else if (commit->refs[i]->remote)
4940 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
ccc33449
JF
4941 else
4942 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1c919d68 4943
de46362f 4944 col += draw_text(view, "[", view->width - col, TRUE, selected);
a00fff3c 4945 col += draw_text(view, commit->refs[i]->name, view->width - col,
de46362f
JF
4946 TRUE, selected);
4947 col += draw_text(view, "]", view->width - col, TRUE, selected);
ccc33449
JF
4948 if (type != LINE_CURSOR)
4949 wattrset(view->win, A_NORMAL);
de46362f 4950 col += draw_text(view, " ", view->width - col, TRUE, selected);
1c919d68
DV
4951 if (col >= view->width)
4952 return TRUE;
ccc33449
JF
4953 } while (commit->refs[i++]->next);
4954 }
4955
4956 if (type != LINE_CURSOR)
4957 wattrset(view->win, get_line_attr(type));
4958
de46362f 4959 draw_text(view, commit->title, view->width - col, TRUE, selected);
ccc33449
JF
4960 return TRUE;
4961}
4962
4c6fabc2 4963/* Reads git log --pretty=raw output and parses it into the commit struct. */
6b161b31 4964static bool
701e4f5d 4965main_read(struct view *view, char *line)
22f66b0a 4966{
2ce5c87c 4967 static struct rev_graph *graph = graph_stacks;
be04d936 4968 enum line_type type;
0ff3b97c 4969 struct commit *commit;
22f66b0a 4970
be04d936 4971 if (!line) {
446a5c36 4972 update_rev_graph(graph);
be04d936
JF
4973 return TRUE;
4974 }
4975
4976 type = get_line_type(line);
0ff3b97c 4977 if (type == LINE_COMMIT) {
22f66b0a
JF
4978 commit = calloc(1, sizeof(struct commit));
4979 if (!commit)
4980 return FALSE;
4981
e81e9c2c
JF
4982 line += STRING_SIZE("commit ");
4983 if (*line == '-') {
4984 graph->boundary = 1;
4985 line++;
4986 }
4987
4988 string_copy_rev(commit->id, line);
c34d9c9f 4989 commit->refs = get_refs(commit->id);
c65a501a 4990 graph->commit = commit;
e314c36d 4991 add_line_data(view, commit, LINE_MAIN_COMMIT);
d3b19ca4
JF
4992
4993 while ((line = strchr(line, ' '))) {
4994 line++;
4995 push_rev_graph(graph->parents, line);
4996 commit->has_parents = TRUE;
4997 }
0ff3b97c
JF
4998 return TRUE;
4999 }
2b757533 5000
0ff3b97c
JF
5001 if (!view->lines)
5002 return TRUE;
5003 commit = view->line[view->lines - 1].data;
5004
5005 switch (type) {
2b757533 5006 case LINE_PARENT:
d3b19ca4
JF
5007 if (commit->has_parents)
5008 break;
0ff3b97c 5009 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
78c70acd 5010 break;
22f66b0a 5011
8855ada4 5012 case LINE_AUTHOR:
b76c2afc 5013 {
19c3ac60
JF
5014 /* Parse author lines where the name may be empty:
5015 * author <email@address.tld> 1138474660 +0100
5016 */
4c6fabc2 5017 char *ident = line + STRING_SIZE("author ");
19c3ac60
JF
5018 char *nameend = strchr(ident, '<');
5019 char *emailend = strchr(ident, '>');
b76c2afc 5020
0ff3b97c 5021 if (!nameend || !emailend)
fe7233c3
JF
5022 break;
5023
c65a501a
JF
5024 update_rev_graph(graph);
5025 graph = graph->next;
2b757533 5026
19c3ac60
JF
5027 *nameend = *emailend = 0;
5028 ident = chomp_string(ident);
5029 if (!*ident) {
5030 ident = chomp_string(nameend + 1);
5031 if (!*ident)
5032 ident = "Unknown";
b76c2afc
JF
5033 }
5034
739e81de 5035 string_ncopy(commit->author, ident, strlen(ident));
b76c2afc 5036
4c6fabc2 5037 /* Parse epoch and timezone */
19c3ac60
JF
5038 if (emailend[1] == ' ') {
5039 char *secs = emailend + 2;
5040 char *zone = strchr(secs, ' ');
5041 time_t time = (time_t) atol(secs);
b76c2afc 5042
4c6fabc2 5043 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
b76c2afc
JF
5044 long tz;
5045
5046 zone++;
5047 tz = ('0' - zone[1]) * 60 * 60 * 10;
5048 tz += ('0' - zone[2]) * 60 * 60;
5049 tz += ('0' - zone[3]) * 60;
5050 tz += ('0' - zone[4]) * 60;
5051
5052 if (zone[0] == '-')
5053 tz = -tz;
5054
5055 time -= tz;
5056 }
19c3ac60 5057
b76c2afc
JF
5058 gmtime_r(&time, &commit->time);
5059 }
5060 break;
5061 }
78c70acd 5062 default:
2e8488b4 5063 /* Fill in the commit title if it has not already been set. */
2e8488b4
JF
5064 if (commit->title[0])
5065 break;
5066
5067 /* Require titles to start with a non-space character at the
5068 * offset used by git log. */
9073c64a
JF
5069 if (strncmp(line, " ", 4))
5070 break;
5071 line += 4;
5072 /* Well, if the title starts with a whitespace character,
5073 * try to be forgiving. Otherwise we end up with no title. */
5074 while (isspace(*line))
5075 line++;
5076 if (*line == '\0')
82e78006 5077 break;
9073c64a
JF
5078 /* FIXME: More graceful handling of titles; append "..." to
5079 * shortened titles, etc. */
82e78006 5080
739e81de 5081 string_ncopy(commit->title, line, strlen(line));
22f66b0a
JF
5082 }
5083
5084 return TRUE;
5085}
5086
586c423d
JF
5087static enum request
5088main_request(struct view *view, enum request request, struct line *line)
b801d8b2 5089{
b3a54cba
JF
5090 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5091
586c423d
JF
5092 if (request == REQ_ENTER)
5093 open_view(view, REQ_VIEW_DIFF, flags);
5094 else
5095 return request;
5096
5097 return REQ_NONE;
b801d8b2
JF
5098}
5099
4af34daa
JF
5100static bool
5101main_grep(struct view *view, struct line *line)
5102{
5103 struct commit *commit = line->data;
5104 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
5105 char buf[DATE_COLS + 1];
5106 regmatch_t pmatch;
5107
5108 for (state = S_TITLE; state < S_END; state++) {
5109 char *text;
5110
5111 switch (state) {
5112 case S_TITLE: text = commit->title; break;
5113 case S_AUTHOR: text = commit->author; break;
5114 case S_DATE:
5115 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5116 continue;
5117 text = buf;
5118 break;
5119
5120 default:
5121 return FALSE;
5122 }
5123
b77b2cb8 5124 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4af34daa
JF
5125 return TRUE;
5126 }
5127
5128 return FALSE;
5129}
5130
d720de4b
JF
5131static void
5132main_select(struct view *view, struct line *line)
5133{
5134 struct commit *commit = line->data;
5135
2463b4ea
JF
5136 string_copy_rev(view->ref, commit->id);
5137 string_copy_rev(ref_commit, view->ref);
d720de4b
JF
5138}
5139
6b161b31 5140static struct view_ops main_ops = {
6734f6b9 5141 "commit",
f098944b 5142 NULL,
6b161b31 5143 main_read,
f098944b 5144 main_draw,
586c423d 5145 main_request,
4af34daa 5146 main_grep,
d720de4b 5147 main_select,
6b161b31 5148};
2e8488b4 5149
c34d9c9f 5150
6b161b31 5151/*
10e290ee
JF
5152 * Unicode / UTF-8 handling
5153 *
5154 * NOTE: Much of the following code for dealing with unicode is derived from
5155 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5156 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5157 */
5158
5159/* I've (over)annotated a lot of code snippets because I am not entirely
5160 * confident that the approach taken by this small UTF-8 interface is correct.
5161 * --jonas */
5162
5163static inline int
5164unicode_width(unsigned long c)
5165{
5166 if (c >= 0x1100 &&
5167 (c <= 0x115f /* Hangul Jamo */
5168 || c == 0x2329
5169 || c == 0x232a
5170 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
f97f4012 5171 /* CJK ... Yi */
10e290ee
JF
5172 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5173 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5174 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5175 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5176 || (c >= 0xffe0 && c <= 0xffe6)
5177 || (c >= 0x20000 && c <= 0x2fffd)
5178 || (c >= 0x30000 && c <= 0x3fffd)))
5179 return 2;
5180
749cdc92
JF
5181 if (c == '\t')
5182 return opt_tab_size;
5183
10e290ee
JF
5184 return 1;
5185}
5186
5187/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5188 * Illegal bytes are set one. */
5189static const unsigned char utf8_bytes[256] = {
5190 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,
5191 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,
5192 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,
5193 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,
5194 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,
5195 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,
5196 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,
5197 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,
5198};
5199
5200/* Decode UTF-8 multi-byte representation into a unicode character. */
5201static inline unsigned long
5202utf8_to_unicode(const char *string, size_t length)
5203{
5204 unsigned long unicode;
5205
5206 switch (length) {
5207 case 1:
5208 unicode = string[0];
5209 break;
5210 case 2:
5211 unicode = (string[0] & 0x1f) << 6;
5212 unicode += (string[1] & 0x3f);
5213 break;
5214 case 3:
5215 unicode = (string[0] & 0x0f) << 12;
5216 unicode += ((string[1] & 0x3f) << 6);
5217 unicode += (string[2] & 0x3f);
5218 break;
5219 case 4:
5220 unicode = (string[0] & 0x0f) << 18;
5221 unicode += ((string[1] & 0x3f) << 12);
5222 unicode += ((string[2] & 0x3f) << 6);
5223 unicode += (string[3] & 0x3f);
5224 break;
5225 case 5:
5226 unicode = (string[0] & 0x0f) << 24;
5227 unicode += ((string[1] & 0x3f) << 18);
5228 unicode += ((string[2] & 0x3f) << 12);
5229 unicode += ((string[3] & 0x3f) << 6);
5230 unicode += (string[4] & 0x3f);
5231 break;
68b6e0eb 5232 case 6:
10e290ee
JF
5233 unicode = (string[0] & 0x01) << 30;
5234 unicode += ((string[1] & 0x3f) << 24);
5235 unicode += ((string[2] & 0x3f) << 18);
5236 unicode += ((string[3] & 0x3f) << 12);
5237 unicode += ((string[4] & 0x3f) << 6);
5238 unicode += (string[5] & 0x3f);
5239 break;
5240 default:
5241 die("Invalid unicode length");
5242 }
5243
5244 /* Invalid characters could return the special 0xfffd value but NUL
5245 * should be just as good. */
5246 return unicode > 0xffff ? 0 : unicode;
5247}
5248
5249/* Calculates how much of string can be shown within the given maximum width
5250 * and sets trimmed parameter to non-zero value if all of string could not be
012e76e9
JF
5251 * shown. If the reserve flag is TRUE, it will reserve at least one
5252 * trailing character, which can be useful when drawing a delimiter.
10e290ee
JF
5253 *
5254 * Returns the number of bytes to output from string to satisfy max_width. */
5255static size_t
012e76e9 5256utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
10e290ee
JF
5257{
5258 const char *start = string;
5259 const char *end = strchr(string, '\0');
012e76e9 5260 unsigned char last_bytes = 0;
10e290ee
JF
5261 size_t width = 0;
5262
5263 *trimmed = 0;
5264
5265 while (string < end) {
5266 int c = *(unsigned char *) string;
5267 unsigned char bytes = utf8_bytes[c];
5268 size_t ucwidth;
5269 unsigned long unicode;
5270
5271 if (string + bytes > end)
5272 break;
5273
5274 /* Change representation to figure out whether
5275 * it is a single- or double-width character. */
5276
5277 unicode = utf8_to_unicode(string, bytes);
5278 /* FIXME: Graceful handling of invalid unicode character. */
5279 if (!unicode)
5280 break;
5281
5282 ucwidth = unicode_width(unicode);
5283 width += ucwidth;
5284 if (width > max_width) {
5285 *trimmed = 1;
012e76e9
JF
5286 if (reserve && width - ucwidth == max_width) {
5287 string -= last_bytes;
5288 }
10e290ee
JF
5289 break;
5290 }
5291
10e290ee 5292 string += bytes;
012e76e9 5293 last_bytes = bytes;
10e290ee
JF
5294 }
5295
10e290ee
JF
5296 return string - start;
5297}
5298
5299
5300/*
6b161b31
JF
5301 * Status management
5302 */
2e8488b4 5303
8855ada4 5304/* Whether or not the curses interface has been initialized. */
68b6e0eb 5305static bool cursed = FALSE;
8855ada4 5306
6b161b31
JF
5307/* The status window is used for polling keystrokes. */
5308static WINDOW *status_win;
4a2909a7 5309
21be28fb
JF
5310static bool status_empty = TRUE;
5311
2e8488b4 5312/* Update status and title window. */
4a2909a7
JF
5313static void
5314report(const char *msg, ...)
5315{
6706b2ba 5316 struct view *view = display[current_view];
b76c2afc 5317
ab4af23e
JF
5318 if (input_mode)
5319 return;
5320
c38c64bb
JF
5321 if (!view) {
5322 char buf[SIZEOF_STR];
5323 va_list args;
5324
5325 va_start(args, msg);
5326 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5327 buf[sizeof(buf) - 1] = 0;
5328 buf[sizeof(buf) - 2] = '.';
5329 buf[sizeof(buf) - 3] = '.';
5330 buf[sizeof(buf) - 4] = '.';
5331 }
5332 va_end(args);
5333 die("%s", buf);
5334 }
5335
21be28fb 5336 if (!status_empty || *msg) {
6706b2ba 5337 va_list args;
4a2909a7 5338
6706b2ba 5339 va_start(args, msg);
4b76734f 5340
6706b2ba
JF
5341 wmove(status_win, 0, 0);
5342 if (*msg) {
5343 vwprintw(status_win, msg, args);
21be28fb 5344 status_empty = FALSE;
6706b2ba 5345 } else {
21be28fb 5346 status_empty = TRUE;
6706b2ba 5347 }
390a8262 5348 wclrtoeol(status_win);
6706b2ba 5349 wrefresh(status_win);
b801d8b2 5350
6706b2ba
JF
5351 va_end(args);
5352 }
5353
5354 update_view_title(view);
2bee3bde 5355 update_display_cursor(view);
b801d8b2
JF
5356}
5357
6b161b31
JF
5358/* Controls when nodelay should be in effect when polling user input. */
5359static void
1ba2ae4b 5360set_nonblocking_input(bool loading)
b801d8b2 5361{
6706b2ba 5362 static unsigned int loading_views;
b801d8b2 5363
6706b2ba
JF
5364 if ((loading == FALSE && loading_views-- == 1) ||
5365 (loading == TRUE && loading_views++ == 0))
1ba2ae4b 5366 nodelay(status_win, loading);
6b161b31
JF
5367}
5368
5369static void
5370init_display(void)
5371{
5372 int x, y;
b76c2afc 5373
6908bdbd
JF
5374 /* Initialize the curses library */
5375 if (isatty(STDIN_FILENO)) {
8855ada4 5376 cursed = !!initscr();
6908bdbd
JF
5377 } else {
5378 /* Leave stdin and stdout alone when acting as a pager. */
5379 FILE *io = fopen("/dev/tty", "r+");
5380
e6f60674
JF
5381 if (!io)
5382 die("Failed to open /dev/tty");
8855ada4 5383 cursed = !!newterm(NULL, io, io);
6908bdbd
JF
5384 }
5385
8855ada4
JF
5386 if (!cursed)
5387 die("Failed to initialize curses");
5388
2e8488b4
JF
5389 nonl(); /* Tell curses not to do NL->CR/NL on output */
5390 cbreak(); /* Take input chars one at a time, no wait for \n */
5391 noecho(); /* Don't echo input */
b801d8b2 5392 leaveok(stdscr, TRUE);
b801d8b2
JF
5393
5394 if (has_colors())
5395 init_colors();
5396
5397 getmaxyx(stdscr, y, x);
5398 status_win = newwin(1, 0, y - 1, 0);
5399 if (!status_win)
5400 die("Failed to create status window");
5401
5402 /* Enable keyboard mapping */
5403 keypad(status_win, TRUE);
78c70acd 5404 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6b161b31
JF
5405}
5406
4af34daa 5407static char *
cb9e48c1 5408read_prompt(const char *prompt)
ef5404a4
JF
5409{
5410 enum { READING, STOP, CANCEL } status = READING;
9e21ce5c 5411 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
ef5404a4
JF
5412 int pos = 0;
5413
5414 while (status == READING) {
5415 struct view *view;
5416 int i, key;
5417
ab4af23e
JF
5418 input_mode = TRUE;
5419
699ae55b 5420 foreach_view (view, i)
ef5404a4
JF
5421 update_view(view);
5422
ab4af23e
JF
5423 input_mode = FALSE;
5424
5425 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5426 wclrtoeol(status_win);
5427
ef5404a4
JF
5428 /* Refresh, accept single keystroke of input */
5429 key = wgetch(status_win);
5430 switch (key) {
5431 case KEY_RETURN:
5432 case KEY_ENTER:
5433 case '\n':
5434 status = pos ? STOP : CANCEL;
5435 break;
5436
5437 case KEY_BACKSPACE:
5438 if (pos > 0)
5439 pos--;
5440 else
5441 status = CANCEL;
5442 break;
5443
5444 case KEY_ESC:
5445 status = CANCEL;
5446 break;
5447
5448 case ERR:
5449 break;
5450
5451 default:
5452 if (pos >= sizeof(buf)) {
5453 report("Input string too long");
9e21ce5c 5454 return NULL;
ef5404a4
JF
5455 }
5456
5457 if (isprint(key))
5458 buf[pos++] = (char) key;
5459 }
5460 }
5461
7a06ebdf
JF
5462 /* Clear the status window */
5463 status_empty = FALSE;
5464 report("");
5465
5466 if (status == CANCEL)
9e21ce5c 5467 return NULL;
ef5404a4
JF
5468
5469 buf[pos++] = 0;
ef5404a4 5470
9e21ce5c 5471 return buf;
ef5404a4 5472}
c34d9c9f
JF
5473
5474/*
5475 * Repository references
5476 */
5477
518234f1
DV
5478static struct ref *refs = NULL;
5479static size_t refs_alloc = 0;
5480static size_t refs_size = 0;
c34d9c9f 5481
1307df1a 5482/* Id <-> ref store */
518234f1
DV
5483static struct ref ***id_refs = NULL;
5484static size_t id_refs_alloc = 0;
5485static size_t id_refs_size = 0;
1307df1a 5486
c34d9c9f
JF
5487static struct ref **
5488get_refs(char *id)
5489{
1307df1a
JF
5490 struct ref ***tmp_id_refs;
5491 struct ref **ref_list = NULL;
518234f1 5492 size_t ref_list_alloc = 0;
1307df1a 5493 size_t ref_list_size = 0;
c34d9c9f
JF
5494 size_t i;
5495
1307df1a
JF
5496 for (i = 0; i < id_refs_size; i++)
5497 if (!strcmp(id, id_refs[i][0]->id))
5498 return id_refs[i];
5499
518234f1
DV
5500 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5501 sizeof(*id_refs));
1307df1a
JF
5502 if (!tmp_id_refs)
5503 return NULL;
5504
5505 id_refs = tmp_id_refs;
5506
c34d9c9f
JF
5507 for (i = 0; i < refs_size; i++) {
5508 struct ref **tmp;
5509
5510 if (strcmp(id, refs[i].id))
5511 continue;
5512
518234f1
DV
5513 tmp = realloc_items(ref_list, &ref_list_alloc,
5514 ref_list_size + 1, sizeof(*ref_list));
c34d9c9f 5515 if (!tmp) {
1307df1a
JF
5516 if (ref_list)
5517 free(ref_list);
c34d9c9f
JF
5518 return NULL;
5519 }
5520
1307df1a
JF
5521 ref_list = tmp;
5522 if (ref_list_size > 0)
5523 ref_list[ref_list_size - 1]->next = 1;
5524 ref_list[ref_list_size] = &refs[i];
3af8774e
JF
5525
5526 /* XXX: The properties of the commit chains ensures that we can
5527 * safely modify the shared ref. The repo references will
5528 * always be similar for the same id. */
1307df1a
JF
5529 ref_list[ref_list_size]->next = 0;
5530 ref_list_size++;
c34d9c9f
JF
5531 }
5532
1307df1a
JF
5533 if (ref_list)
5534 id_refs[id_refs_size++] = ref_list;
5535
5536 return ref_list;
c34d9c9f
JF
5537}
5538
5539static int
5699e0cf 5540read_ref(char *id, size_t idlen, char *name, size_t namelen)
c34d9c9f 5541{
d0cea5f9
JF
5542 struct ref *ref;
5543 bool tag = FALSE;
2384880b 5544 bool ltag = FALSE;
e15ec88e 5545 bool remote = FALSE;
8b3475e6 5546 bool tracked = FALSE;
2384880b 5547 bool check_replace = FALSE;
70ea8175 5548 bool head = FALSE;
d0cea5f9 5549
8b0297ae 5550 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2384880b
DV
5551 if (!strcmp(name + namelen - 3, "^{}")) {
5552 namelen -= 3;
5553 name[namelen] = 0;
5554 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5555 check_replace = TRUE;
5556 } else {
5557 ltag = TRUE;
5558 }
c34d9c9f 5559
d0cea5f9 5560 tag = TRUE;
8b0297ae
JF
5561 namelen -= STRING_SIZE("refs/tags/");
5562 name += STRING_SIZE("refs/tags/");
c34d9c9f 5563
e15ec88e
JF
5564 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5565 remote = TRUE;
5566 namelen -= STRING_SIZE("refs/remotes/");
5567 name += STRING_SIZE("refs/remotes/");
8b3475e6 5568 tracked = !strcmp(opt_remote, name);
e15ec88e 5569
d0cea5f9 5570 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
8b0297ae
JF
5571 namelen -= STRING_SIZE("refs/heads/");
5572 name += STRING_SIZE("refs/heads/");
70ea8175 5573 head = !strncmp(opt_head, name, namelen);
c34d9c9f 5574
d0cea5f9 5575 } else if (!strcmp(name, "HEAD")) {
22d9b77c 5576 opt_no_head = FALSE;
d0cea5f9
JF
5577 return OK;
5578 }
6706b2ba 5579
2384880b
DV
5580 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5581 /* it's an annotated tag, replace the previous sha1 with the
5582 * resolved commit id; relies on the fact git-ls-remote lists
5583 * the commit id of an annotated tag right beofre the commit id
5584 * it points to. */
5585 refs[refs_size - 1].ltag = ltag;
5586 string_copy_rev(refs[refs_size - 1].id, id);
5587
5588 return OK;
5589 }
518234f1 5590 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
d0cea5f9
JF
5591 if (!refs)
5592 return ERR;
c34d9c9f 5593
d0cea5f9 5594 ref = &refs[refs_size++];
8b0297ae 5595 ref->name = malloc(namelen + 1);
d0cea5f9
JF
5596 if (!ref->name)
5597 return ERR;
3af8774e 5598
8b0297ae
JF
5599 strncpy(ref->name, name, namelen);
5600 ref->name[namelen] = 0;
8b3475e6 5601 ref->head = head;
d0cea5f9 5602 ref->tag = tag;
2384880b 5603 ref->ltag = ltag;
e15ec88e 5604 ref->remote = remote;
8b3475e6 5605 ref->tracked = tracked;
2463b4ea 5606 string_copy_rev(ref->id, id);
3af8774e 5607
d0cea5f9
JF
5608 return OK;
5609}
c34d9c9f 5610
d0cea5f9
JF
5611static int
5612load_refs(void)
5613{
5614 const char *cmd_env = getenv("TIG_LS_REMOTE");
5615 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
c34d9c9f 5616
4a63c884 5617 return read_properties(popen(cmd, "r"), "\t", read_ref);
d0cea5f9 5618}
c34d9c9f 5619
d0cea5f9 5620static int
5699e0cf 5621read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
d0cea5f9 5622{
22913179 5623 if (!strcmp(name, "i18n.commitencoding"))
739e81de 5624 string_ncopy(opt_encoding, value, valuelen);
c34d9c9f 5625
0cea0d43
JF
5626 if (!strcmp(name, "core.editor"))
5627 string_ncopy(opt_editor, value, valuelen);
5628
8b3475e6
JF
5629 /* branch.<head>.remote */
5630 if (*opt_head &&
5631 !strncmp(name, "branch.", 7) &&
5632 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5633 !strcmp(name + 7 + strlen(opt_head), ".remote"))
5634 string_ncopy(opt_remote, value, valuelen);
5635
5636 if (*opt_head && *opt_remote &&
5637 !strncmp(name, "branch.", 7) &&
5638 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5639 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5640 size_t from = strlen(opt_remote);
5641
5642 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5643 value += STRING_SIZE("refs/heads/");
5644 valuelen -= STRING_SIZE("refs/heads/");
5645 }
5646
5647 if (!string_format_from(opt_remote, &from, "/%s", value))
5648 opt_remote[0] = 0;
5649 }
5650
c34d9c9f
JF
5651 return OK;
5652}
5653
4670cf89 5654static int
417ae6d7 5655load_git_config(void)
4670cf89 5656{
96e58f5b 5657 return read_properties(popen(GIT_CONFIG " --list", "r"),
14c778a6 5658 "=", read_repo_config_option);
d0cea5f9
JF
5659}
5660
5661static int
5699e0cf 5662read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
91c5d983 5663{
c38c64bb 5664 if (!opt_git_dir[0]) {
810f0078 5665 string_ncopy(opt_git_dir, name, namelen);
c38c64bb
JF
5666
5667 } else if (opt_is_inside_work_tree == -1) {
5668 /* This can be 3 different values depending on the
5669 * version of git being used. If git-rev-parse does not
5670 * understand --is-inside-work-tree it will simply echo
5671 * the option else either "true" or "false" is printed.
5672 * Default to true for the unknown case. */
5673 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5674
70ea8175 5675 } else if (opt_cdup[0] == ' ') {
739e81de 5676 string_ncopy(opt_cdup, name, namelen);
70ea8175
JF
5677 } else {
5678 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5679 namelen -= STRING_SIZE("refs/heads/");
5680 name += STRING_SIZE("refs/heads/");
5681 string_ncopy(opt_head, name, namelen);
5682 }
c38c64bb
JF
5683 }
5684
91c5d983
JF
5685 return OK;
5686}
5687
5688static int
5689load_repo_info(void)
5690{
70ea8175
JF
5691 int result;
5692 FILE *pipe = popen("git rev-parse --git-dir --is-inside-work-tree "
5693 " --show-cdup --symbolic-full-name HEAD 2>/dev/null", "r");
5694
5695 /* XXX: The line outputted by "--show-cdup" can be empty so
5696 * initialize it to something invalid to make it possible to
5697 * detect whether it has been set or not. */
5698 opt_cdup[0] = ' ';
5699
5700 result = read_properties(pipe, "=", read_repo_info);
5701 if (opt_cdup[0] == ' ')
5702 opt_cdup[0] = 0;
5703
5704 return result;
91c5d983
JF
5705}
5706
5707static int
4a63c884 5708read_properties(FILE *pipe, const char *separators,
5699e0cf 5709 int (*read_property)(char *, size_t, char *, size_t))
d0cea5f9 5710{
4670cf89
JF
5711 char buffer[BUFSIZ];
5712 char *name;
d0cea5f9 5713 int state = OK;
4670cf89
JF
5714
5715 if (!pipe)
5716 return ERR;
5717
d0cea5f9 5718 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4a63c884
JF
5719 char *value;
5720 size_t namelen;
5721 size_t valuelen;
4670cf89 5722
4a63c884
JF
5723 name = chomp_string(name);
5724 namelen = strcspn(name, separators);
5725
5726 if (name[namelen]) {
5727 name[namelen] = 0;
5728 value = chomp_string(name + namelen + 1);
d0cea5f9 5729 valuelen = strlen(value);
4670cf89 5730
d0cea5f9 5731 } else {
d0cea5f9
JF
5732 value = "";
5733 valuelen = 0;
4670cf89 5734 }
d0cea5f9 5735
3c3801c2 5736 state = read_property(name, namelen, value, valuelen);
4670cf89
JF
5737 }
5738
d0cea5f9
JF
5739 if (state != ERR && ferror(pipe))
5740 state = ERR;
4670cf89
JF
5741
5742 pclose(pipe);
5743
d0cea5f9 5744 return state;
4670cf89
JF
5745}
5746
d0cea5f9 5747
6b161b31
JF
5748/*
5749 * Main
5750 */
5751
b5c9e67f 5752static void __NORETURN
6b161b31
JF
5753quit(int sig)
5754{
8855ada4
JF
5755 /* XXX: Restore tty modes and let the OS cleanup the rest! */
5756 if (cursed)
5757 endwin();
6b161b31
JF
5758 exit(0);
5759}
5760
c6704a4e
JF
5761static void __NORETURN
5762die(const char *err, ...)
6b161b31
JF
5763{
5764 va_list args;
5765
5766 endwin();
5767
5768 va_start(args, err);
5769 fputs("tig: ", stderr);
5770 vfprintf(stderr, err, args);
5771 fputs("\n", stderr);
5772 va_end(args);
5773
5774 exit(1);
5775}
5776
77452abc
JF
5777static void
5778warn(const char *msg, ...)
5779{
5780 va_list args;
5781
5782 va_start(args, msg);
5783 fputs("tig warning: ", stderr);
5784 vfprintf(stderr, msg, args);
5785 fputs("\n", stderr);
5786 va_end(args);
5787}
5788
6b161b31
JF
5789int
5790main(int argc, char *argv[])
5791{
1ba2ae4b 5792 struct view *view;
6b161b31 5793 enum request request;
1ba2ae4b 5794 size_t i;
6b161b31
JF
5795
5796 signal(SIGINT, quit);
5797
6b68fd24 5798 if (setlocale(LC_ALL, "")) {
739e81de
JF
5799 char *codeset = nl_langinfo(CODESET);
5800
5801 string_ncopy(opt_codeset, codeset, strlen(codeset));
6b68fd24
JF
5802 }
5803
e0f50df0
JF
5804 if (load_repo_info() == ERR)
5805 die("Failed to load repo info.");
5806
660e09ad
JF
5807 if (load_options() == ERR)
5808 die("Failed to load user config.");
5809
417ae6d7 5810 if (load_git_config() == ERR)
afdc35b3 5811 die("Failed to load repo config.");
91c5d983 5812
8855ada4 5813 if (!parse_options(argc, argv))
6b161b31
JF
5814 return 0;
5815
58a5e4ea 5816 /* Require a git repository unless when running in pager mode. */
094e6ab0 5817 if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
58a5e4ea
JF
5818 die("Not a git repository");
5819
504fbeeb
JF
5820 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5821 opt_utf8 = FALSE;
5822
6b68fd24
JF
5823 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5824 opt_iconv = iconv_open(opt_codeset, opt_encoding);
20f4b4a3 5825 if (opt_iconv == ICONV_NONE)
6b68fd24
JF
5826 die("Failed to initialize character set conversion");
5827 }
5828
fbe047c9 5829 if (*opt_git_dir && load_refs() == ERR)
c34d9c9f
JF
5830 die("Failed to load refs.");
5831
1ba2ae4b
JF
5832 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5833 view->cmd_env = getenv(view->cmd_env);
5834
6b161b31
JF
5835 request = opt_request;
5836
5837 init_display();
b801d8b2
JF
5838
5839 while (view_driver(display[current_view], request)) {
6b161b31 5840 int key;
b801d8b2
JF
5841 int i;
5842
699ae55b 5843 foreach_view (view, i)
6b161b31 5844 update_view(view);
b801d8b2
JF
5845
5846 /* Refresh, accept single keystroke of input */
6b161b31 5847 key = wgetch(status_win);
04e2b7b2 5848
cf4d82e6
JF
5849 /* wgetch() with nodelay() enabled returns ERR when there's no
5850 * input. */
5851 if (key == ERR) {
5852 request = REQ_NONE;
8b534a13 5853 continue;
cf4d82e6 5854 }
04e2b7b2
JF
5855
5856 request = get_keybinding(display[current_view]->keymap, key);
03a93dbb 5857
6706b2ba 5858 /* Some low-level request handling. This keeps access to
fac7db6c
JF
5859 * status_win restricted. */
5860 switch (request) {
5861 case REQ_PROMPT:
9e21ce5c
JF
5862 {
5863 char *cmd = read_prompt(":");
5864
5865 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5866 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5867 opt_request = REQ_VIEW_DIFF;
5868 } else {
5869 opt_request = REQ_VIEW_PAGER;
5870 }
5871 break;
5872 }
fac7db6c 5873
1d754561 5874 request = REQ_NONE;
9e21ce5c
JF
5875 break;
5876 }
4af34daa
JF
5877 case REQ_SEARCH:
5878 case REQ_SEARCH_BACK:
5879 {
5880 const char *prompt = request == REQ_SEARCH
5881 ? "/" : "?";
5882 char *search = read_prompt(prompt);
5883
5884 if (search)
739e81de 5885 string_ncopy(opt_search, search, strlen(search));
4af34daa
JF
5886 else
5887 request = REQ_NONE;
5888 break;
5889 }
fac7db6c
JF
5890 case REQ_SCREEN_RESIZE:
5891 {
5892 int height, width;
5893
5894 getmaxyx(stdscr, height, width);
5895
5896 /* Resize the status view and let the view driver take
5897 * care of resizing the displayed views. */
5898 wresize(status_win, 1, width);
5899 mvwin(status_win, height - 1, 0);
5900 wrefresh(status_win);
5901 break;
5902 }
5903 default:
5904 break;
03a93dbb 5905 }
b801d8b2
JF
5906 }
5907
5908 quit(0);
5909
5910 return 0;
5911}