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