Fix compatibility for git rev-parse without --symbolic-full-name
[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);
1e61580b
JF
2511 /* FIXME: When all views can refresh always do this. */
2512 if (view == VIEW(REQ_VIEW_STATUS) ||
2513 view == VIEW(REQ_VIEW_STAGE))
2514 request = REQ_REFRESH;
2515 else
2516 return TRUE;
9eb14b72
JF
2517 }
2518
586c423d
JF
2519 if (view && view->lines) {
2520 request = view->ops->request(view, request, &view->line[view->lineno]);
2521 if (request == REQ_NONE)
2522 return TRUE;
2523 }
2524
b801d8b2 2525 switch (request) {
4a2909a7
JF
2526 case REQ_MOVE_UP:
2527 case REQ_MOVE_DOWN:
2528 case REQ_MOVE_PAGE_UP:
2529 case REQ_MOVE_PAGE_DOWN:
2530 case REQ_MOVE_FIRST_LINE:
2531 case REQ_MOVE_LAST_LINE:
8522ecc7 2532 move_view(view, request);
fd85fef1
JF
2533 break;
2534
4a2909a7
JF
2535 case REQ_SCROLL_LINE_DOWN:
2536 case REQ_SCROLL_LINE_UP:
2537 case REQ_SCROLL_PAGE_DOWN:
2538 case REQ_SCROLL_PAGE_UP:
a28bcc22 2539 scroll_view(view, request);
b801d8b2
JF
2540 break;
2541
8a680988 2542 case REQ_VIEW_BLAME:
a2d5d9ef 2543 if (!opt_file[0]) {
8a680988
JF
2544 report("No file chosen, press %s to open tree view",
2545 get_key(REQ_VIEW_TREE));
2546 break;
2547 }
8a680988
JF
2548 open_view(view, request, OPEN_DEFAULT);
2549 break;
2550
e733ee54
JF
2551 case REQ_VIEW_BLOB:
2552 if (!ref_blob[0]) {
550cd4b5
JF
2553 report("No file chosen, press %s to open tree view",
2554 get_key(REQ_VIEW_TREE));
e733ee54
JF
2555 break;
2556 }
5c4358d1
JF
2557 open_view(view, request, OPEN_DEFAULT);
2558 break;
2559
2560 case REQ_VIEW_PAGER:
b64c5b75 2561 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
5c4358d1
JF
2562 report("No pager content, press %s to run command from prompt",
2563 get_key(REQ_PROMPT));
2564 break;
2565 }
2566 open_view(view, request, OPEN_DEFAULT);
2567 break;
2568
3e634113
JF
2569 case REQ_VIEW_STAGE:
2570 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2571 report("No stage content, press %s to open the status view and choose file",
2572 get_key(REQ_VIEW_STATUS));
2573 break;
2574 }
2575 open_view(view, request, OPEN_DEFAULT);
2576 break;
2577
c38c64bb
JF
2578 case REQ_VIEW_STATUS:
2579 if (opt_is_inside_work_tree == FALSE) {
2580 report("The status view requires a working tree");
2581 break;
2582 }
2583 open_view(view, request, OPEN_DEFAULT);
2584 break;
2585
4a2909a7 2586 case REQ_VIEW_MAIN:
4a2909a7 2587 case REQ_VIEW_DIFF:
2e8488b4 2588 case REQ_VIEW_LOG:
e733ee54 2589 case REQ_VIEW_TREE:
2e8488b4 2590 case REQ_VIEW_HELP:
49f2b43f 2591 open_view(view, request, OPEN_DEFAULT);
b801d8b2
JF
2592 break;
2593
b3a54cba
JF
2594 case REQ_NEXT:
2595 case REQ_PREVIOUS:
2596 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2597
e733ee54
JF
2598 if ((view == VIEW(REQ_VIEW_DIFF) &&
2599 view->parent == VIEW(REQ_VIEW_MAIN)) ||
8a680988
JF
2600 (view == VIEW(REQ_VIEW_DIFF) &&
2601 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3e634113 2602 (view == VIEW(REQ_VIEW_STAGE) &&
b9b5b4cd 2603 view->parent == VIEW(REQ_VIEW_STATUS)) ||
e733ee54
JF
2604 (view == VIEW(REQ_VIEW_BLOB) &&
2605 view->parent == VIEW(REQ_VIEW_TREE))) {
03400136
WF
2606 int line;
2607
b3a54cba 2608 view = view->parent;
03400136 2609 line = view->lineno;
8522ecc7
JF
2610 move_view(view, request);
2611 if (view_is_displayed(view))
f0b3ab80 2612 update_view_title(view);
328d27f7
JF
2613 if (line != view->lineno)
2614 view->ops->request(view, REQ_ENTER,
2615 &view->line[view->lineno]);
2616
b3a54cba 2617 } else {
8522ecc7 2618 move_view(view, request);
b3a54cba 2619 }
328d27f7 2620 break;
6b161b31 2621
03a93dbb
JF
2622 case REQ_VIEW_NEXT:
2623 {
9f41488f 2624 int nviews = displayed_views();
03a93dbb
JF
2625 int next_view = (current_view + 1) % nviews;
2626
2627 if (next_view == current_view) {
2628 report("Only one view is displayed");
2629 break;
2630 }
2631
2632 current_view = next_view;
2633 /* Blur out the title of the previous view. */
2634 update_view_title(view);
6734f6b9 2635 report("");
03a93dbb
JF
2636 break;
2637 }
acaef3b3
JF
2638 case REQ_REFRESH:
2639 report("Refreshing is not yet supported for the %s view", view->name);
2640 break;
2641
47dd652b
JF
2642 case REQ_MAXIMIZE:
2643 if (displayed_views() == 2)
2644 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2645 break;
2646
24b5b3e0 2647 case REQ_TOGGLE_LINENO:
b76c2afc 2648 opt_line_number = !opt_line_number;
d79f1577 2649 redraw_view(view);
b801d8b2
JF
2650 break;
2651
823057f4
DV
2652 case REQ_TOGGLE_DATE:
2653 opt_date = !opt_date;
d79f1577 2654 redraw_view(view);
823057f4
DV
2655 break;
2656
2657 case REQ_TOGGLE_AUTHOR:
2658 opt_author = !opt_author;
d79f1577 2659 redraw_view(view);
823057f4
DV
2660 break;
2661
54efb62b
JF
2662 case REQ_TOGGLE_REV_GRAPH:
2663 opt_rev_graph = !opt_rev_graph;
d79f1577 2664 redraw_view(view);
54efb62b
JF
2665 break;
2666
823057f4
DV
2667 case REQ_TOGGLE_REFS:
2668 opt_show_refs = !opt_show_refs;
d79f1577 2669 redraw_view(view);
823057f4
DV
2670 break;
2671
03a93dbb 2672 case REQ_PROMPT:
8855ada4 2673 /* Always reload^Wrerun commands from the prompt. */
49f2b43f 2674 open_view(view, opt_request, OPEN_RELOAD);
03a93dbb
JF
2675 break;
2676
4af34daa
JF
2677 case REQ_SEARCH:
2678 case REQ_SEARCH_BACK:
c02d8fce 2679 search_view(view, request);
4af34daa
JF
2680 break;
2681
2682 case REQ_FIND_NEXT:
2683 case REQ_FIND_PREV:
2684 find_next(view, request);
2685 break;
2686
4a2909a7 2687 case REQ_STOP_LOADING:
59a45d3a
JF
2688 for (i = 0; i < ARRAY_SIZE(views); i++) {
2689 view = &views[i];
2e8488b4 2690 if (view->pipe)
6a7bb912 2691 report("Stopped loading the %s view", view->name),
03a93dbb
JF
2692 end_update(view);
2693 }
b801d8b2
JF
2694 break;
2695
4a2909a7 2696 case REQ_SHOW_VERSION:
ec31d0d0 2697 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
b801d8b2
JF
2698 return TRUE;
2699
fac7db6c
JF
2700 case REQ_SCREEN_RESIZE:
2701 resize_display();
2702 /* Fall-through */
4a2909a7 2703 case REQ_SCREEN_REDRAW:
20bb5e18 2704 redraw_display();
4a2909a7
JF
2705 break;
2706
0cea0d43
JF
2707 case REQ_EDIT:
2708 report("Nothing to edit");
531c6c69
JF
2709 break;
2710
226da94b 2711
531c6c69
JF
2712 case REQ_ENTER:
2713 report("Nothing to enter");
2714 break;
ca1d71ea 2715
b801d8b2 2716
4f9b667a 2717 case REQ_VIEW_CLOSE:
2fcf5401
JF
2718 /* XXX: Mark closed views by letting view->parent point to the
2719 * view itself. Parents to closed view should never be
2720 * followed. */
2721 if (view->parent &&
2722 view->parent->parent != view->parent) {
4f9b667a
JF
2723 memset(display, 0, sizeof(display));
2724 current_view = 0;
f6da0b66 2725 display[current_view] = view->parent;
2fcf5401 2726 view->parent = view;
4f9b667a
JF
2727 resize_display();
2728 redraw_display();
2729 break;
2730 }
2731 /* Fall-through */
b801d8b2
JF
2732 case REQ_QUIT:
2733 return FALSE;
2734
2735 default:
2e8488b4 2736 /* An unknown key will show most commonly used commands. */
468876c9 2737 report("Unknown key, press 'h' for help");
b801d8b2
JF
2738 return TRUE;
2739 }
2740
2741 return TRUE;
2742}
2743
2744
2745/*
ff26aa29 2746 * Pager backend
b801d8b2
JF
2747 */
2748
6b161b31 2749static bool
5dcf8064 2750pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
b801d8b2 2751{
f4de14c5 2752 static char spaces[] = " ";
fe7233c3
JF
2753 char *text = line->data;
2754 enum line_type type = line->type;
f4de14c5
JF
2755 int attr = A_NORMAL;
2756 int col = 0;
b801d8b2 2757
6706b2ba
JF
2758 wmove(view->win, lineno, 0);
2759
5dcf8064 2760 if (selected) {
78c70acd 2761 type = LINE_CURSOR;
6706b2ba 2762 wchgat(view->win, -1, 0, type, NULL);
f4de14c5 2763 attr = get_line_attr(type);
fd85fef1 2764 }
b801d8b2 2765 wattrset(view->win, attr);
b76c2afc 2766
f4de14c5
JF
2767 if (opt_line_number) {
2768 col += draw_lineno(view, lineno, view->width, selected);
2769 if (col >= view->width)
2770 return TRUE;
2771 }
8855ada4 2772
f4de14c5
JF
2773 if (!selected) {
2774 attr = get_line_attr(type);
2775 wattrset(view->win, attr);
2776 }
2777 if (opt_tab_size < TABSIZE) {
2778 int col_offset = col;
8855ada4 2779
f4de14c5 2780 col = 0;
fe7233c3 2781 while (text && col_offset + col < view->width) {
6706b2ba 2782 int cols_max = view->width - col_offset - col;
fe7233c3 2783 char *pos = text;
6706b2ba 2784 int cols;
4c6fabc2 2785
fe7233c3
JF
2786 if (*text == '\t') {
2787 text++;
6706b2ba 2788 assert(sizeof(spaces) > TABSIZE);
fe7233c3 2789 pos = spaces;
6706b2ba 2790 cols = opt_tab_size - (col % opt_tab_size);
82e78006 2791
b76c2afc 2792 } else {
fe7233c3
JF
2793 text = strchr(text, '\t');
2794 cols = line ? text - pos : strlen(pos);
b76c2afc 2795 }
6706b2ba 2796
fe7233c3 2797 waddnstr(view->win, pos, MIN(cols, cols_max));
6706b2ba 2798 col += cols;
b76c2afc 2799 }
b76c2afc
JF
2800
2801 } else {
f4de14c5 2802 draw_text(view, text, view->width - col, TRUE, selected);
6706b2ba 2803 }
2e8488b4 2804
b801d8b2
JF
2805 return TRUE;
2806}
2807
dc23c0e3 2808static bool
d65ced0d 2809add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
dc23c0e3 2810{
17482b11 2811 char refbuf[SIZEOF_STR];
dc23c0e3
JF
2812 char *ref = NULL;
2813 FILE *pipe;
2814
d3c345f7 2815 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
dc23c0e3
JF
2816 return TRUE;
2817
2818 pipe = popen(refbuf, "r");
2819 if (!pipe)
2820 return TRUE;
2821
2822 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2823 ref = chomp_string(ref);
2824 pclose(pipe);
2825
2826 if (!ref || !*ref)
2827 return TRUE;
2828
2829 /* This is the only fatal call, since it can "corrupt" the buffer. */
17482b11 2830 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
dc23c0e3
JF
2831 return FALSE;
2832
2833 return TRUE;
2834}
2835
7b99a34c
JF
2836static void
2837add_pager_refs(struct view *view, struct line *line)
2838{
17482b11 2839 char buf[SIZEOF_STR];
9295982a 2840 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
7b99a34c 2841 struct ref **refs;
d65ced0d 2842 size_t bufpos = 0, refpos = 0;
7b99a34c 2843 const char *sep = "Refs: ";
dc23c0e3 2844 bool is_tag = FALSE;
7b99a34c
JF
2845
2846 assert(line->type == LINE_COMMIT);
2847
c9ca1ec3 2848 refs = get_refs(commit_id);
dc23c0e3
JF
2849 if (!refs) {
2850 if (view == VIEW(REQ_VIEW_DIFF))
2851 goto try_add_describe_ref;
7b99a34c 2852 return;
dc23c0e3 2853 }
7b99a34c
JF
2854
2855 do {
cc2d1364 2856 struct ref *ref = refs[refpos];
e15ec88e
JF
2857 char *fmt = ref->tag ? "%s[%s]" :
2858 ref->remote ? "%s<%s>" : "%s%s";
7b99a34c 2859
cc2d1364
JF
2860 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2861 return;
7b99a34c 2862 sep = ", ";
dc23c0e3
JF
2863 if (ref->tag)
2864 is_tag = TRUE;
7b99a34c
JF
2865 } while (refs[refpos++]->next);
2866
dc23c0e3
JF
2867 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2868try_add_describe_ref:
d42c8a35 2869 /* Add <tag>-g<commit_id> "fake" reference. */
dc23c0e3
JF
2870 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2871 return;
2872 }
2873
d42c8a35
JF
2874 if (bufpos == 0)
2875 return;
2876
cc2d1364 2877 if (!realloc_lines(view, view->line_size + 1))
7b99a34c
JF
2878 return;
2879
0a0d8910 2880 add_line_text(view, buf, LINE_PP_REFS);
7b99a34c
JF
2881}
2882
6b161b31 2883static bool
701e4f5d 2884pager_read(struct view *view, char *data)
22f66b0a 2885{
0a0d8910 2886 struct line *line;
22f66b0a 2887
be04d936
JF
2888 if (!data)
2889 return TRUE;
2890
0a0d8910
JF
2891 line = add_line_text(view, data, get_line_type(data));
2892 if (!line)
7b99a34c 2893 return FALSE;
fe7233c3 2894
7b99a34c
JF
2895 if (line->type == LINE_COMMIT &&
2896 (view == VIEW(REQ_VIEW_DIFF) ||
2897 view == VIEW(REQ_VIEW_LOG)))
2898 add_pager_refs(view, line);
2899
22f66b0a
JF
2900 return TRUE;
2901}
2902
586c423d
JF
2903static enum request
2904pager_request(struct view *view, enum request request, struct line *line)
6b161b31 2905{
91e8e277 2906 int split = 0;
6b161b31 2907
586c423d
JF
2908 if (request != REQ_ENTER)
2909 return request;
2910
9fbbd28f
JF
2911 if (line->type == LINE_COMMIT &&
2912 (view == VIEW(REQ_VIEW_LOG) ||
2913 view == VIEW(REQ_VIEW_PAGER))) {
91e8e277
JF
2914 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2915 split = 1;
67e48ac5
JF
2916 }
2917
91e8e277
JF
2918 /* Always scroll the view even if it was split. That way
2919 * you can use Enter to scroll through the log view and
2920 * split open each commit diff. */
2921 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2922
2923 /* FIXME: A minor workaround. Scrolling the view will call report("")
9d82d824
JF
2924 * but if we are scrolling a non-current view this won't properly
2925 * update the view title. */
91e8e277
JF
2926 if (split)
2927 update_view_title(view);
6b161b31 2928
586c423d 2929 return REQ_NONE;
6b161b31
JF
2930}
2931
4af34daa
JF
2932static bool
2933pager_grep(struct view *view, struct line *line)
2934{
2935 regmatch_t pmatch;
2936 char *text = line->data;
2937
2938 if (!*text)
2939 return FALSE;
2940
b77b2cb8 2941 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
4af34daa
JF
2942 return FALSE;
2943
2944 return TRUE;
2945}
2946
d720de4b
JF
2947static void
2948pager_select(struct view *view, struct line *line)
2949{
2950 if (line->type == LINE_COMMIT) {
9295982a 2951 char *text = (char *)line->data + STRING_SIZE("commit ");
d720de4b 2952
035ba11f 2953 if (view != VIEW(REQ_VIEW_PAGER))
2463b4ea
JF
2954 string_copy_rev(view->ref, text);
2955 string_copy_rev(ref_commit, text);
d720de4b
JF
2956 }
2957}
2958
6b161b31 2959static struct view_ops pager_ops = {
6734f6b9 2960 "line",
f098944b 2961 NULL,
6b161b31 2962 pager_read,
f098944b 2963 pager_draw,
586c423d 2964 pager_request,
f098944b
JF
2965 pager_grep,
2966 pager_select,
2967};
2968
2969
2970/*
2971 * Help backend
2972 */
2973
2974static bool
2975help_open(struct view *view)
2976{
2977 char buf[BUFSIZ];
2978 int lines = ARRAY_SIZE(req_info) + 2;
2979 int i;
2980
2981 if (view->lines > 0)
2982 return TRUE;
2983
2984 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2985 if (!req_info[i].request)
2986 lines++;
2987
9eb14b72
JF
2988 lines += run_requests + 1;
2989
f098944b
JF
2990 view->line = calloc(lines, sizeof(*view->line));
2991 if (!view->line)
2992 return FALSE;
2993
2994 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2995
2996 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2997 char *key;
2998
0e4360b6
JF
2999 if (req_info[i].request == REQ_NONE)
3000 continue;
3001
f098944b
JF
3002 if (!req_info[i].request) {
3003 add_line_text(view, "", LINE_DEFAULT);
3004 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3005 continue;
3006 }
3007
3008 key = get_key(req_info[i].request);
0e4360b6
JF
3009 if (!*key)
3010 key = "(no key defined)";
3011
f098944b
JF
3012 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3013 continue;
3014
3015 add_line_text(view, buf, LINE_DEFAULT);
3016 }
3017
9eb14b72
JF
3018 if (run_requests) {
3019 add_line_text(view, "", LINE_DEFAULT);
3020 add_line_text(view, "External commands:", LINE_DEFAULT);
3021 }
3022
3023 for (i = 0; i < run_requests; i++) {
3024 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3025 char *key;
3026
3027 if (!req)
3028 continue;
3029
3030 key = get_key_name(req->key);
3031 if (!*key)
3032 key = "(no key defined)";
3033
3034 if (!string_format(buf, " %-10s %-14s `%s`",
3035 keymap_table[req->keymap].name,
3036 key, req->cmd))
3037 continue;
3038
3039 add_line_text(view, buf, LINE_DEFAULT);
3040 }
3041
f098944b
JF
3042 return TRUE;
3043}
3044
3045static struct view_ops help_ops = {
3046 "line",
3047 help_open,
3048 NULL,
3049 pager_draw,
586c423d 3050 pager_request,
4af34daa 3051 pager_grep,
d720de4b 3052 pager_select,
6b161b31
JF
3053};
3054
80ce96ea 3055
ff26aa29 3056/*
e733ee54
JF
3057 * Tree backend
3058 */
3059
69efc854
JF
3060struct tree_stack_entry {
3061 struct tree_stack_entry *prev; /* Entry below this in the stack */
3062 unsigned long lineno; /* Line number to restore */
3063 char *name; /* Position of name in opt_path */
3064};
3065
3066/* The top of the path stack. */
3067static struct tree_stack_entry *tree_stack = NULL;
3068unsigned long tree_lineno = 0;
3069
3070static void
3071pop_tree_stack_entry(void)
3072{
3073 struct tree_stack_entry *entry = tree_stack;
3074
3075 tree_lineno = entry->lineno;
3076 entry->name[0] = 0;
3077 tree_stack = entry->prev;
3078 free(entry);
3079}
3080
3081static void
3082push_tree_stack_entry(char *name, unsigned long lineno)
3083{
3084 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3085 size_t pathlen = strlen(opt_path);
3086
3087 if (!entry)
3088 return;
3089
3090 entry->prev = tree_stack;
3091 entry->name = opt_path + pathlen;
3092 tree_stack = entry;
3093
3094 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3095 pop_tree_stack_entry();
3096 return;
3097 }
3098
3099 /* Move the current line to the first tree entry. */
3100 tree_lineno = 1;
3101 entry->lineno = lineno;
3102}
3103
4795d620 3104/* Parse output from git-ls-tree(1):
e733ee54
JF
3105 *
3106 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3107 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3108 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3109 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3110 */
3111
3112#define SIZEOF_TREE_ATTR \
3113 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3114
3115#define TREE_UP_FORMAT "040000 tree %s\t.."
3116
3117static int
3118tree_compare_entry(enum line_type type1, char *name1,
3119 enum line_type type2, char *name2)
3120{
3121 if (type1 != type2) {
3122 if (type1 == LINE_TREE_DIR)
3123 return -1;
3124 return 1;
3125 }
3126
3127 return strcmp(name1, name2);
3128}
3129
a2d5d9ef
JF
3130static char *
3131tree_path(struct line *line)
3132{
3133 char *path = line->data;
3134
3135 return path + SIZEOF_TREE_ATTR;
3136}
3137
e733ee54
JF
3138static bool
3139tree_read(struct view *view, char *text)
3140{
be04d936 3141 size_t textlen = text ? strlen(text) : 0;
e733ee54
JF
3142 char buf[SIZEOF_STR];
3143 unsigned long pos;
3144 enum line_type type;
f88a5319 3145 bool first_read = view->lines == 0;
e733ee54 3146
4ed67514
JF
3147 if (!text)
3148 return TRUE;
e733ee54
JF
3149 if (textlen <= SIZEOF_TREE_ATTR)
3150 return FALSE;
3151
3152 type = text[STRING_SIZE("100644 ")] == 't'
3153 ? LINE_TREE_DIR : LINE_TREE_FILE;
3154
f88a5319 3155 if (first_read) {
e733ee54 3156 /* Add path info line */
0a0d8910
JF
3157 if (!string_format(buf, "Directory path /%s", opt_path) ||
3158 !realloc_lines(view, view->line_size + 1) ||
3159 !add_line_text(view, buf, LINE_DEFAULT))
e733ee54
JF
3160 return FALSE;
3161
3162 /* Insert "link" to parent directory. */
0a0d8910
JF
3163 if (*opt_path) {
3164 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3165 !realloc_lines(view, view->line_size + 1) ||
3166 !add_line_text(view, buf, LINE_TREE_DIR))
3167 return FALSE;
3168 }
e733ee54
JF
3169 }
3170
3171 /* Strip the path part ... */
3172 if (*opt_path) {
3173 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3174 size_t striplen = strlen(opt_path);
3175 char *path = text + SIZEOF_TREE_ATTR;
3176
3177 if (pathlen > striplen)
3178 memmove(path, path + striplen,
3179 pathlen - striplen + 1);
3180 }
3181
3182 /* Skip "Directory ..." and ".." line. */
3183 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3184 struct line *line = &view->line[pos];
a2d5d9ef 3185 char *path1 = tree_path(line);
e733ee54
JF
3186 char *path2 = text + SIZEOF_TREE_ATTR;
3187 int cmp = tree_compare_entry(line->type, path1, type, path2);
3188
3189 if (cmp <= 0)
3190 continue;
3191
3192 text = strdup(text);
3193 if (!text)
3194 return FALSE;
3195
3196 if (view->lines > pos)
3197 memmove(&view->line[pos + 1], &view->line[pos],
3198 (view->lines - pos) * sizeof(*line));
3199
3200 line = &view->line[pos];
3201 line->data = text;
3202 line->type = type;
3203 view->lines++;
3204 return TRUE;
3205 }
3206
0a0d8910 3207 if (!add_line_text(view, text, type))
e733ee54
JF
3208 return FALSE;
3209
69efc854
JF
3210 if (tree_lineno > view->lineno) {
3211 view->lineno = tree_lineno;
3212 tree_lineno = 0;
3213 }
f88a5319 3214
e733ee54
JF
3215 return TRUE;
3216}
3217
586c423d
JF
3218static enum request
3219tree_request(struct view *view, enum request request, struct line *line)
e733ee54 3220{
aac64c17 3221 enum open_flags flags;
586c423d 3222
a2d5d9ef
JF
3223 if (request == REQ_VIEW_BLAME) {
3224 char *filename = tree_path(line);
3225
3226 if (line->type == LINE_TREE_DIR) {
3227 report("Cannot show blame for directory %s", opt_path);
3228 return REQ_NONE;
3229 }
3230
8f298f3e
JF
3231 string_copy(opt_ref, view->vid);
3232 string_format(opt_file, "%s%s", opt_path, filename);
a2d5d9ef
JF
3233 return request;
3234 }
c509eed2
DV
3235 if (request == REQ_TREE_PARENT) {
3236 if (*opt_path) {
3237 /* fake 'cd ..' */
3238 request = REQ_ENTER;
3239 line = &view->line[1];
3240 } else {
3241 /* quit view if at top of tree */
3242 return REQ_VIEW_CLOSE;
3243 }
3244 }
586c423d
JF
3245 if (request != REQ_ENTER)
3246 return request;
e733ee54 3247
69efc854
JF
3248 /* Cleanup the stack if the tree view is at a different tree. */
3249 while (!*opt_path && tree_stack)
3250 pop_tree_stack_entry();
3251
e733ee54
JF
3252 switch (line->type) {
3253 case LINE_TREE_DIR:
3254 /* Depending on whether it is a subdir or parent (updir?) link
3255 * mangle the path buffer. */
3256 if (line == &view->line[1] && *opt_path) {
69efc854 3257 pop_tree_stack_entry();
e733ee54
JF
3258
3259 } else {
a2d5d9ef 3260 char *basename = tree_path(line);
e733ee54 3261
69efc854 3262 push_tree_stack_entry(basename, view->lineno);
e733ee54
JF
3263 }
3264
3265 /* Trees and subtrees share the same ID, so they are not not
3266 * unique like blobs. */
aac64c17 3267 flags = OPEN_RELOAD;
e733ee54
JF
3268 request = REQ_VIEW_TREE;
3269 break;
3270
3271 case LINE_TREE_FILE:
aac64c17 3272 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
e733ee54
JF
3273 request = REQ_VIEW_BLOB;
3274 break;
3275
3276 default:
3277 return TRUE;
3278 }
3279
3280 open_view(view, request, flags);
69efc854
JF
3281 if (request == REQ_VIEW_TREE) {
3282 view->lineno = tree_lineno;
3283 }
e733ee54 3284
586c423d 3285 return REQ_NONE;
e733ee54
JF
3286}
3287
d720de4b
JF
3288static void
3289tree_select(struct view *view, struct line *line)
3290{
9295982a 3291 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
73c76ef5
JF
3292
3293 if (line->type == LINE_TREE_FILE) {
2463b4ea 3294 string_copy_rev(ref_blob, text);
d720de4b 3295
ebbaf4fe
JF
3296 } else if (line->type != LINE_TREE_DIR) {
3297 return;
d720de4b 3298 }
ebbaf4fe 3299
2463b4ea 3300 string_copy_rev(view->ref, text);
d720de4b
JF
3301}
3302
e733ee54
JF
3303static struct view_ops tree_ops = {
3304 "file",
f098944b 3305 NULL,
e733ee54 3306 tree_read,
f098944b 3307 pager_draw,
586c423d 3308 tree_request,
e733ee54 3309 pager_grep,
d720de4b 3310 tree_select,
e733ee54
JF
3311};
3312
3313static bool
3314blob_read(struct view *view, char *line)
3315{
a2d5d9ef
JF
3316 if (!line)
3317 return TRUE;
c115e7ac 3318 return add_line_text(view, line, LINE_DEFAULT) != NULL;
e733ee54
JF
3319}
3320
3321static struct view_ops blob_ops = {
3322 "line",
f098944b 3323 NULL,
e733ee54 3324 blob_read,
f098944b 3325 pager_draw,
586c423d 3326 pager_request,
e733ee54 3327 pager_grep,
d720de4b 3328 pager_select,
e733ee54
JF
3329};
3330
8a680988
JF
3331/*
3332 * Blame backend
3333 *
3334 * Loading the blame view is a two phase job:
3335 *
a2d5d9ef 3336 * 1. File content is read either using opt_file from the
8a680988
JF
3337 * filesystem or using git-cat-file.
3338 * 2. Then blame information is incrementally added by
3339 * reading output from git-blame.
3340 */
3341
3342struct blame_commit {
3343 char id[SIZEOF_REV]; /* SHA1 ID. */
3344 char title[128]; /* First line of the commit message. */
3345 char author[75]; /* Author of the commit. */
3346 struct tm time; /* Date from the author ident. */
3347 char filename[128]; /* Name of file. */
3348};
3349
3350struct blame {
3351 struct blame_commit *commit;
3352 unsigned int header:1;
3353 char text[1];
3354};
3355
3356#define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3357#define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3358
3359static bool
3360blame_open(struct view *view)
3361{
3362 char path[SIZEOF_STR];
3363 char ref[SIZEOF_STR] = "";
3364
a2d5d9ef 3365 if (sq_quote(path, 0, opt_file) >= sizeof(path))
8a680988
JF
3366 return FALSE;
3367
3368 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3369 return FALSE;
3370
3371 if (*opt_ref) {
3372 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3373 return FALSE;
3374 } else {
a2d5d9ef 3375 view->pipe = fopen(opt_file, "r");
8a680988
JF
3376 if (!view->pipe &&
3377 !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3378 return FALSE;
3379 }
3380
3381 if (!view->pipe)
3382 view->pipe = popen(view->cmd, "r");
3383 if (!view->pipe)
3384 return FALSE;
3385
3386 if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3387 return FALSE;
3388
a2d5d9ef
JF
3389 string_format(view->ref, "%s ...", opt_file);
3390 string_copy_rev(view->vid, opt_file);
8a680988
JF
3391 set_nonblocking_input(TRUE);
3392
3393 if (view->line) {
3394 int i;
3395
3396 for (i = 0; i < view->lines; i++)
3397 free(view->line[i].data);
3398 free(view->line);
3399 }
3400
3401 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3402 view->offset = view->lines = view->lineno = 0;
3403 view->line = NULL;
3404 view->start_time = time(NULL);
442cbee3
JF
3405
3406 return TRUE;
8a680988
JF
3407}
3408
3409static struct blame_commit *
3410get_blame_commit(struct view *view, const char *id)
3411{
3412 size_t i;
3413
3414 for (i = 0; i < view->lines; i++) {
3415 struct blame *blame = view->line[i].data;
3416
3417 if (!blame->commit)
3418 continue;
3419
3420 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3421 return blame->commit;
3422 }
3423
3424 {
3425 struct blame_commit *commit = calloc(1, sizeof(*commit));
3426
3427 if (commit)
3428 string_ncopy(commit->id, id, SIZEOF_REV);
3429 return commit;
3430 }
3431}
3432
3433static bool
3434parse_number(char **posref, size_t *number, size_t min, size_t max)
3435{
3436 char *pos = *posref;
3437
3438 *posref = NULL;
3439 pos = strchr(pos + 1, ' ');
3440 if (!pos || !isdigit(pos[1]))
3441 return FALSE;
3442 *number = atoi(pos + 1);
3443 if (*number < min || *number > max)
3444 return FALSE;
3445
3446 *posref = pos;
3447 return TRUE;
3448}
3449
3450static struct blame_commit *
3451parse_blame_commit(struct view *view, char *text, int *blamed)
3452{
3453 struct blame_commit *commit;
3454 struct blame *blame;
3455 char *pos = text + SIZEOF_REV - 1;
3456 size_t lineno;
3457 size_t group;
8a680988
JF
3458
3459 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3460 return NULL;
3461
3462 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3463 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3464 return NULL;
3465
3466 commit = get_blame_commit(view, text);
3467 if (!commit)
3468 return NULL;
3469
3470 *blamed += group;
3471 while (group--) {
3472 struct line *line = &view->line[lineno + group - 1];
3473
3474 blame = line->data;
3475 blame->commit = commit;
76724178 3476 blame->header = !group;
8a680988
JF
3477 line->dirty = 1;
3478 }
8a680988
JF
3479
3480 return commit;
3481}
3482
3483static bool
3484blame_read_file(struct view *view, char *line)
3485{
3486 if (!line) {
3487 FILE *pipe = NULL;
3488
3489 if (view->lines > 0)
3490 pipe = popen(view->cmd, "r");
3bdbba9a
JF
3491 else if (!view->parent)
3492 die("No blame exist for %s", view->vid);
8a680988
JF
3493 view->cmd[0] = 0;
3494 if (!pipe) {
3495 report("Failed to load blame data");
3496 return TRUE;
3497 }
3498
3499 fclose(view->pipe);
3500 view->pipe = pipe;
3501 return FALSE;
3502
3503 } else {
3504 size_t linelen = strlen(line);
3505 struct blame *blame = malloc(sizeof(*blame) + linelen);
3506
3507 if (!line)
3508 return FALSE;
3509
3510 blame->commit = NULL;
3511 strncpy(blame->text, line, linelen);
3512 blame->text[linelen] = 0;
3513 return add_line_data(view, blame, LINE_BLAME_COMMIT) != NULL;
3514 }
3515}
3516
3517static bool
3518match_blame_header(const char *name, char **line)
3519{
3520 size_t namelen = strlen(name);
3521 bool matched = !strncmp(name, *line, namelen);
3522
3523 if (matched)
3524 *line += namelen;
3525
3526 return matched;
3527}
3528
3529static bool
3530blame_read(struct view *view, char *line)
3531{
3532 static struct blame_commit *commit = NULL;
3533 static int blamed = 0;
3534 static time_t author_time;
3535
3536 if (*view->cmd)
3537 return blame_read_file(view, line);
3538
3539 if (!line) {
3540 /* Reset all! */
3541 commit = NULL;
3542 blamed = 0;
3543 string_format(view->ref, "%s", view->vid);
3544 if (view_is_displayed(view)) {
3545 update_view_title(view);
3546 redraw_view_from(view, 0);
3547 }
3548 return TRUE;
3549 }
3550
3551 if (!commit) {
3552 commit = parse_blame_commit(view, line, &blamed);
3553 string_format(view->ref, "%s %2d%%", view->vid,
3554 blamed * 100 / view->lines);
3555
3556 } else if (match_blame_header("author ", &line)) {
3557 string_ncopy(commit->author, line, strlen(line));
3558
3559 } else if (match_blame_header("author-time ", &line)) {
3560 author_time = (time_t) atol(line);
3561
3562 } else if (match_blame_header("author-tz ", &line)) {
3563 long tz;
3564
3565 tz = ('0' - line[1]) * 60 * 60 * 10;
3566 tz += ('0' - line[2]) * 60 * 60;
3567 tz += ('0' - line[3]) * 60;
3568 tz += ('0' - line[4]) * 60;
3569
3570 if (line[0] == '-')
3571 tz = -tz;
3572
3573 author_time -= tz;
3574 gmtime_r(&author_time, &commit->time);
3575
3576 } else if (match_blame_header("summary ", &line)) {
3577 string_ncopy(commit->title, line, strlen(line));
3578
3579 } else if (match_blame_header("filename ", &line)) {
3580 string_ncopy(commit->filename, line, strlen(line));
3581 commit = NULL;
3582 }
3583
3584 return TRUE;
3585}
3586
3587static bool
3588blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3589{
8a680988
JF
3590 struct blame *blame = line->data;
3591 int col = 0;
3592
3593 wmove(view->win, lineno, 0);
3594
3595 if (selected) {
3596 wattrset(view->win, get_line_attr(LINE_CURSOR));
3597 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3598 } else {
3599 wattrset(view->win, A_NORMAL);
8a680988
JF
3600 }
3601
3602 if (opt_date) {
3603 int n;
3604
3605 if (!selected)
3606 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3607 if (blame->commit) {
3608 char buf[DATE_COLS + 1];
3609 int timelen;
3610
3611 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &blame->commit->time);
de46362f
JF
3612 n = draw_text(view, buf, view->width - col, FALSE, selected);
3613 draw_text(view, " ", view->width - col - n, FALSE, selected);
8a680988
JF
3614 }
3615
3616 col += DATE_COLS;
3617 wmove(view->win, lineno, col);
3618 if (col >= view->width)
3619 return TRUE;
3620 }
3621
3622 if (opt_author) {
3623 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3624
3625 if (!selected)
3626 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3627 if (blame->commit)
de46362f 3628 draw_text(view, blame->commit->author, max, TRUE, selected);
8a680988
JF
3629 col += AUTHOR_COLS;
3630 if (col >= view->width)
3631 return TRUE;
3632 wmove(view->win, lineno, col);
3633 }
3634
3635 {
3636 int max = MIN(ID_COLS - 1, view->width - col);
3637
3638 if (!selected)
3639 wattrset(view->win, get_line_attr(LINE_BLAME_ID));
3640 if (blame->commit)
3641 draw_text(view, blame->commit->id, max, FALSE, -1);
3642 col += ID_COLS;
3643 if (col >= view->width)
3644 return TRUE;
3645 wmove(view->win, lineno, col);
3646 }
3647
3648 {
f4de14c5 3649 col += draw_lineno(view, lineno, view->width - col, selected);
8a680988
JF
3650 if (col >= view->width)
3651 return TRUE;
3652 }
3653
de46362f 3654 col += draw_text(view, blame->text, view->width - col, TRUE, selected);
8a680988
JF
3655
3656 return TRUE;
3657}
3658
3659static enum request
3660blame_request(struct view *view, enum request request, struct line *line)
3661{
3662 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3663 struct blame *blame = line->data;
3664
3665 switch (request) {
3666 case REQ_ENTER:
3667 if (!blame->commit) {
3668 report("No commit loaded yet");
3669 break;
3670 }
3671
22d9b77c 3672 if (!strcmp(blame->commit->id, NULL_ID)) {
8a680988
JF
3673 char path[SIZEOF_STR];
3674
3675 if (sq_quote(path, 0, view->vid) >= sizeof(path))
3676 break;
3677 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3678 }
3679
3680 open_view(view, REQ_VIEW_DIFF, flags);
3681 break;
3682
3683 default:
3684 return request;
3685 }
3686
3687 return REQ_NONE;
3688}
3689
3690static bool
3691blame_grep(struct view *view, struct line *line)
3692{
3693 struct blame *blame = line->data;
3694 struct blame_commit *commit = blame->commit;
3695 regmatch_t pmatch;
3696
3697#define MATCH(text) \
3698 (*text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3699
3700 if (commit) {
3701 char buf[DATE_COLS + 1];
3702
3703 if (MATCH(commit->title) ||
3704 MATCH(commit->author) ||
3705 MATCH(commit->id))
3706 return TRUE;
3707
3708 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3709 MATCH(buf))
3710 return TRUE;
3711 }
3712
3713 return MATCH(blame->text);
3714
3715#undef MATCH
3716}
3717
3718static void
3719blame_select(struct view *view, struct line *line)
3720{
3721 struct blame *blame = line->data;
3722 struct blame_commit *commit = blame->commit;
3723
3724 if (!commit)
3725 return;
3726
22d9b77c 3727 if (!strcmp(commit->id, NULL_ID))
8a680988
JF
3728 string_ncopy(ref_commit, "HEAD", 4);
3729 else
3730 string_copy_rev(ref_commit, commit->id);
3731}
3732
3733static struct view_ops blame_ops = {
3734 "line",
3735 blame_open,
3736 blame_read,
3737 blame_draw,
3738 blame_request,
3739 blame_grep,
3740 blame_select,
3741};
e733ee54
JF
3742
3743/*
173d76ea
JF
3744 * Status backend
3745 */
3746
3747struct status {
3748 char status;
3749 struct {
3750 mode_t mode;
3751 char rev[SIZEOF_REV];
5ba030cf 3752 char name[SIZEOF_STR];
173d76ea
JF
3753 } old;
3754 struct {
3755 mode_t mode;
3756 char rev[SIZEOF_REV];
5ba030cf 3757 char name[SIZEOF_STR];
173d76ea 3758 } new;
173d76ea
JF
3759};
3760
f5a5e640 3761static char status_onbranch[SIZEOF_STR];
b33611d8
JF
3762static struct status stage_status;
3763static enum line_type stage_line_type;
3764
173d76ea
JF
3765/* Get fields from the diff line:
3766 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3767 */
3768static inline bool
3769status_get_diff(struct status *file, char *buf, size_t bufsize)
3770{
3771 char *old_mode = buf + 1;
3772 char *new_mode = buf + 8;
3773 char *old_rev = buf + 15;
3774 char *new_rev = buf + 56;
3775 char *status = buf + 97;
3776
5ba030cf 3777 if (bufsize < 99 ||
173d76ea
JF
3778 old_mode[-1] != ':' ||
3779 new_mode[-1] != ' ' ||
3780 old_rev[-1] != ' ' ||
3781 new_rev[-1] != ' ' ||
3782 status[-1] != ' ')
3783 return FALSE;
3784
3785 file->status = *status;
3786
3787 string_copy_rev(file->old.rev, old_rev);
3788 string_copy_rev(file->new.rev, new_rev);
3789
3790 file->old.mode = strtoul(old_mode, NULL, 8);
3791 file->new.mode = strtoul(new_mode, NULL, 8);
3792
5ba030cf 3793 file->old.name[0] = file->new.name[0] = 0;
173d76ea
JF
3794
3795 return TRUE;
3796}
3797
3798static bool
22d9b77c 3799status_run(struct view *view, const char cmd[], char status, enum line_type type)
173d76ea
JF
3800{
3801 struct status *file = NULL;
b5c18d9d 3802 struct status *unmerged = NULL;
173d76ea
JF
3803 char buf[SIZEOF_STR * 4];
3804 size_t bufsize = 0;
3805 FILE *pipe;
3806
3807 pipe = popen(cmd, "r");
3808 if (!pipe)
3809 return FALSE;
3810
3811 add_line_data(view, NULL, type);
3812
3813 while (!feof(pipe) && !ferror(pipe)) {
3814 char *sep;
3815 size_t readsize;
3816
3817 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3818 if (!readsize)
3819 break;
3820 bufsize += readsize;
3821
3822 /* Process while we have NUL chars. */
3823 while ((sep = memchr(buf, 0, bufsize))) {
3824 size_t sepsize = sep - buf + 1;
3825
3826 if (!file) {
3827 if (!realloc_lines(view, view->line_size + 1))
3828 goto error_out;
3829
3830 file = calloc(1, sizeof(*file));
3831 if (!file)
3832 goto error_out;
3833
3834 add_line_data(view, file, type);
3835 }
3836
3837 /* Parse diff info part. */
22d9b77c
JF
3838 if (status) {
3839 file->status = status;
3840 if (status == 'A')
3841 string_copy(file->old.rev, NULL_ID);
173d76ea
JF
3842
3843 } else if (!file->status) {
3844 if (!status_get_diff(file, buf, sepsize))
3845 goto error_out;
3846
3847 bufsize -= sepsize;
3848 memmove(buf, sep + 1, bufsize);
3849
3850 sep = memchr(buf, 0, bufsize);
3851 if (!sep)
3852 break;
3853 sepsize = sep - buf + 1;
b5c18d9d
JF
3854
3855 /* Collapse all 'M'odified entries that
3856 * follow a associated 'U'nmerged entry.
3857 */
3858 if (file->status == 'U') {
3859 unmerged = file;
3860
3861 } else if (unmerged) {
5ba030cf 3862 int collapse = !strcmp(buf, unmerged->new.name);
b5c18d9d
JF
3863
3864 unmerged = NULL;
3865 if (collapse) {
3866 free(file);
3867 view->lines--;
3868 continue;
3869 }
3870 }
173d76ea
JF
3871 }
3872
5ba030cf
JF
3873 /* Grab the old name for rename/copy. */
3874 if (!*file->old.name &&
3875 (file->status == 'R' || file->status == 'C')) {
3876 sepsize = sep - buf + 1;
3877 string_ncopy(file->old.name, buf, sepsize);
3878 bufsize -= sepsize;
3879 memmove(buf, sep + 1, bufsize);
3880
3881 sep = memchr(buf, 0, bufsize);
3882 if (!sep)
3883 break;
3884 sepsize = sep - buf + 1;
3885 }
3886
173d76ea
JF
3887 /* git-ls-files just delivers a NUL separated
3888 * list of file names similar to the second half
3889 * of the git-diff-* output. */
5ba030cf
JF
3890 string_ncopy(file->new.name, buf, sepsize);
3891 if (!*file->old.name)
3892 string_copy(file->old.name, file->new.name);
173d76ea
JF
3893 bufsize -= sepsize;
3894 memmove(buf, sep + 1, bufsize);
3895 file = NULL;
3896 }
3897 }
3898
3899 if (ferror(pipe)) {
3900error_out:
3901 pclose(pipe);
3902 return FALSE;
3903 }
3904
3905 if (!view->line[view->lines - 1].data)
3906 add_line_data(view, NULL, LINE_STAT_NONE);
3907
3908 pclose(pipe);
3909 return TRUE;
3910}
3911
b5c18d9d 3912/* Don't show unmerged entries in the staged section. */
5ba030cf 3913#define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
b1744cbe 3914#define STATUS_DIFF_FILES_CMD "git diff-files -z"
173d76ea 3915#define STATUS_LIST_OTHER_CMD \
810f0078 3916 "git ls-files -z --others --exclude-per-directory=.gitignore"
22d9b77c
JF
3917#define STATUS_LIST_NO_HEAD_CMD \
3918 "git ls-files -z --cached --exclude-per-directory=.gitignore"
173d76ea 3919
f99c6095 3920#define STATUS_DIFF_INDEX_SHOW_CMD \
5ba030cf 3921 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
f99c6095
JF
3922
3923#define STATUS_DIFF_FILES_SHOW_CMD \
5ba030cf 3924 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
89d917a2 3925
22d9b77c
JF
3926#define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3927 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3928
173d76ea
JF
3929/* First parse staged info using git-diff-index(1), then parse unstaged
3930 * info using git-diff-files(1), and finally untracked files using
3931 * git-ls-files(1). */
3932static bool
3933status_open(struct view *view)
3934{
810f0078
JF
3935 struct stat statbuf;
3936 char exclude[SIZEOF_STR];
22d9b77c
JF
3937 char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3938 char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
12e8c2be 3939 unsigned long prev_lineno = view->lineno;
22d9b77c 3940 char indexstatus = 0;
173d76ea
JF
3941 size_t i;
3942
3943 for (i = 0; i < view->lines; i++)
3944 free(view->line[i].data);
3945 free(view->line);
518234f1 3946 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
173d76ea
JF
3947 view->line = NULL;
3948
f5a5e640
JF
3949 if (!realloc_lines(view, view->line_size + 7))
3950 return FALSE;
3951
3952 add_line_data(view, NULL, LINE_STAT_HEAD);
22d9b77c
JF
3953 if (opt_no_head)
3954 string_copy(status_onbranch, "Initial commit");
3955 else if (!*opt_head)
f5a5e640
JF
3956 string_copy(status_onbranch, "Not currently on any branch");
3957 else if (!string_format(status_onbranch, "On branch %s", opt_head))
173d76ea
JF
3958 return FALSE;
3959
22d9b77c
JF
3960 if (opt_no_head) {
3961 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3962 indexstatus = 'A';
3963 }
3964
810f0078
JF
3965 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3966 return FALSE;
3967
810f0078 3968 if (stat(exclude, &statbuf) >= 0) {
22d9b77c
JF
3969 size_t cmdsize = strlen(othercmd);
3970
3971 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
3972 sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
3973 return FALSE;
810f0078 3974
22d9b77c
JF
3975 cmdsize = strlen(indexcmd);
3976 if (opt_no_head &&
3977 (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
3978 sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
810f0078
JF
3979 return FALSE;
3980 }
3981
b1744cbe
JF
3982 system("git update-index -q --refresh");
3983
22d9b77c
JF
3984 if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
3985 !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
3986 !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
173d76ea
JF
3987 return FALSE;
3988
12e8c2be 3989 /* If all went well restore the previous line number to stay in
f5a5e640
JF
3990 * the context or select a line with something that can be
3991 * updated. */
3992 if (prev_lineno >= view->lines)
3993 prev_lineno = view->lines - 1;
3994 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
3995 prev_lineno++;
31862819
JF
3996 while (prev_lineno > 0 && !view->line[prev_lineno].data)
3997 prev_lineno--;
f5a5e640
JF
3998
3999 /* If the above fails, always skip the "On branch" line. */
12e8c2be
JF
4000 if (prev_lineno < view->lines)
4001 view->lineno = prev_lineno;
4002 else
f5a5e640 4003 view->lineno = 1;
12e8c2be 4004
31862819
JF
4005 if (view->lineno < view->offset)
4006 view->offset = view->lineno;
4007 else if (view->offset + view->height <= view->lineno)
4008 view->offset = view->lineno - view->height + 1;
4009
173d76ea
JF
4010 return TRUE;
4011}
4012
4013static bool
4014status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4015{
4016 struct status *status = line->data;
f2cd17cd
JF
4017 char *text;
4018 int col = 0;
173d76ea
JF
4019
4020 wmove(view->win, lineno, 0);
4021
4022 if (selected) {
4023 wattrset(view->win, get_line_attr(LINE_CURSOR));
4024 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
4025
f5a5e640
JF
4026 } else if (line->type == LINE_STAT_HEAD) {
4027 wattrset(view->win, get_line_attr(LINE_STAT_HEAD));
4028 wchgat(view->win, -1, 0, LINE_STAT_HEAD, NULL);
4029
173d76ea
JF
4030 } else if (!status && line->type != LINE_STAT_NONE) {
4031 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
4032 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
4033
4034 } else {
4035 wattrset(view->win, get_line_attr(line->type));
4036 }
4037
4038 if (!status) {
173d76ea
JF
4039 switch (line->type) {
4040 case LINE_STAT_STAGED:
4041 text = "Changes to be committed:";
4042 break;
4043
4044 case LINE_STAT_UNSTAGED:
4045 text = "Changed but not updated:";
4046 break;
4047
4048 case LINE_STAT_UNTRACKED:
4049 text = "Untracked files:";
4050 break;
4051
4052 case LINE_STAT_NONE:
4053 text = " (no files)";
4054 break;
4055
f5a5e640
JF
4056 case LINE_STAT_HEAD:
4057 text = status_onbranch;
4058 break;
4059
173d76ea
JF
4060 default:
4061 return FALSE;
4062 }
f2cd17cd
JF
4063 } else {
4064 char buf[] = { status->status, ' ', ' ', ' ', 0 };
173d76ea 4065
f2cd17cd
JF
4066 col += draw_text(view, buf, view->width, TRUE, selected);
4067 if (!selected)
4068 wattrset(view->win, A_NORMAL);
4069 text = status->new.name;
173d76ea
JF
4070 }
4071
f2cd17cd 4072 draw_text(view, text, view->width - col, TRUE, selected);
173d76ea
JF
4073 return TRUE;
4074}
4075
586c423d 4076static enum request
88f66e2d 4077status_enter(struct view *view, struct line *line)
173d76ea 4078{
89d917a2 4079 struct status *status = line->data;
5ba030cf
JF
4080 char oldpath[SIZEOF_STR] = "";
4081 char newpath[SIZEOF_STR] = "";
89d917a2
JF
4082 char *info;
4083 size_t cmdsize = 0;
4084
4e8159cf
JF
4085 if (line->type == LINE_STAT_NONE ||
4086 (!status && line[1].type == LINE_STAT_NONE)) {
4087 report("No file to diff");
586c423d 4088 return REQ_NONE;
89d917a2
JF
4089 }
4090
5ba030cf
JF
4091 if (status) {
4092 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4093 return REQ_QUIT;
4094 /* Diffs for unmerged entries are empty when pasing the
4095 * new path, so leave it empty. */
4096 if (status->status != 'U' &&
4097 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4098 return REQ_QUIT;
4099 }
89d917a2
JF
4100
4101 if (opt_cdup[0] &&
4102 line->type != LINE_STAT_UNTRACKED &&
4103 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
586c423d 4104 return REQ_QUIT;
89d917a2
JF
4105
4106 switch (line->type) {
4107 case LINE_STAT_STAGED:
22d9b77c
JF
4108 if (opt_no_head) {
4109 if (!string_format_from(opt_cmd, &cmdsize,
4110 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4111 newpath))
4112 return REQ_QUIT;
4113 } else {
4114 if (!string_format_from(opt_cmd, &cmdsize,
4115 STATUS_DIFF_INDEX_SHOW_CMD,
4116 oldpath, newpath))
4117 return REQ_QUIT;
4118 }
4119
4e8159cf
JF
4120 if (status)
4121 info = "Staged changes to %s";
4122 else
4123 info = "Staged changes";
89d917a2
JF
4124 break;
4125
4126 case LINE_STAT_UNSTAGED:
f99c6095 4127 if (!string_format_from(opt_cmd, &cmdsize,
5ba030cf 4128 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
586c423d 4129 return REQ_QUIT;
4e8159cf
JF
4130 if (status)
4131 info = "Unstaged changes to %s";
4132 else
4133 info = "Unstaged changes";
89d917a2
JF
4134 break;
4135
4136 case LINE_STAT_UNTRACKED:
4137 if (opt_pipe)
586c423d
JF
4138 return REQ_QUIT;
4139
4e8159cf
JF
4140 if (!status) {
4141 report("No file to show");
586c423d 4142 return REQ_NONE;
4e8159cf
JF
4143 }
4144
5ba030cf 4145 opt_pipe = fopen(status->new.name, "r");
89d917a2
JF
4146 info = "Untracked file %s";
4147 break;
4148
f5a5e640
JF
4149 case LINE_STAT_HEAD:
4150 return REQ_NONE;
4151
89d917a2 4152 default:
e77aa656 4153 die("line type %d not handled in switch", line->type);
89d917a2
JF
4154 }
4155
3e634113
JF
4156 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
4157 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
b33611d8
JF
4158 if (status) {
4159 stage_status = *status;
4160 } else {
4161 memset(&stage_status, 0, sizeof(stage_status));
4162 }
4163
4164 stage_line_type = line->type;
5ba030cf 4165 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
89d917a2
JF
4166 }
4167
586c423d 4168 return REQ_NONE;
ca1d71ea
JF
4169}
4170
61e39818
JF
4171static bool
4172status_exists(struct status *status, enum line_type type)
4173{
4174 struct view *view = VIEW(REQ_VIEW_STATUS);
4175 struct line *line;
4176
4177 for (line = view->line; line < view->line + view->lines; line++) {
4178 struct status *pos = line->data;
4179
4180 if (line->type == type && pos &&
4181 !strcmp(status->new.name, pos->new.name))
4182 return TRUE;
4183 }
4184
4185 return FALSE;
4186}
4187
88f66e2d 4188
cbbd7f62
JF
4189static FILE *
4190status_update_prepare(enum line_type type)
ca1d71ea 4191{
91c5d983 4192 char cmd[SIZEOF_STR];
91c5d983 4193 size_t cmdsize = 0;
173d76ea 4194
91c5d983 4195 if (opt_cdup[0] &&
ca1d71ea 4196 type != LINE_STAT_UNTRACKED &&
91c5d983 4197 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
cbbd7f62
JF
4198 return NULL;
4199
4200 switch (type) {
4201 case LINE_STAT_STAGED:
4202 string_add(cmd, cmdsize, "git update-index -z --index-info");
4203 break;
4204
4205 case LINE_STAT_UNSTAGED:
4206 case LINE_STAT_UNTRACKED:
4207 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4208 break;
4209
4210 default:
4211 die("line type %d not handled in switch", type);
4212 }
4213
4214 return popen(cmd, "w");
4215}
4216
4217static bool
4218status_update_write(FILE *pipe, struct status *status, enum line_type type)
4219{
4220 char buf[SIZEOF_STR];
4221 size_t bufsize = 0;
4222 size_t written = 0;
91c5d983 4223
ca1d71ea 4224 switch (type) {
173d76ea
JF
4225 case LINE_STAT_STAGED:
4226 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
cbbd7f62 4227 status->old.mode,
173d76ea 4228 status->old.rev,
5ba030cf 4229 status->old.name, 0))
173d76ea 4230 return FALSE;
173d76ea
JF
4231 break;
4232
4233 case LINE_STAT_UNSTAGED:
4234 case LINE_STAT_UNTRACKED:
5ba030cf 4235 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
173d76ea 4236 return FALSE;
173d76ea
JF
4237 break;
4238
4239 default:
e77aa656 4240 die("line type %d not handled in switch", type);
173d76ea
JF
4241 }
4242
173d76ea
JF
4243 while (!ferror(pipe) && written < bufsize) {
4244 written += fwrite(buf + written, 1, bufsize - written, pipe);
4245 }
4246
cbbd7f62
JF
4247 return written == bufsize;
4248}
4249
4250static bool
4251status_update_file(struct status *status, enum line_type type)
4252{
4253 FILE *pipe = status_update_prepare(type);
4254 bool result;
4255
4256 if (!pipe)
4257 return FALSE;
4258
4259 result = status_update_write(pipe, status, type);
173d76ea 4260 pclose(pipe);
cbbd7f62
JF
4261 return result;
4262}
4263
4264static bool
4265status_update_files(struct view *view, struct line *line)
4266{
4267 FILE *pipe = status_update_prepare(line->type);
4268 bool result = TRUE;
4269 struct line *pos = view->line + view->lines;
4270 int files = 0;
4271 int file, done;
173d76ea 4272
cbbd7f62 4273 if (!pipe)
173d76ea
JF
4274 return FALSE;
4275
cbbd7f62
JF
4276 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4277 files++;
4278
4279 for (file = 0, done = 0; result && file < files; line++, file++) {
4280 int almost_done = file * 100 / files;
4281
4282 if (almost_done > done) {
4283 done = almost_done;
4284 string_format(view->ref, "updating file %u of %u (%d%% done)",
4285 file, files, done);
4286 update_view_title(view);
4287 }
4288 result = status_update_write(pipe, line->data, line->type);
4289 }
4290
4291 pclose(pipe);
4292 return result;
173d76ea
JF
4293}
4294
7be144b4 4295static bool
ca1d71ea
JF
4296status_update(struct view *view)
4297{
dec7b437 4298 struct line *line = &view->line[view->lineno];
351917f8 4299
dec7b437 4300 assert(view->lines);
11359638 4301
dec7b437 4302 if (!line->data) {
cbbd7f62
JF
4303 /* This should work even for the "On branch" line. */
4304 if (line < view->line + view->lines && !line[1].data) {
dec7b437 4305 report("Nothing to update");
7be144b4 4306 return FALSE;
93e4c4f6
JF
4307 }
4308
cbbd7f62
JF
4309 if (!status_update_files(view, line + 1))
4310 report("Failed to update file status");
4311
4312 } else if (!status_update_file(line->data, line->type)) {
dec7b437 4313 report("Failed to update file status");
ca1d71ea 4314 }
7be144b4
JF
4315
4316 return TRUE;
ca1d71ea
JF
4317}
4318
88f66e2d
JF
4319static enum request
4320status_request(struct view *view, enum request request, struct line *line)
4321{
0cea0d43
JF
4322 struct status *status = line->data;
4323
88f66e2d
JF
4324 switch (request) {
4325 case REQ_STATUS_UPDATE:
7be144b4
JF
4326 if (!status_update(view))
4327 return REQ_NONE;
88f66e2d
JF
4328 break;
4329
b5c18d9d 4330 case REQ_STATUS_MERGE:
91521b9d
JF
4331 if (!status || status->status != 'U') {
4332 report("Merging only possible for files with unmerged status ('U').");
4333 return REQ_NONE;
4334 }
5ba030cf 4335 open_mergetool(status->new.name);
b5c18d9d
JF
4336 break;
4337
0cea0d43
JF
4338 case REQ_EDIT:
4339 if (!status)
4340 return request;
4341
5ba030cf 4342 open_editor(status->status != '?', status->new.name);
0cea0d43
JF
4343 break;
4344
8a680988
JF
4345 case REQ_VIEW_BLAME:
4346 if (status) {
a2d5d9ef 4347 string_copy(opt_file, status->new.name);
8a680988
JF
4348 opt_ref[0] = 0;
4349 }
4350 return request;
4351
88f66e2d 4352 case REQ_ENTER:
17a27c16
JF
4353 /* After returning the status view has been split to
4354 * show the stage view. No further reloading is
4355 * necessary. */
88f66e2d 4356 status_enter(view, line);
17a27c16 4357 return REQ_NONE;
88f66e2d 4358
acaef3b3 4359 case REQ_REFRESH:
17a27c16 4360 /* Simply reload the view. */
acaef3b3
JF
4361 break;
4362
88f66e2d
JF
4363 default:
4364 return request;
4365 }
4366
17a27c16
JF
4367 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4368
88f66e2d
JF
4369 return REQ_NONE;
4370}
4371
ca1d71ea 4372static void
173d76ea
JF
4373status_select(struct view *view, struct line *line)
4374{
11359638
JF
4375 struct status *status = line->data;
4376 char file[SIZEOF_STR] = "all files";
173d76ea 4377 char *text;
b5c18d9d 4378 char *key;
173d76ea 4379
5ba030cf 4380 if (status && !string_format(file, "'%s'", status->new.name))
11359638
JF
4381 return;
4382
4383 if (!status && line[1].type == LINE_STAT_NONE)
4384 line++;
4385
173d76ea
JF
4386 switch (line->type) {
4387 case LINE_STAT_STAGED:
11359638 4388 text = "Press %s to unstage %s for commit";
173d76ea
JF
4389 break;
4390
4391 case LINE_STAT_UNSTAGED:
11359638 4392 text = "Press %s to stage %s for commit";
173d76ea
JF
4393 break;
4394
4395 case LINE_STAT_UNTRACKED:
11359638 4396 text = "Press %s to stage %s for addition";
173d76ea
JF
4397 break;
4398
f5a5e640 4399 case LINE_STAT_HEAD:
173d76ea 4400 case LINE_STAT_NONE:
11359638
JF
4401 text = "Nothing to update";
4402 break;
173d76ea
JF
4403
4404 default:
e77aa656 4405 die("line type %d not handled in switch", line->type);
173d76ea
JF
4406 }
4407
b5c18d9d
JF
4408 if (status && status->status == 'U') {
4409 text = "Press %s to resolve conflict in %s";
4410 key = get_key(REQ_STATUS_MERGE);
4411
4412 } else {
4413 key = get_key(REQ_STATUS_UPDATE);
4414 }
4415
4416 string_format(view->ref, text, key, file);
173d76ea
JF
4417}
4418
4419static bool
4420status_grep(struct view *view, struct line *line)
4421{
4422 struct status *status = line->data;
4423 enum { S_STATUS, S_NAME, S_END } state;
4424 char buf[2] = "?";
4425 regmatch_t pmatch;
4426
4427 if (!status)
4428 return FALSE;
4429
4430 for (state = S_STATUS; state < S_END; state++) {
4431 char *text;
4432
4433 switch (state) {
5ba030cf 4434 case S_NAME: text = status->new.name; break;
173d76ea
JF
4435 case S_STATUS:
4436 buf[0] = status->status;
4437 text = buf;
4438 break;
4439
4440 default:
4441 return FALSE;
4442 }
4443
4444 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4445 return TRUE;
4446 }
4447
4448 return FALSE;
4449}
4450
4451static struct view_ops status_ops = {
4452 "file",
4453 status_open,
4454 NULL,
4455 status_draw,
586c423d 4456 status_request,
173d76ea
JF
4457 status_grep,
4458 status_select,
4459};
4460
b33611d8
JF
4461
4462static bool
4463stage_diff_line(FILE *pipe, struct line *line)
4464{
4465 char *buf = line->data;
4466 size_t bufsize = strlen(buf);
4467 size_t written = 0;
4468
4469 while (!ferror(pipe) && written < bufsize) {
4470 written += fwrite(buf + written, 1, bufsize - written, pipe);
4471 }
4472
4473 fputc('\n', pipe);
4474
4475 return written == bufsize;
4476}
4477
23491842
JF
4478static bool
4479stage_diff_write(FILE *pipe, struct line *line, struct line *end)
b33611d8 4480{
23491842
JF
4481 while (line < end) {
4482 if (!stage_diff_line(pipe, line++))
4483 return FALSE;
4484 if (line->type == LINE_DIFF_CHUNK ||
4485 line->type == LINE_DIFF_HEADER)
4486 break;
4487 }
b33611d8 4488
23491842
JF
4489 return TRUE;
4490}
b33611d8 4491
23491842
JF
4492static struct line *
4493stage_diff_find(struct view *view, struct line *line, enum line_type type)
4494{
4495 for (; view->line < line; line--)
4496 if (line->type == type)
4497 return line;
b33611d8
JF
4498
4499 return NULL;
4500}
4501
4502static bool
23491842 4503stage_update_chunk(struct view *view, struct line *chunk)
b33611d8
JF
4504{
4505 char cmd[SIZEOF_STR];
4506 size_t cmdsize = 0;
23491842 4507 struct line *diff_hdr;
b33611d8
JF
4508 FILE *pipe;
4509
23491842 4510 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
b33611d8
JF
4511 if (!diff_hdr)
4512 return FALSE;
4513
4514 if (opt_cdup[0] &&
4515 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4516 return FALSE;
4517
4518 if (!string_format_from(cmd, &cmdsize,
bc02ff85 4519 "git apply --whitespace=nowarn --cached %s - && "
b33611d8
JF
4520 "git update-index -q --unmerged --refresh 2>/dev/null",
4521 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4522 return FALSE;
4523
4524 pipe = popen(cmd, "w");
4525 if (!pipe)
4526 return FALSE;
4527
23491842
JF
4528 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4529 !stage_diff_write(pipe, chunk, view->line + view->lines))
4530 chunk = NULL;
b33611d8
JF
4531
4532 pclose(pipe);
4533
23491842 4534 return chunk ? TRUE : FALSE;
b33611d8
JF
4535}
4536
61e39818 4537static bool
b33611d8
JF
4538stage_update(struct view *view, struct line *line)
4539{
23491842
JF
4540 struct line *chunk = NULL;
4541
4542 if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4543 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4544
4545 if (chunk) {
4546 if (!stage_update_chunk(view, chunk)) {
b33611d8 4547 report("Failed to apply chunk");
61e39818 4548 return FALSE;
b33611d8
JF
4549 }
4550
cbbd7f62 4551 } else if (!status_update_file(&stage_status, stage_line_type)) {
b33611d8 4552 report("Failed to update file");
61e39818 4553 return FALSE;
b33611d8
JF
4554 }
4555
61e39818 4556 return TRUE;
b33611d8
JF
4557}
4558
4559static enum request
4560stage_request(struct view *view, enum request request, struct line *line)
4561{
4562 switch (request) {
4563 case REQ_STATUS_UPDATE:
4564 stage_update(view, line);
4565 break;
4566
4567 case REQ_EDIT:
5ba030cf 4568 if (!stage_status.new.name[0])
b33611d8
JF
4569 return request;
4570
5ba030cf 4571 open_editor(stage_status.status != '?', stage_status.new.name);
b33611d8
JF
4572 break;
4573
61e39818
JF
4574 case REQ_REFRESH:
4575 /* Reload everything ... */
4576 break;
4577
8a680988
JF
4578 case REQ_VIEW_BLAME:
4579 if (stage_status.new.name[0]) {
a2d5d9ef 4580 string_copy(opt_file, stage_status.new.name);
8a680988
JF
4581 opt_ref[0] = 0;
4582 }
4583 return request;
4584
b33611d8 4585 case REQ_ENTER:
61e39818 4586 return pager_request(view, request, line);
b33611d8
JF
4587
4588 default:
4589 return request;
4590 }
4591
61e39818
JF
4592 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4593
4594 /* Check whether the staged entry still exists, and close the
4595 * stage view if it doesn't. */
4596 if (!status_exists(&stage_status, stage_line_type))
4597 return REQ_VIEW_CLOSE;
4598
4599 if (stage_line_type == LINE_STAT_UNTRACKED)
4600 opt_pipe = fopen(stage_status.new.name, "r");
4601 else
4602 string_copy(opt_cmd, view->cmd);
4603 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4604
b33611d8
JF
4605 return REQ_NONE;
4606}
4607
3e634113
JF
4608static struct view_ops stage_ops = {
4609 "line",
4610 NULL,
4611 pager_read,
4612 pager_draw,
b33611d8 4613 stage_request,
3e634113
JF
4614 pager_grep,
4615 pager_select,
4616};
173d76ea 4617
b33611d8 4618
173d76ea 4619/*
ccc33449 4620 * Revision graph
ff26aa29
JF
4621 */
4622
4623struct commit {
10446330 4624 char id[SIZEOF_REV]; /* SHA1 ID. */
aea510c8 4625 char title[128]; /* First line of the commit message. */
54efb62b
JF
4626 char author[75]; /* Author of the commit. */
4627 struct tm time; /* Date from the author ident. */
4628 struct ref **refs; /* Repository references. */
4629 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
4630 size_t graph_size; /* The width of the graph array. */
d3b19ca4 4631 bool has_parents; /* Rewritten --parents seen. */
ff26aa29 4632};
c34d9c9f 4633
ccc33449
JF
4634/* Size of rev graph with no "padding" columns */
4635#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
2b757533 4636
2ce5c87c
JF
4637struct rev_graph {
4638 struct rev_graph *prev, *next, *parents;
2b757533
JF
4639 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4640 size_t size;
88757ebd
JF
4641 struct commit *commit;
4642 size_t pos;
e81e9c2c 4643 unsigned int boundary:1;
2b757533
JF
4644};
4645
2b757533 4646/* Parents of the commit being visualized. */
446a5c36 4647static struct rev_graph graph_parents[4];
c8d60a25 4648
c65a501a 4649/* The current stack of revisions on the graph. */
446a5c36
JF
4650static struct rev_graph graph_stacks[4] = {
4651 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
c65a501a 4652 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
446a5c36
JF
4653 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4654 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
c65a501a
JF
4655};
4656
9e43b9cd 4657static inline bool
2ce5c87c 4658graph_parent_is_merge(struct rev_graph *graph)
9e43b9cd
JF
4659{
4660 return graph->parents->size > 1;
4661}
4662
88757ebd 4663static inline void
2ce5c87c 4664append_to_rev_graph(struct rev_graph *graph, chtype symbol)
88757ebd 4665{
2c27faac
JF
4666 struct commit *commit = graph->commit;
4667
4668 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4669 commit->graph[commit->graph_size++] = symbol;
88757ebd
JF
4670}
4671
2b757533 4672static void
2ce5c87c 4673done_rev_graph(struct rev_graph *graph)
987890af
JF
4674{
4675 if (graph_parent_is_merge(graph) &&
4676 graph->pos < graph->size - 1 &&
4677 graph->next->size == graph->size + graph->parents->size - 1) {
4678 size_t i = graph->pos + graph->parents->size - 1;
4679
4680 graph->commit->graph_size = i * 2;
4681 while (i < graph->next->size - 1) {
4682 append_to_rev_graph(graph, ' ');
4683 append_to_rev_graph(graph, '\\');
4684 i++;
4685 }
4686 }
4687
4688 graph->size = graph->pos = 0;
4689 graph->commit = NULL;
4690 memset(graph->parents, 0, sizeof(*graph->parents));
4691}
4692
4693static void
2ce5c87c 4694push_rev_graph(struct rev_graph *graph, char *parent)
2b757533 4695{
2fe894e6
JF
4696 int i;
4697
4698 /* "Collapse" duplicate parents lines.
4699 *
4700 * FIXME: This needs to also update update the drawn graph but
4701 * for now it just serves as a method for pruning graph lines. */
4702 for (i = 0; i < graph->size; i++)
4703 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4704 return;
2b757533 4705
2ce5c87c 4706 if (graph->size < SIZEOF_REVITEMS) {
739e81de 4707 string_copy_rev(graph->rev[graph->size++], parent);
2b757533
JF
4708 }
4709}
4710
92507a24
JF
4711static chtype
4712get_rev_graph_symbol(struct rev_graph *graph)
2b757533 4713{
92507a24 4714 chtype symbol;
2b757533 4715
e81e9c2c
JF
4716 if (graph->boundary)
4717 symbol = REVGRAPH_BOUND;
4718 else if (graph->parents->size == 0)
c8d60a25 4719 symbol = REVGRAPH_INIT;
18ffaa23 4720 else if (graph_parent_is_merge(graph))
c8d60a25 4721 symbol = REVGRAPH_MERGE;
c65a501a 4722 else if (graph->pos >= graph->size)
c8d60a25 4723 symbol = REVGRAPH_BRANCH;
2b757533 4724 else
c8d60a25 4725 symbol = REVGRAPH_COMMIT;
1dcb3bec 4726
92507a24
JF
4727 return symbol;
4728}
4729
4730static void
4731draw_rev_graph(struct rev_graph *graph)
4732{
e937c2c8
JF
4733 struct rev_filler {
4734 chtype separator, line;
4735 };
4736 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4737 static struct rev_filler fillers[] = {
4738 { ' ', REVGRAPH_LINE },
4739 { '`', '.' },
4740 { '\'', ' ' },
4741 { '/', ' ' },
e937c2c8 4742 };
92507a24 4743 chtype symbol = get_rev_graph_symbol(graph);
e937c2c8 4744 struct rev_filler *filler;
92507a24
JF
4745 size_t i;
4746
e937c2c8 4747 filler = &fillers[DEFAULT];
110e948e 4748
c65a501a 4749 for (i = 0; i < graph->pos; i++) {
e937c2c8 4750 append_to_rev_graph(graph, filler->line);
9e43b9cd 4751 if (graph_parent_is_merge(graph->prev) &&
e937c2c8
JF
4752 graph->prev->pos == i)
4753 filler = &fillers[RSHARP];
4754
4755 append_to_rev_graph(graph, filler->separator);
110e948e
JF
4756 }
4757
92507a24 4758 /* Place the symbol for this revision. */
c65a501a 4759 append_to_rev_graph(graph, symbol);
2b757533 4760
e937c2c8
JF
4761 if (graph->prev->size > graph->size)
4762 filler = &fillers[RDIAG];
4763 else
4764 filler = &fillers[DEFAULT];
4765
c8d60a25 4766 i++;
2b757533 4767
c65a501a 4768 for (; i < graph->size; i++) {
e937c2c8
JF
4769 append_to_rev_graph(graph, filler->separator);
4770 append_to_rev_graph(graph, filler->line);
4771 if (graph_parent_is_merge(graph->prev) &&
4772 i < graph->prev->pos + graph->parents->size)
4773 filler = &fillers[RSHARP];
4774 if (graph->prev->size > graph->size)
4775 filler = &fillers[LDIAG];
c65a501a
JF
4776 }
4777
4778 if (graph->prev->size > graph->size) {
e937c2c8
JF
4779 append_to_rev_graph(graph, filler->separator);
4780 if (filler->line != ' ')
4781 append_to_rev_graph(graph, filler->line);
2b757533 4782 }
b5d8f208
JF
4783}
4784
61eed810
JF
4785/* Prepare the next rev graph */
4786static void
4787prepare_rev_graph(struct rev_graph *graph)
b5d8f208 4788{
b5d8f208
JF
4789 size_t i;
4790
320df4ea 4791 /* First, traverse all lines of revisions up to the active one. */
c65a501a
JF
4792 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4793 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
b5d8f208 4794 break;
b5d8f208 4795
2ce5c87c 4796 push_rev_graph(graph->next, graph->rev[graph->pos]);
b5d8f208
JF
4797 }
4798
320df4ea 4799 /* Interleave the new revision parent(s). */
e81e9c2c 4800 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
2ce5c87c 4801 push_rev_graph(graph->next, graph->parents->rev[i]);
b5d8f208 4802
320df4ea 4803 /* Lastly, put any remaining revisions. */
c65a501a 4804 for (i = graph->pos + 1; i < graph->size; i++)
2ce5c87c 4805 push_rev_graph(graph->next, graph->rev[i]);
61eed810
JF
4806}
4807
4808static void
4809update_rev_graph(struct rev_graph *graph)
4810{
446a5c36
JF
4811 /* If this is the finalizing update ... */
4812 if (graph->commit)
4813 prepare_rev_graph(graph);
4814
4815 /* Graph visualization needs a one rev look-ahead,
4816 * so the first update doesn't visualize anything. */
4817 if (!graph->prev->commit)
4818 return;
c65a501a 4819
61eed810
JF
4820 draw_rev_graph(graph->prev);
4821 done_rev_graph(graph->prev->prev);
2b757533
JF
4822}
4823
ccc33449
JF
4824
4825/*
4826 * Main view backend
4827 */
4828
4829static bool
4830main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4831{
4832 char buf[DATE_COLS + 1];
4833 struct commit *commit = line->data;
4834 enum line_type type;
4835 int col = 0;
4836 size_t timelen;
1c919d68 4837 int space;
ccc33449
JF
4838
4839 if (!*commit->author)
4840 return FALSE;
4841
1c919d68 4842 space = view->width;
ccc33449
JF
4843 wmove(view->win, lineno, col);
4844
4845 if (selected) {
4846 type = LINE_CURSOR;
4847 wattrset(view->win, get_line_attr(type));
4848 wchgat(view->win, -1, 0, type, NULL);
ccc33449
JF
4849 } else {
4850 type = LINE_MAIN_COMMIT;
4851 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4852 }
4853
823057f4 4854 if (opt_date) {
1c919d68 4855 int n;
ccc33449 4856
1c919d68 4857 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
de46362f
JF
4858 n = draw_text(view, buf, view->width - col, FALSE, selected);
4859 draw_text(view, " ", view->width - col - n, FALSE, selected);
ccc33449 4860
1c919d68
DV
4861 col += DATE_COLS;
4862 wmove(view->win, lineno, col);
4863 if (col >= view->width)
4864 return TRUE;
ccc33449 4865 }
1c919d68
DV
4866 if (type != LINE_CURSOR)
4867 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
ccc33449 4868
823057f4 4869 if (opt_author) {
1c919d68
DV
4870 int max_len;
4871
4872 max_len = view->width - col;
4873 if (max_len > AUTHOR_COLS - 1)
4874 max_len = AUTHOR_COLS - 1;
de46362f 4875 draw_text(view, commit->author, max_len, TRUE, selected);
1c919d68
DV
4876 col += AUTHOR_COLS;
4877 if (col >= view->width)
4878 return TRUE;
ccc33449
JF
4879 }
4880
ccc33449 4881 if (opt_rev_graph && commit->graph_size) {
749cdc92 4882 size_t graph_size = view->width - col;
ccc33449
JF
4883 size_t i;
4884
f83b1c33
DV
4885 if (type != LINE_CURSOR)
4886 wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
ccc33449 4887 wmove(view->win, lineno, col);
749cdc92
JF
4888 if (graph_size > commit->graph_size)
4889 graph_size = commit->graph_size;
ccc33449
JF
4890 /* Using waddch() instead of waddnstr() ensures that
4891 * they'll be rendered correctly for the cursor line. */
749cdc92 4892 for (i = 0; i < graph_size; i++)
ccc33449
JF
4893 waddch(view->win, commit->graph[i]);
4894
4895 col += commit->graph_size + 1;
1c919d68
DV
4896 if (col >= view->width)
4897 return TRUE;
749cdc92 4898 waddch(view->win, ' ');
ccc33449 4899 }
f83b1c33
DV
4900 if (type != LINE_CURSOR)
4901 wattrset(view->win, A_NORMAL);
ccc33449
JF
4902
4903 wmove(view->win, lineno, col);
4904
823057f4 4905 if (opt_show_refs && commit->refs) {
ccc33449
JF
4906 size_t i = 0;
4907
4908 do {
4909 if (type == LINE_CURSOR)
4910 ;
70ea8175
JF
4911 else if (commit->refs[i]->head)
4912 wattrset(view->win, get_line_attr(LINE_MAIN_HEAD));
2384880b
DV
4913 else if (commit->refs[i]->ltag)
4914 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
ccc33449
JF
4915 else if (commit->refs[i]->tag)
4916 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
8b3475e6
JF
4917 else if (commit->refs[i]->tracked)
4918 wattrset(view->win, get_line_attr(LINE_MAIN_TRACKED));
e15ec88e
JF
4919 else if (commit->refs[i]->remote)
4920 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
ccc33449
JF
4921 else
4922 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1c919d68 4923
de46362f 4924 col += draw_text(view, "[", view->width - col, TRUE, selected);
a00fff3c 4925 col += draw_text(view, commit->refs[i]->name, view->width - col,
de46362f
JF
4926 TRUE, selected);
4927 col += draw_text(view, "]", view->width - col, TRUE, selected);
ccc33449
JF
4928 if (type != LINE_CURSOR)
4929 wattrset(view->win, A_NORMAL);
de46362f 4930 col += draw_text(view, " ", view->width - col, TRUE, selected);
1c919d68
DV
4931 if (col >= view->width)
4932 return TRUE;
ccc33449
JF
4933 } while (commit->refs[i++]->next);
4934 }
4935
4936 if (type != LINE_CURSOR)
4937 wattrset(view->win, get_line_attr(type));
4938
de46362f 4939 draw_text(view, commit->title, view->width - col, TRUE, selected);
ccc33449
JF
4940 return TRUE;
4941}
4942
4c6fabc2 4943/* Reads git log --pretty=raw output and parses it into the commit struct. */
6b161b31 4944static bool
701e4f5d 4945main_read(struct view *view, char *line)
22f66b0a 4946{
2ce5c87c 4947 static struct rev_graph *graph = graph_stacks;
be04d936 4948 enum line_type type;
0ff3b97c 4949 struct commit *commit;
22f66b0a 4950
be04d936 4951 if (!line) {
3bdbba9a
JF
4952 if (!view->lines && !view->parent)
4953 die("No revisions match the given arguments.");
446a5c36 4954 update_rev_graph(graph);
be04d936
JF
4955 return TRUE;
4956 }
4957
4958 type = get_line_type(line);
0ff3b97c 4959 if (type == LINE_COMMIT) {
22f66b0a
JF
4960 commit = calloc(1, sizeof(struct commit));
4961 if (!commit)
4962 return FALSE;
4963
e81e9c2c
JF
4964 line += STRING_SIZE("commit ");
4965 if (*line == '-') {
4966 graph->boundary = 1;
4967 line++;
4968 }
4969
4970 string_copy_rev(commit->id, line);
c34d9c9f 4971 commit->refs = get_refs(commit->id);
c65a501a 4972 graph->commit = commit;
e314c36d 4973 add_line_data(view, commit, LINE_MAIN_COMMIT);
d3b19ca4
JF
4974
4975 while ((line = strchr(line, ' '))) {
4976 line++;
4977 push_rev_graph(graph->parents, line);
4978 commit->has_parents = TRUE;
4979 }
0ff3b97c
JF
4980 return TRUE;
4981 }
2b757533 4982
0ff3b97c
JF
4983 if (!view->lines)
4984 return TRUE;
4985 commit = view->line[view->lines - 1].data;
4986
4987 switch (type) {
2b757533 4988 case LINE_PARENT:
d3b19ca4
JF
4989 if (commit->has_parents)
4990 break;
0ff3b97c 4991 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
78c70acd 4992 break;
22f66b0a 4993
8855ada4 4994 case LINE_AUTHOR:
b76c2afc 4995 {
19c3ac60
JF
4996 /* Parse author lines where the name may be empty:
4997 * author <email@address.tld> 1138474660 +0100
4998 */
4c6fabc2 4999 char *ident = line + STRING_SIZE("author ");
19c3ac60
JF
5000 char *nameend = strchr(ident, '<');
5001 char *emailend = strchr(ident, '>');
b76c2afc 5002
0ff3b97c 5003 if (!nameend || !emailend)
fe7233c3
JF
5004 break;
5005
c65a501a
JF
5006 update_rev_graph(graph);
5007 graph = graph->next;
2b757533 5008
19c3ac60
JF
5009 *nameend = *emailend = 0;
5010 ident = chomp_string(ident);
5011 if (!*ident) {
5012 ident = chomp_string(nameend + 1);
5013 if (!*ident)
5014 ident = "Unknown";
b76c2afc
JF
5015 }
5016
739e81de 5017 string_ncopy(commit->author, ident, strlen(ident));
b76c2afc 5018
4c6fabc2 5019 /* Parse epoch and timezone */
19c3ac60
JF
5020 if (emailend[1] == ' ') {
5021 char *secs = emailend + 2;
5022 char *zone = strchr(secs, ' ');
5023 time_t time = (time_t) atol(secs);
b76c2afc 5024
4c6fabc2 5025 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
b76c2afc
JF
5026 long tz;
5027
5028 zone++;
5029 tz = ('0' - zone[1]) * 60 * 60 * 10;
5030 tz += ('0' - zone[2]) * 60 * 60;
5031 tz += ('0' - zone[3]) * 60;
5032 tz += ('0' - zone[4]) * 60;
5033
5034 if (zone[0] == '-')
5035 tz = -tz;
5036
5037 time -= tz;
5038 }
19c3ac60 5039
b76c2afc
JF
5040 gmtime_r(&time, &commit->time);
5041 }
5042 break;
5043 }
78c70acd 5044 default:
2e8488b4 5045 /* Fill in the commit title if it has not already been set. */
2e8488b4
JF
5046 if (commit->title[0])
5047 break;
5048
5049 /* Require titles to start with a non-space character at the
5050 * offset used by git log. */
9073c64a
JF
5051 if (strncmp(line, " ", 4))
5052 break;
5053 line += 4;
5054 /* Well, if the title starts with a whitespace character,
5055 * try to be forgiving. Otherwise we end up with no title. */
5056 while (isspace(*line))
5057 line++;
5058 if (*line == '\0')
82e78006 5059 break;
9073c64a
JF
5060 /* FIXME: More graceful handling of titles; append "..." to
5061 * shortened titles, etc. */
82e78006 5062
739e81de 5063 string_ncopy(commit->title, line, strlen(line));
22f66b0a
JF
5064 }
5065
5066 return TRUE;
5067}
5068
586c423d
JF
5069static enum request
5070main_request(struct view *view, enum request request, struct line *line)
b801d8b2 5071{
b3a54cba
JF
5072 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5073
586c423d
JF
5074 if (request == REQ_ENTER)
5075 open_view(view, REQ_VIEW_DIFF, flags);
5076 else
5077 return request;
5078
5079 return REQ_NONE;
b801d8b2
JF
5080}
5081
4af34daa
JF
5082static bool
5083main_grep(struct view *view, struct line *line)
5084{
5085 struct commit *commit = line->data;
5086 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
5087 char buf[DATE_COLS + 1];
5088 regmatch_t pmatch;
5089
5090 for (state = S_TITLE; state < S_END; state++) {
5091 char *text;
5092
5093 switch (state) {
5094 case S_TITLE: text = commit->title; break;
5095 case S_AUTHOR: text = commit->author; break;
5096 case S_DATE:
5097 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5098 continue;
5099 text = buf;
5100 break;
5101
5102 default:
5103 return FALSE;
5104 }
5105
b77b2cb8 5106 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4af34daa
JF
5107 return TRUE;
5108 }
5109
5110 return FALSE;
5111}
5112
d720de4b
JF
5113static void
5114main_select(struct view *view, struct line *line)
5115{
5116 struct commit *commit = line->data;
5117
2463b4ea
JF
5118 string_copy_rev(view->ref, commit->id);
5119 string_copy_rev(ref_commit, view->ref);
d720de4b
JF
5120}
5121
6b161b31 5122static struct view_ops main_ops = {
6734f6b9 5123 "commit",
f098944b 5124 NULL,
6b161b31 5125 main_read,
f098944b 5126 main_draw,
586c423d 5127 main_request,
4af34daa 5128 main_grep,
d720de4b 5129 main_select,
6b161b31 5130};
2e8488b4 5131
c34d9c9f 5132
6b161b31 5133/*
10e290ee
JF
5134 * Unicode / UTF-8 handling
5135 *
5136 * NOTE: Much of the following code for dealing with unicode is derived from
5137 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5138 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5139 */
5140
5141/* I've (over)annotated a lot of code snippets because I am not entirely
5142 * confident that the approach taken by this small UTF-8 interface is correct.
5143 * --jonas */
5144
5145static inline int
5146unicode_width(unsigned long c)
5147{
5148 if (c >= 0x1100 &&
5149 (c <= 0x115f /* Hangul Jamo */
5150 || c == 0x2329
5151 || c == 0x232a
5152 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
f97f4012 5153 /* CJK ... Yi */
10e290ee
JF
5154 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5155 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5156 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5157 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5158 || (c >= 0xffe0 && c <= 0xffe6)
5159 || (c >= 0x20000 && c <= 0x2fffd)
5160 || (c >= 0x30000 && c <= 0x3fffd)))
5161 return 2;
5162
749cdc92
JF
5163 if (c == '\t')
5164 return opt_tab_size;
5165
10e290ee
JF
5166 return 1;
5167}
5168
5169/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5170 * Illegal bytes are set one. */
5171static const unsigned char utf8_bytes[256] = {
5172 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,
5173 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,
5174 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,
5175 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,
5176 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,
5177 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,
5178 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,
5179 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,
5180};
5181
5182/* Decode UTF-8 multi-byte representation into a unicode character. */
5183static inline unsigned long
5184utf8_to_unicode(const char *string, size_t length)
5185{
5186 unsigned long unicode;
5187
5188 switch (length) {
5189 case 1:
5190 unicode = string[0];
5191 break;
5192 case 2:
5193 unicode = (string[0] & 0x1f) << 6;
5194 unicode += (string[1] & 0x3f);
5195 break;
5196 case 3:
5197 unicode = (string[0] & 0x0f) << 12;
5198 unicode += ((string[1] & 0x3f) << 6);
5199 unicode += (string[2] & 0x3f);
5200 break;
5201 case 4:
5202 unicode = (string[0] & 0x0f) << 18;
5203 unicode += ((string[1] & 0x3f) << 12);
5204 unicode += ((string[2] & 0x3f) << 6);
5205 unicode += (string[3] & 0x3f);
5206 break;
5207 case 5:
5208 unicode = (string[0] & 0x0f) << 24;
5209 unicode += ((string[1] & 0x3f) << 18);
5210 unicode += ((string[2] & 0x3f) << 12);
5211 unicode += ((string[3] & 0x3f) << 6);
5212 unicode += (string[4] & 0x3f);
5213 break;
68b6e0eb 5214 case 6:
10e290ee
JF
5215 unicode = (string[0] & 0x01) << 30;
5216 unicode += ((string[1] & 0x3f) << 24);
5217 unicode += ((string[2] & 0x3f) << 18);
5218 unicode += ((string[3] & 0x3f) << 12);
5219 unicode += ((string[4] & 0x3f) << 6);
5220 unicode += (string[5] & 0x3f);
5221 break;
5222 default:
5223 die("Invalid unicode length");
5224 }
5225
5226 /* Invalid characters could return the special 0xfffd value but NUL
5227 * should be just as good. */
5228 return unicode > 0xffff ? 0 : unicode;
5229}
5230
5231/* Calculates how much of string can be shown within the given maximum width
5232 * and sets trimmed parameter to non-zero value if all of string could not be
012e76e9
JF
5233 * shown. If the reserve flag is TRUE, it will reserve at least one
5234 * trailing character, which can be useful when drawing a delimiter.
10e290ee
JF
5235 *
5236 * Returns the number of bytes to output from string to satisfy max_width. */
5237static size_t
012e76e9 5238utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
10e290ee
JF
5239{
5240 const char *start = string;
5241 const char *end = strchr(string, '\0');
012e76e9 5242 unsigned char last_bytes = 0;
10e290ee
JF
5243 size_t width = 0;
5244
5245 *trimmed = 0;
5246
5247 while (string < end) {
5248 int c = *(unsigned char *) string;
5249 unsigned char bytes = utf8_bytes[c];
5250 size_t ucwidth;
5251 unsigned long unicode;
5252
5253 if (string + bytes > end)
5254 break;
5255
5256 /* Change representation to figure out whether
5257 * it is a single- or double-width character. */
5258
5259 unicode = utf8_to_unicode(string, bytes);
5260 /* FIXME: Graceful handling of invalid unicode character. */
5261 if (!unicode)
5262 break;
5263
5264 ucwidth = unicode_width(unicode);
5265 width += ucwidth;
5266 if (width > max_width) {
5267 *trimmed = 1;
012e76e9
JF
5268 if (reserve && width - ucwidth == max_width) {
5269 string -= last_bytes;
5270 }
10e290ee
JF
5271 break;
5272 }
5273
10e290ee 5274 string += bytes;
012e76e9 5275 last_bytes = bytes;
10e290ee
JF
5276 }
5277
10e290ee
JF
5278 return string - start;
5279}
5280
5281
5282/*
6b161b31
JF
5283 * Status management
5284 */
2e8488b4 5285
8855ada4 5286/* Whether or not the curses interface has been initialized. */
68b6e0eb 5287static bool cursed = FALSE;
8855ada4 5288
6b161b31
JF
5289/* The status window is used for polling keystrokes. */
5290static WINDOW *status_win;
4a2909a7 5291
21be28fb
JF
5292static bool status_empty = TRUE;
5293
2e8488b4 5294/* Update status and title window. */
4a2909a7
JF
5295static void
5296report(const char *msg, ...)
5297{
6706b2ba 5298 struct view *view = display[current_view];
b76c2afc 5299
ab4af23e
JF
5300 if (input_mode)
5301 return;
5302
c38c64bb
JF
5303 if (!view) {
5304 char buf[SIZEOF_STR];
5305 va_list args;
5306
5307 va_start(args, msg);
5308 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5309 buf[sizeof(buf) - 1] = 0;
5310 buf[sizeof(buf) - 2] = '.';
5311 buf[sizeof(buf) - 3] = '.';
5312 buf[sizeof(buf) - 4] = '.';
5313 }
5314 va_end(args);
5315 die("%s", buf);
5316 }
5317
21be28fb 5318 if (!status_empty || *msg) {
6706b2ba 5319 va_list args;
4a2909a7 5320
6706b2ba 5321 va_start(args, msg);
4b76734f 5322
6706b2ba
JF
5323 wmove(status_win, 0, 0);
5324 if (*msg) {
5325 vwprintw(status_win, msg, args);
21be28fb 5326 status_empty = FALSE;
6706b2ba 5327 } else {
21be28fb 5328 status_empty = TRUE;
6706b2ba 5329 }
390a8262 5330 wclrtoeol(status_win);
6706b2ba 5331 wrefresh(status_win);
b801d8b2 5332
6706b2ba
JF
5333 va_end(args);
5334 }
5335
5336 update_view_title(view);
2bee3bde 5337 update_display_cursor(view);
b801d8b2
JF
5338}
5339
6b161b31
JF
5340/* Controls when nodelay should be in effect when polling user input. */
5341static void
1ba2ae4b 5342set_nonblocking_input(bool loading)
b801d8b2 5343{
6706b2ba 5344 static unsigned int loading_views;
b801d8b2 5345
6706b2ba
JF
5346 if ((loading == FALSE && loading_views-- == 1) ||
5347 (loading == TRUE && loading_views++ == 0))
1ba2ae4b 5348 nodelay(status_win, loading);
6b161b31
JF
5349}
5350
5351static void
5352init_display(void)
5353{
5354 int x, y;
b76c2afc 5355
6908bdbd
JF
5356 /* Initialize the curses library */
5357 if (isatty(STDIN_FILENO)) {
8855ada4 5358 cursed = !!initscr();
6908bdbd
JF
5359 } else {
5360 /* Leave stdin and stdout alone when acting as a pager. */
5361 FILE *io = fopen("/dev/tty", "r+");
5362
e6f60674
JF
5363 if (!io)
5364 die("Failed to open /dev/tty");
8855ada4 5365 cursed = !!newterm(NULL, io, io);
6908bdbd
JF
5366 }
5367
8855ada4
JF
5368 if (!cursed)
5369 die("Failed to initialize curses");
5370
2e8488b4
JF
5371 nonl(); /* Tell curses not to do NL->CR/NL on output */
5372 cbreak(); /* Take input chars one at a time, no wait for \n */
5373 noecho(); /* Don't echo input */
b801d8b2 5374 leaveok(stdscr, TRUE);
b801d8b2
JF
5375
5376 if (has_colors())
5377 init_colors();
5378
5379 getmaxyx(stdscr, y, x);
5380 status_win = newwin(1, 0, y - 1, 0);
5381 if (!status_win)
5382 die("Failed to create status window");
5383
5384 /* Enable keyboard mapping */
5385 keypad(status_win, TRUE);
78c70acd 5386 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6b161b31
JF
5387}
5388
4af34daa 5389static char *
cb9e48c1 5390read_prompt(const char *prompt)
ef5404a4
JF
5391{
5392 enum { READING, STOP, CANCEL } status = READING;
9e21ce5c 5393 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
ef5404a4
JF
5394 int pos = 0;
5395
5396 while (status == READING) {
5397 struct view *view;
5398 int i, key;
5399
ab4af23e
JF
5400 input_mode = TRUE;
5401
699ae55b 5402 foreach_view (view, i)
ef5404a4
JF
5403 update_view(view);
5404
ab4af23e
JF
5405 input_mode = FALSE;
5406
5407 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5408 wclrtoeol(status_win);
5409
ef5404a4
JF
5410 /* Refresh, accept single keystroke of input */
5411 key = wgetch(status_win);
5412 switch (key) {
5413 case KEY_RETURN:
5414 case KEY_ENTER:
5415 case '\n':
5416 status = pos ? STOP : CANCEL;
5417 break;
5418
5419 case KEY_BACKSPACE:
5420 if (pos > 0)
5421 pos--;
5422 else
5423 status = CANCEL;
5424 break;
5425
5426 case KEY_ESC:
5427 status = CANCEL;
5428 break;
5429
5430 case ERR:
5431 break;
5432
5433 default:
5434 if (pos >= sizeof(buf)) {
5435 report("Input string too long");
9e21ce5c 5436 return NULL;
ef5404a4
JF
5437 }
5438
5439 if (isprint(key))
5440 buf[pos++] = (char) key;
5441 }
5442 }
5443
7a06ebdf
JF
5444 /* Clear the status window */
5445 status_empty = FALSE;
5446 report("");
5447
5448 if (status == CANCEL)
9e21ce5c 5449 return NULL;
ef5404a4
JF
5450
5451 buf[pos++] = 0;
ef5404a4 5452
9e21ce5c 5453 return buf;
ef5404a4 5454}
c34d9c9f
JF
5455
5456/*
5457 * Repository references
5458 */
5459
518234f1
DV
5460static struct ref *refs = NULL;
5461static size_t refs_alloc = 0;
5462static size_t refs_size = 0;
c34d9c9f 5463
1307df1a 5464/* Id <-> ref store */
518234f1
DV
5465static struct ref ***id_refs = NULL;
5466static size_t id_refs_alloc = 0;
5467static size_t id_refs_size = 0;
1307df1a 5468
c34d9c9f
JF
5469static struct ref **
5470get_refs(char *id)
5471{
1307df1a
JF
5472 struct ref ***tmp_id_refs;
5473 struct ref **ref_list = NULL;
518234f1 5474 size_t ref_list_alloc = 0;
1307df1a 5475 size_t ref_list_size = 0;
c34d9c9f
JF
5476 size_t i;
5477
1307df1a
JF
5478 for (i = 0; i < id_refs_size; i++)
5479 if (!strcmp(id, id_refs[i][0]->id))
5480 return id_refs[i];
5481
518234f1
DV
5482 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5483 sizeof(*id_refs));
1307df1a
JF
5484 if (!tmp_id_refs)
5485 return NULL;
5486
5487 id_refs = tmp_id_refs;
5488
c34d9c9f
JF
5489 for (i = 0; i < refs_size; i++) {
5490 struct ref **tmp;
5491
5492 if (strcmp(id, refs[i].id))
5493 continue;
5494
518234f1
DV
5495 tmp = realloc_items(ref_list, &ref_list_alloc,
5496 ref_list_size + 1, sizeof(*ref_list));
c34d9c9f 5497 if (!tmp) {
1307df1a
JF
5498 if (ref_list)
5499 free(ref_list);
c34d9c9f
JF
5500 return NULL;
5501 }
5502
1307df1a
JF
5503 ref_list = tmp;
5504 if (ref_list_size > 0)
5505 ref_list[ref_list_size - 1]->next = 1;
5506 ref_list[ref_list_size] = &refs[i];
3af8774e
JF
5507
5508 /* XXX: The properties of the commit chains ensures that we can
5509 * safely modify the shared ref. The repo references will
5510 * always be similar for the same id. */
1307df1a
JF
5511 ref_list[ref_list_size]->next = 0;
5512 ref_list_size++;
c34d9c9f
JF
5513 }
5514
1307df1a
JF
5515 if (ref_list)
5516 id_refs[id_refs_size++] = ref_list;
5517
5518 return ref_list;
c34d9c9f
JF
5519}
5520
5521static int
5699e0cf 5522read_ref(char *id, size_t idlen, char *name, size_t namelen)
c34d9c9f 5523{
d0cea5f9
JF
5524 struct ref *ref;
5525 bool tag = FALSE;
2384880b 5526 bool ltag = FALSE;
e15ec88e 5527 bool remote = FALSE;
8b3475e6 5528 bool tracked = FALSE;
2384880b 5529 bool check_replace = FALSE;
70ea8175 5530 bool head = FALSE;
d0cea5f9 5531
8b0297ae 5532 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2384880b
DV
5533 if (!strcmp(name + namelen - 3, "^{}")) {
5534 namelen -= 3;
5535 name[namelen] = 0;
5536 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5537 check_replace = TRUE;
5538 } else {
5539 ltag = TRUE;
5540 }
c34d9c9f 5541
d0cea5f9 5542 tag = TRUE;
8b0297ae
JF
5543 namelen -= STRING_SIZE("refs/tags/");
5544 name += STRING_SIZE("refs/tags/");
c34d9c9f 5545
e15ec88e
JF
5546 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5547 remote = TRUE;
5548 namelen -= STRING_SIZE("refs/remotes/");
5549 name += STRING_SIZE("refs/remotes/");
8b3475e6 5550 tracked = !strcmp(opt_remote, name);
e15ec88e 5551
d0cea5f9 5552 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
8b0297ae
JF
5553 namelen -= STRING_SIZE("refs/heads/");
5554 name += STRING_SIZE("refs/heads/");
70ea8175 5555 head = !strncmp(opt_head, name, namelen);
c34d9c9f 5556
d0cea5f9 5557 } else if (!strcmp(name, "HEAD")) {
22d9b77c 5558 opt_no_head = FALSE;
d0cea5f9
JF
5559 return OK;
5560 }
6706b2ba 5561
2384880b
DV
5562 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5563 /* it's an annotated tag, replace the previous sha1 with the
5564 * resolved commit id; relies on the fact git-ls-remote lists
5565 * the commit id of an annotated tag right beofre the commit id
5566 * it points to. */
5567 refs[refs_size - 1].ltag = ltag;
5568 string_copy_rev(refs[refs_size - 1].id, id);
5569
5570 return OK;
5571 }
518234f1 5572 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
d0cea5f9
JF
5573 if (!refs)
5574 return ERR;
c34d9c9f 5575
d0cea5f9 5576 ref = &refs[refs_size++];
8b0297ae 5577 ref->name = malloc(namelen + 1);
d0cea5f9
JF
5578 if (!ref->name)
5579 return ERR;
3af8774e 5580
8b0297ae
JF
5581 strncpy(ref->name, name, namelen);
5582 ref->name[namelen] = 0;
8b3475e6 5583 ref->head = head;
d0cea5f9 5584 ref->tag = tag;
2384880b 5585 ref->ltag = ltag;
e15ec88e 5586 ref->remote = remote;
8b3475e6 5587 ref->tracked = tracked;
2463b4ea 5588 string_copy_rev(ref->id, id);
3af8774e 5589
d0cea5f9
JF
5590 return OK;
5591}
c34d9c9f 5592
d0cea5f9
JF
5593static int
5594load_refs(void)
5595{
5596 const char *cmd_env = getenv("TIG_LS_REMOTE");
5597 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
c34d9c9f 5598
4a63c884 5599 return read_properties(popen(cmd, "r"), "\t", read_ref);
d0cea5f9 5600}
c34d9c9f 5601
d0cea5f9 5602static int
5699e0cf 5603read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
d0cea5f9 5604{
22913179 5605 if (!strcmp(name, "i18n.commitencoding"))
739e81de 5606 string_ncopy(opt_encoding, value, valuelen);
c34d9c9f 5607
0cea0d43
JF
5608 if (!strcmp(name, "core.editor"))
5609 string_ncopy(opt_editor, value, valuelen);
5610
8b3475e6
JF
5611 /* branch.<head>.remote */
5612 if (*opt_head &&
5613 !strncmp(name, "branch.", 7) &&
5614 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5615 !strcmp(name + 7 + strlen(opt_head), ".remote"))
5616 string_ncopy(opt_remote, value, valuelen);
5617
5618 if (*opt_head && *opt_remote &&
5619 !strncmp(name, "branch.", 7) &&
5620 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5621 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5622 size_t from = strlen(opt_remote);
5623
5624 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5625 value += STRING_SIZE("refs/heads/");
5626 valuelen -= STRING_SIZE("refs/heads/");
5627 }
5628
5629 if (!string_format_from(opt_remote, &from, "/%s", value))
5630 opt_remote[0] = 0;
5631 }
5632
c34d9c9f
JF
5633 return OK;
5634}
5635
4670cf89 5636static int
417ae6d7 5637load_git_config(void)
4670cf89 5638{
96e58f5b 5639 return read_properties(popen(GIT_CONFIG " --list", "r"),
14c778a6 5640 "=", read_repo_config_option);
d0cea5f9
JF
5641}
5642
5643static int
5699e0cf 5644read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
91c5d983 5645{
c38c64bb 5646 if (!opt_git_dir[0]) {
810f0078 5647 string_ncopy(opt_git_dir, name, namelen);
c38c64bb
JF
5648
5649 } else if (opt_is_inside_work_tree == -1) {
5650 /* This can be 3 different values depending on the
5651 * version of git being used. If git-rev-parse does not
5652 * understand --is-inside-work-tree it will simply echo
5653 * the option else either "true" or "false" is printed.
5654 * Default to true for the unknown case. */
5655 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5656
70ea8175 5657 } else if (opt_cdup[0] == ' ') {
739e81de 5658 string_ncopy(opt_cdup, name, namelen);
70ea8175
JF
5659 } else {
5660 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5661 namelen -= STRING_SIZE("refs/heads/");
5662 name += STRING_SIZE("refs/heads/");
5663 string_ncopy(opt_head, name, namelen);
5664 }
c38c64bb
JF
5665 }
5666
91c5d983
JF
5667 return OK;
5668}
5669
5670static int
5671load_repo_info(void)
5672{
70ea8175 5673 int result;
3704f96b
JF
5674 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5675 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
70ea8175
JF
5676
5677 /* XXX: The line outputted by "--show-cdup" can be empty so
5678 * initialize it to something invalid to make it possible to
5679 * detect whether it has been set or not. */
5680 opt_cdup[0] = ' ';
5681
5682 result = read_properties(pipe, "=", read_repo_info);
5683 if (opt_cdup[0] == ' ')
5684 opt_cdup[0] = 0;
5685
5686 return result;
91c5d983
JF
5687}
5688
5689static int
4a63c884 5690read_properties(FILE *pipe, const char *separators,
5699e0cf 5691 int (*read_property)(char *, size_t, char *, size_t))
d0cea5f9 5692{
4670cf89
JF
5693 char buffer[BUFSIZ];
5694 char *name;
d0cea5f9 5695 int state = OK;
4670cf89
JF
5696
5697 if (!pipe)
5698 return ERR;
5699
d0cea5f9 5700 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4a63c884
JF
5701 char *value;
5702 size_t namelen;
5703 size_t valuelen;
4670cf89 5704
4a63c884
JF
5705 name = chomp_string(name);
5706 namelen = strcspn(name, separators);
5707
5708 if (name[namelen]) {
5709 name[namelen] = 0;
5710 value = chomp_string(name + namelen + 1);
d0cea5f9 5711 valuelen = strlen(value);
4670cf89 5712
d0cea5f9 5713 } else {
d0cea5f9
JF
5714 value = "";
5715 valuelen = 0;
4670cf89 5716 }
d0cea5f9 5717
3c3801c2 5718 state = read_property(name, namelen, value, valuelen);
4670cf89
JF
5719 }
5720
d0cea5f9
JF
5721 if (state != ERR && ferror(pipe))
5722 state = ERR;
4670cf89
JF
5723
5724 pclose(pipe);
5725
d0cea5f9 5726 return state;
4670cf89
JF
5727}
5728
d0cea5f9 5729
6b161b31
JF
5730/*
5731 * Main
5732 */
5733
b5c9e67f 5734static void __NORETURN
6b161b31
JF
5735quit(int sig)
5736{
8855ada4
JF
5737 /* XXX: Restore tty modes and let the OS cleanup the rest! */
5738 if (cursed)
5739 endwin();
6b161b31
JF
5740 exit(0);
5741}
5742
c6704a4e
JF
5743static void __NORETURN
5744die(const char *err, ...)
6b161b31
JF
5745{
5746 va_list args;
5747
5748 endwin();
5749
5750 va_start(args, err);
5751 fputs("tig: ", stderr);
5752 vfprintf(stderr, err, args);
5753 fputs("\n", stderr);
5754 va_end(args);
5755
5756 exit(1);
5757}
5758
77452abc
JF
5759static void
5760warn(const char *msg, ...)
5761{
5762 va_list args;
5763
5764 va_start(args, msg);
5765 fputs("tig warning: ", stderr);
5766 vfprintf(stderr, msg, args);
5767 fputs("\n", stderr);
5768 va_end(args);
5769}
5770
6b161b31
JF
5771int
5772main(int argc, char *argv[])
5773{
1ba2ae4b 5774 struct view *view;
6b161b31 5775 enum request request;
1ba2ae4b 5776 size_t i;
6b161b31
JF
5777
5778 signal(SIGINT, quit);
5779
6b68fd24 5780 if (setlocale(LC_ALL, "")) {
739e81de
JF
5781 char *codeset = nl_langinfo(CODESET);
5782
5783 string_ncopy(opt_codeset, codeset, strlen(codeset));
6b68fd24
JF
5784 }
5785
e0f50df0
JF
5786 if (load_repo_info() == ERR)
5787 die("Failed to load repo info.");
5788
660e09ad
JF
5789 if (load_options() == ERR)
5790 die("Failed to load user config.");
5791
417ae6d7 5792 if (load_git_config() == ERR)
afdc35b3 5793 die("Failed to load repo config.");
91c5d983 5794
8855ada4 5795 if (!parse_options(argc, argv))
6b161b31
JF
5796 return 0;
5797
58a5e4ea 5798 /* Require a git repository unless when running in pager mode. */
094e6ab0 5799 if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
58a5e4ea
JF
5800 die("Not a git repository");
5801
504fbeeb
JF
5802 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5803 opt_utf8 = FALSE;
5804
6b68fd24
JF
5805 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5806 opt_iconv = iconv_open(opt_codeset, opt_encoding);
20f4b4a3 5807 if (opt_iconv == ICONV_NONE)
6b68fd24
JF
5808 die("Failed to initialize character set conversion");
5809 }
5810
fbe047c9 5811 if (*opt_git_dir && load_refs() == ERR)
c34d9c9f
JF
5812 die("Failed to load refs.");
5813
1ba2ae4b
JF
5814 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5815 view->cmd_env = getenv(view->cmd_env);
5816
6b161b31
JF
5817 request = opt_request;
5818
5819 init_display();
b801d8b2
JF
5820
5821 while (view_driver(display[current_view], request)) {
6b161b31 5822 int key;
b801d8b2
JF
5823 int i;
5824
699ae55b 5825 foreach_view (view, i)
6b161b31 5826 update_view(view);
b801d8b2
JF
5827
5828 /* Refresh, accept single keystroke of input */
6b161b31 5829 key = wgetch(status_win);
04e2b7b2 5830
cf4d82e6
JF
5831 /* wgetch() with nodelay() enabled returns ERR when there's no
5832 * input. */
5833 if (key == ERR) {
5834 request = REQ_NONE;
8b534a13 5835 continue;
cf4d82e6 5836 }
04e2b7b2
JF
5837
5838 request = get_keybinding(display[current_view]->keymap, key);
03a93dbb 5839
6706b2ba 5840 /* Some low-level request handling. This keeps access to
fac7db6c
JF
5841 * status_win restricted. */
5842 switch (request) {
5843 case REQ_PROMPT:
9e21ce5c
JF
5844 {
5845 char *cmd = read_prompt(":");
5846
5847 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5848 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5849 opt_request = REQ_VIEW_DIFF;
5850 } else {
5851 opt_request = REQ_VIEW_PAGER;
5852 }
5853 break;
5854 }
fac7db6c 5855
1d754561 5856 request = REQ_NONE;
9e21ce5c
JF
5857 break;
5858 }
4af34daa
JF
5859 case REQ_SEARCH:
5860 case REQ_SEARCH_BACK:
5861 {
5862 const char *prompt = request == REQ_SEARCH
5863 ? "/" : "?";
5864 char *search = read_prompt(prompt);
5865
5866 if (search)
739e81de 5867 string_ncopy(opt_search, search, strlen(search));
4af34daa
JF
5868 else
5869 request = REQ_NONE;
5870 break;
5871 }
fac7db6c
JF
5872 case REQ_SCREEN_RESIZE:
5873 {
5874 int height, width;
5875
5876 getmaxyx(stdscr, height, width);
5877
5878 /* Resize the status view and let the view driver take
5879 * care of resizing the displayed views. */
5880 wresize(status_win, 1, width);
5881 mvwin(status_win, height - 1, 0);
5882 wrefresh(status_win);
5883 break;
5884 }
5885 default:
5886 break;
03a93dbb 5887 }
b801d8b2
JF
5888 }
5889
5890 quit(0);
5891
5892 return 0;
5893}