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