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