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