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