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