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