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