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