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