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