Refactor view->line reallocation
[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
JF
13/**
14 * TIG(1)
15 * ======
16 *
17 * NAME
18 * ----
19 * tig - text-mode interface for git
20 *
21 * SYNOPSIS
22 * --------
23 * [verse]
03a93dbb
JF
24 * tig [options]
25 * tig [options] [--] [git log options]
b76c2afc
JF
26 * tig [options] log [git log options]
27 * tig [options] diff [git diff options]
8855ada4
JF
28 * tig [options] show [git show options]
29 * tig [options] < [git command output]
b801d8b2
JF
30 *
31 * DESCRIPTION
32 * -----------
6706b2ba
JF
33 * Browse changes in a git repository. Additionally, tig(1) can also act
34 * as a pager for output of various git commands.
35 *
57bdf034 36 * When browsing repositories, tig(1) uses the underlying git commands
468876c9
JF
37 * to present the user with various views, such as summarized commit log
38 * and showing the commit with the log message, diffstat, and the diff.
6706b2ba 39 *
468876c9
JF
40 * Using tig(1) as a pager, it will display input from stdin and try
41 * to colorize it.
b801d8b2
JF
42 **/
43
b76c2afc 44#ifndef VERSION
8c11094f 45#define VERSION "tig-0.3"
b76c2afc
JF
46#endif
47
8855ada4
JF
48#ifndef DEBUG
49#define NDEBUG
50#endif
51
22f66b0a 52#include <assert.h>
4c6fabc2 53#include <errno.h>
22f66b0a
JF
54#include <ctype.h>
55#include <signal.h>
b801d8b2 56#include <stdarg.h>
b801d8b2 57#include <stdio.h>
22f66b0a 58#include <stdlib.h>
b801d8b2 59#include <string.h>
6908bdbd 60#include <unistd.h>
b76c2afc 61#include <time.h>
b801d8b2
JF
62
63#include <curses.h>
b801d8b2
JF
64
65static void die(const char *err, ...);
66static void report(const char *msg, ...);
4a63c884 67static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
1ba2ae4b 68static void set_nonblocking_input(bool loading);
10e290ee 69static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
24b5b3e0 70static void load_help_page(void);
6b161b31
JF
71
72#define ABS(x) ((x) >= 0 ? (x) : -(x))
73#define MIN(x, y) ((x) < (y) ? (x) : (y))
74
75#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
76#define STRING_SIZE(x) (sizeof(x) - 1)
b76c2afc 77
2e8488b4
JF
78#define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
79#define SIZEOF_CMD 1024 /* Size of command buffer. */
b801d8b2 80
82e78006
JF
81/* This color name can be used to refer to the default term colors. */
82#define COLOR_DEFAULT (-1)
78c70acd 83
82e78006 84/* The format and size of the date column in the main view. */
4c6fabc2 85#define DATE_FORMAT "%Y-%m-%d %H:%M"
6b161b31 86#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
4c6fabc2 87
10e290ee
JF
88#define AUTHOR_COLS 20
89
a28bcc22 90/* The default interval between line numbers. */
4a2909a7 91#define NUMBER_INTERVAL 1
82e78006 92
6706b2ba
JF
93#define TABSIZE 8
94
a28bcc22
JF
95#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
96
8855ada4 97/* Some ascii-shorthands fitted into the ncurses namespace. */
a28bcc22
JF
98#define KEY_TAB '\t'
99#define KEY_RETURN '\r'
4a2909a7
JF
100#define KEY_ESC 27
101
6706b2ba 102
c34d9c9f 103struct ref {
468876c9
JF
104 char *name; /* Ref name; tag or head names are shortened. */
105 char id[41]; /* Commit SHA1 ID */
106 unsigned int tag:1; /* Is it a tag? */
107 unsigned int next:1; /* For ref lists: are there more refs? */
c34d9c9f
JF
108};
109
ff26aa29 110static struct ref **get_refs(char *id);
4c6fabc2 111
660e09ad
JF
112struct int_map {
113 const char *name;
114 int namelen;
115 int value;
116};
117
118static int
119set_from_int_map(struct int_map *map, size_t map_size,
120 int *value, const char *name, int namelen)
121{
122
123 int i;
124
125 for (i = 0; i < map_size; i++)
126 if (namelen == map[i].namelen &&
127 !strncasecmp(name, map[i].name, namelen)) {
128 *value = map[i].value;
129 return OK;
130 }
131
132 return ERR;
133}
134
6706b2ba 135
03a93dbb
JF
136/*
137 * String helpers
138 */
78c70acd 139
82e78006 140static inline void
4685845e 141string_ncopy(char *dst, const char *src, int dstlen)
82e78006
JF
142{
143 strncpy(dst, src, dstlen - 1);
144 dst[dstlen - 1] = 0;
03a93dbb 145
82e78006
JF
146}
147
148/* Shorthand for safely copying into a fixed buffer. */
149#define string_copy(dst, src) \
150 string_ncopy(dst, src, sizeof(dst))
151
4a63c884
JF
152static char *
153chomp_string(char *name)
154{
155 int namelen;
156
157 while (isspace(*name))
158 name++;
159
160 namelen = strlen(name) - 1;
161 while (namelen > 0 && isspace(name[namelen]))
162 name[namelen--] = 0;
163
164 return name;
165}
166
6706b2ba 167
03a93dbb
JF
168/* Shell quoting
169 *
170 * NOTE: The following is a slightly modified copy of the git project's shell
171 * quoting routines found in the quote.c file.
172 *
173 * Help to copy the thing properly quoted for the shell safety. any single
174 * quote is replaced with '\'', any exclamation point is replaced with '\!',
175 * and the whole thing is enclosed in a
176 *
177 * E.g.
178 * original sq_quote result
179 * name ==> name ==> 'name'
180 * a b ==> a b ==> 'a b'
181 * a'b ==> a'\''b ==> 'a'\''b'
182 * a!b ==> a'\!'b ==> 'a'\!'b'
183 */
184
185static size_t
186sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
187{
188 char c;
189
a9274b95 190#define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
03a93dbb
JF
191
192 BUFPUT('\'');
193 while ((c = *src++)) {
194 if (c == '\'' || c == '!') {
195 BUFPUT('\'');
196 BUFPUT('\\');
197 BUFPUT(c);
198 BUFPUT('\'');
199 } else {
200 BUFPUT(c);
201 }
202 }
203 BUFPUT('\'');
204
205 return bufsize;
206}
207
82e78006 208
24b5b3e0
JF
209/*
210 * User requests
211 */
212
213#define REQ_INFO \
214 /* XXX: Keep the view request first and in sync with views[]. */ \
215 REQ_GROUP("View switching") \
216 REQ_(VIEW_MAIN, "Show main view"), \
217 REQ_(VIEW_DIFF, "Show diff view"), \
218 REQ_(VIEW_LOG, "Show log view"), \
219 REQ_(VIEW_HELP, "Show help page"), \
220 REQ_(VIEW_PAGER, "Show pager view"), \
221 \
222 REQ_GROUP("View manipulation") \
223 REQ_(ENTER, "Enter current line and scroll"), \
224 REQ_(NEXT, "Move to next"), \
225 REQ_(PREVIOUS, "Move to previous"), \
226 REQ_(VIEW_NEXT, "Move focus to next view"), \
227 REQ_(VIEW_CLOSE, "Close the current view"), \
228 REQ_(QUIT, "Close all views and quit"), \
229 \
230 REQ_GROUP("Cursor navigation") \
231 REQ_(MOVE_UP, "Move cursor one line up"), \
232 REQ_(MOVE_DOWN, "Move cursor one line down"), \
233 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
234 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
235 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
236 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
237 \
238 REQ_GROUP("Scrolling") \
239 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
240 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
241 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
242 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
243 \
244 REQ_GROUP("Misc") \
245 REQ_(PROMPT, "Bring up the prompt"), \
246 REQ_(SCREEN_UPDATE, "Update the screen"), \
247 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
248 REQ_(SCREEN_RESIZE, "Resize the screen"), \
249 REQ_(SHOW_VERSION, "Show version information"), \
250 REQ_(STOP_LOADING, "Stop all loading views"), \
251 REQ_(TOGGLE_LINENO, "Toggle line numbers"),
252
253
254/* User action requests. */
255enum request {
256#define REQ_GROUP(help)
257#define REQ_(req, help) REQ_##req
258
259 /* Offset all requests to avoid conflicts with ncurses getch values. */
260 REQ_OFFSET = KEY_MAX + 1,
261 REQ_INFO
262
263#undef REQ_GROUP
264#undef REQ_
265};
266
267struct request_info {
268 enum request request;
269 char *help;
270};
271
272static struct request_info req_info[] = {
273#define REQ_GROUP(help) { 0, (help) },
274#define REQ_(req, help) { REQ_##req, (help) }
275 REQ_INFO
276#undef REQ_GROUP
277#undef REQ_
278};
279
b76c2afc
JF
280/**
281 * OPTIONS
282 * -------
283 **/
284
4b8c01a3
JF
285static const char usage[] =
286VERSION " (" __DATE__ ")\n"
287"\n"
288"Usage: tig [options]\n"
289" or: tig [options] [--] [git log options]\n"
290" or: tig [options] log [git log options]\n"
291" or: tig [options] diff [git diff options]\n"
292" or: tig [options] show [git show options]\n"
293" or: tig [options] < [git command output]\n"
294"\n"
295"Options:\n"
296" -l Start up in log view\n"
297" -d Start up in diff view\n"
298" -n[I], --line-number[=I] Show line numbers with given interval\n"
b3c965c9 299" -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
4b8c01a3
JF
300" -- Mark end of tig options\n"
301" -v, --version Show version and exit\n"
302" -h, --help Show help message and exit\n";
303
6706b2ba
JF
304/* Option and state variables. */
305static bool opt_line_number = FALSE;
2e8488b4 306static int opt_num_interval = NUMBER_INTERVAL;
6706b2ba 307static int opt_tab_size = TABSIZE;
6b161b31 308static enum request opt_request = REQ_VIEW_MAIN;
03a93dbb 309static char opt_cmd[SIZEOF_CMD] = "";
9989bf60
JF
310static char opt_encoding[20] = "";
311static bool opt_utf8 = TRUE;
6908bdbd 312static FILE *opt_pipe = NULL;
b76c2afc 313
b76c2afc 314/* Returns the index of log or diff command or -1 to exit. */
8855ada4 315static bool
b76c2afc
JF
316parse_options(int argc, char *argv[])
317{
318 int i;
319
320 for (i = 1; i < argc; i++) {
321 char *opt = argv[i];
322
323 /**
b76c2afc 324 * -l::
1ba2ae4b 325 * Start up in log view using the internal log command.
b76c2afc 326 **/
6b161b31 327 if (!strcmp(opt, "-l")) {
4a2909a7 328 opt_request = REQ_VIEW_LOG;
6b161b31
JF
329 continue;
330 }
b76c2afc
JF
331
332 /**
333 * -d::
1ba2ae4b 334 * Start up in diff view using the internal diff command.
b76c2afc 335 **/
6b161b31 336 if (!strcmp(opt, "-d")) {
4a2909a7 337 opt_request = REQ_VIEW_DIFF;
6b161b31
JF
338 continue;
339 }
b76c2afc
JF
340
341 /**
2e8488b4 342 * -n[INTERVAL], --line-number[=INTERVAL]::
b76c2afc 343 * Prefix line numbers in log and diff view.
2e8488b4 344 * Optionally, with interval different than each line.
b76c2afc 345 **/
6b161b31 346 if (!strncmp(opt, "-n", 2) ||
8855ada4 347 !strncmp(opt, "--line-number", 13)) {
2e8488b4
JF
348 char *num = opt;
349
350 if (opt[1] == 'n') {
351 num = opt + 2;
352
353 } else if (opt[STRING_SIZE("--line-number")] == '=') {
354 num = opt + STRING_SIZE("--line-number=");
355 }
356
357 if (isdigit(*num))
358 opt_num_interval = atoi(num);
359
6b161b31
JF
360 opt_line_number = TRUE;
361 continue;
362 }
b76c2afc
JF
363
364 /**
380ec161 365 * -b[NSPACES], --tab-size[=NSPACES]::
6706b2ba
JF
366 * Set the number of spaces tabs should be expanded to.
367 **/
380ec161 368 if (!strncmp(opt, "-b", 2) ||
6706b2ba
JF
369 !strncmp(opt, "--tab-size", 10)) {
370 char *num = opt;
371
380ec161 372 if (opt[1] == 'b') {
6706b2ba
JF
373 num = opt + 2;
374
375 } else if (opt[STRING_SIZE("--tab-size")] == '=') {
376 num = opt + STRING_SIZE("--tab-size=");
377 }
378
379 if (isdigit(*num))
380 opt_tab_size = MIN(atoi(num), TABSIZE);
381 continue;
382 }
383
384 /**
b76c2afc
JF
385 * -v, --version::
386 * Show version and exit.
387 **/
6b161b31 388 if (!strcmp(opt, "-v") ||
8855ada4 389 !strcmp(opt, "--version")) {
b76c2afc 390 printf("tig version %s\n", VERSION);
8855ada4 391 return FALSE;
6b161b31 392 }
b76c2afc
JF
393
394 /**
4b8c01a3
JF
395 * -h, --help::
396 * Show help message and exit.
397 **/
398 if (!strcmp(opt, "-h") ||
399 !strcmp(opt, "--help")) {
400 printf(usage);
401 return FALSE;
402 }
403
404 /**
03a93dbb 405 * \--::
0721c53a 406 * End of tig(1) options. Useful when specifying command
d44f34bf 407 * options for the main view. Example:
03a93dbb 408 *
889cb462 409 * $ tig -- --since=1.month
b76c2afc 410 **/
6908bdbd
JF
411 if (!strcmp(opt, "--")) {
412 i++;
413 break;
414 }
03a93dbb 415
8855ada4 416 /**
d44f34bf 417 * log [git log options]::
8855ada4
JF
418 * Open log view using the given git log options.
419 *
d44f34bf 420 * diff [git diff options]::
8855ada4
JF
421 * Open diff view using the given git diff options.
422 *
d44f34bf 423 * show [git show options]::
8855ada4
JF
424 * Open diff view using the given git show options.
425 **/
426 if (!strcmp(opt, "log") ||
427 !strcmp(opt, "diff") ||
428 !strcmp(opt, "show")) {
429 opt_request = opt[0] == 'l'
430 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
431 break;
432 }
433
d44f34bf 434 /**
57bdf034 435 * [git log options]::
d44f34bf
JF
436 * tig(1) will stop the option parsing when the first
437 * command line parameter not starting with "-" is
57bdf034
JF
438 * encountered. All options including this one will be
439 * passed to git log when loading the main view.
440 * This makes it possible to say:
889cb462 441 *
03a93dbb 442 * $ tig tag-1.0..HEAD
d44f34bf 443 **/
03a93dbb 444 if (opt[0] && opt[0] != '-')
6908bdbd 445 break;
6b161b31
JF
446
447 die("unknown command '%s'", opt);
b76c2afc
JF
448 }
449
6908bdbd 450 if (!isatty(STDIN_FILENO)) {
6908bdbd
JF
451 opt_request = REQ_VIEW_PAGER;
452 opt_pipe = stdin;
453
454 } else if (i < argc) {
455 size_t buf_size;
456
6908bdbd 457 if (opt_request == REQ_VIEW_MAIN)
8855ada4
JF
458 /* XXX: This is vulnerable to the user overriding
459 * options required for the main view parser. */
6908bdbd
JF
460 string_copy(opt_cmd, "git log --stat --pretty=raw");
461 else
462 string_copy(opt_cmd, "git");
463 buf_size = strlen(opt_cmd);
464
465 while (buf_size < sizeof(opt_cmd) && i < argc) {
466 opt_cmd[buf_size++] = ' ';
467 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
468 }
469
470 if (buf_size >= sizeof(opt_cmd))
471 die("command too long");
472
473 opt_cmd[buf_size] = 0;
474
475 }
476
afdc35b3
JF
477 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
478 opt_utf8 = FALSE;
479
8855ada4 480 return TRUE;
b76c2afc
JF
481}
482
483
219ee52a
JF
484/**
485 * ENVIRONMENT VARIABLES
486 * ---------------------
219ee52a
JF
487 * TIG_LS_REMOTE::
488 * Set command for retrieving all repository references. The command
489 * should output data in the same format as git-ls-remote(1).
490 **/
491
492#define TIG_LS_REMOTE \
493 "git ls-remote . 2>/dev/null"
494
495/**
219ee52a
JF
496 * TIG_DIFF_CMD::
497 * The command used for the diff view. By default, git show is used
498 * as a backend.
499 *
500 * TIG_LOG_CMD::
501 * The command used for the log view. If you prefer to have both
502 * author and committer shown in the log view be sure to pass
503 * `--pretty=fuller` to git log.
504 *
505 * TIG_MAIN_CMD::
506 * The command used for the main view. Note, you must always specify
507 * the option: `--pretty=raw` since the main view parser expects to
508 * read that format.
509 **/
510
511#define TIG_DIFF_CMD \
512 "git show --patch-with-stat --find-copies-harder -B -C %s"
513
514#define TIG_LOG_CMD \
515 "git log --cc --stat -n100 %s"
516
517#define TIG_MAIN_CMD \
518 "git log --topo-order --stat --pretty=raw %s"
519
520/* ... silently ignore that the following are also exported. */
521
522#define TIG_HELP_CMD \
24b5b3e0 523 ""
219ee52a
JF
524
525#define TIG_PAGER_CMD \
526 ""
527
528
660e09ad
JF
529/**
530 * FILES
531 * -----
3df9850a 532 * '~/.tigrc'::
cb7f42cd 533 * User configuration file. See tigrc(5) for examples.
660e09ad
JF
534 *
535 * '.git/config'::
536 * Repository config file. Read on startup with the help of
537 * git-repo-config(1).
538 **/
660e09ad
JF
539
540static struct int_map color_map[] = {
541#define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
542 COLOR_MAP(DEFAULT),
543 COLOR_MAP(BLACK),
544 COLOR_MAP(BLUE),
545 COLOR_MAP(CYAN),
546 COLOR_MAP(GREEN),
547 COLOR_MAP(MAGENTA),
548 COLOR_MAP(RED),
549 COLOR_MAP(WHITE),
550 COLOR_MAP(YELLOW),
551};
552
660e09ad
JF
553static struct int_map attr_map[] = {
554#define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
555 ATTR_MAP(NORMAL),
556 ATTR_MAP(BLINK),
557 ATTR_MAP(BOLD),
558 ATTR_MAP(DIM),
559 ATTR_MAP(REVERSE),
560 ATTR_MAP(STANDOUT),
561 ATTR_MAP(UNDERLINE),
562};
563
2e8488b4 564#define LINE_INFO \
660e09ad 565LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
a28bcc22
JF
566LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
567LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
568LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
660e09ad
JF
569LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
570LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
571LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
572LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
573LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
574LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
575LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
576LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
577LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
578LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
6908bdbd 579LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
8855ada4 580LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
6908bdbd
JF
581LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
582LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
8855ada4
JF
583LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
584LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
a28bcc22
JF
585LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
586LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
587LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
8855ada4 588LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
a28bcc22 589LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
a28bcc22 590LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
a28bcc22
JF
591LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
592LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
593LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
6b161b31
JF
594LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
595LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
a28bcc22
JF
596LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
597LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
598LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
c34d9c9f
JF
599LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
600LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
660e09ad 601LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
660e09ad
JF
602
603
604/*
605 * Line-oriented content detection.
606 */
2e8488b4 607
78c70acd 608enum line_type {
2e8488b4
JF
609#define LINE(type, line, fg, bg, attr) \
610 LINE_##type
611 LINE_INFO
612#undef LINE
78c70acd
JF
613};
614
615struct line_info {
660e09ad
JF
616 const char *name; /* Option name. */
617 int namelen; /* Size of option name. */
4685845e 618 const char *line; /* The start of line to match. */
2e8488b4
JF
619 int linelen; /* Size of string to match. */
620 int fg, bg, attr; /* Color and text attributes for the lines. */
78c70acd
JF
621};
622
2e8488b4 623static struct line_info line_info[] = {
78c70acd 624#define LINE(type, line, fg, bg, attr) \
660e09ad 625 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
2e8488b4
JF
626 LINE_INFO
627#undef LINE
78c70acd
JF
628};
629
2e8488b4
JF
630static enum line_type
631get_line_type(char *line)
78c70acd
JF
632{
633 int linelen = strlen(line);
a28bcc22 634 enum line_type type;
78c70acd 635
a28bcc22 636 for (type = 0; type < ARRAY_SIZE(line_info); type++)
2e8488b4 637 /* Case insensitive search matches Signed-off-by lines better. */
a28bcc22
JF
638 if (linelen >= line_info[type].linelen &&
639 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
640 return type;
78c70acd 641
2e8488b4 642 return LINE_DEFAULT;
78c70acd
JF
643}
644
2e8488b4 645static inline int
78c70acd
JF
646get_line_attr(enum line_type type)
647{
2e8488b4
JF
648 assert(type < ARRAY_SIZE(line_info));
649 return COLOR_PAIR(type) | line_info[type].attr;
78c70acd
JF
650}
651
660e09ad
JF
652static struct line_info *
653get_line_info(char *name, int namelen)
654{
655 enum line_type type;
656 int i;
657
658 /* Diff-Header -> DIFF_HEADER */
659 for (i = 0; i < namelen; i++) {
660 if (name[i] == '-')
661 name[i] = '_';
662 else if (name[i] == '.')
663 name[i] = '_';
664 }
665
666 for (type = 0; type < ARRAY_SIZE(line_info); type++)
667 if (namelen == line_info[type].namelen &&
668 !strncasecmp(line_info[type].name, name, namelen))
669 return &line_info[type];
670
671 return NULL;
672}
673
78c70acd
JF
674static void
675init_colors(void)
676{
82e78006
JF
677 int default_bg = COLOR_BLACK;
678 int default_fg = COLOR_WHITE;
a28bcc22 679 enum line_type type;
78c70acd
JF
680
681 start_color();
682
683 if (use_default_colors() != ERR) {
82e78006
JF
684 default_bg = -1;
685 default_fg = -1;
78c70acd
JF
686 }
687
a28bcc22
JF
688 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
689 struct line_info *info = &line_info[type];
82e78006
JF
690 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
691 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
78c70acd 692
a28bcc22 693 init_pair(type, fg, bg);
78c70acd
JF
694 }
695}
696
fe7233c3
JF
697struct line {
698 enum line_type type;
699 void *data; /* User data */
700};
701
78c70acd 702
1899507c
JF
703/*
704 * User config file handling.
705 */
706
660e09ad
JF
707#define set_color(color, name, namelen) \
708 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, namelen)
709
710#define set_attribute(attr, name, namelen) \
711 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, namelen)
712
3c3801c2
JF
713static int config_lineno;
714static bool config_errors;
715static char *config_msg;
716
660e09ad 717static int
3c3801c2 718set_option(char *opt, int optlen, char *value, int valuelen)
660e09ad 719{
660e09ad
JF
720 /* Reads: "color" object fgcolor bgcolor [attr] */
721 if (!strcmp(opt, "color")) {
722 struct line_info *info;
723
724 value = chomp_string(value);
725 valuelen = strcspn(value, " \t");
726 info = get_line_info(value, valuelen);
3c3801c2
JF
727 if (!info) {
728 config_msg = "Unknown color name";
660e09ad 729 return ERR;
3c3801c2 730 }
660e09ad
JF
731
732 value = chomp_string(value + valuelen);
733 valuelen = strcspn(value, " \t");
3c3801c2
JF
734 if (set_color(&info->fg, value, valuelen) == ERR) {
735 config_msg = "Unknown color";
660e09ad 736 return ERR;
3c3801c2 737 }
660e09ad
JF
738
739 value = chomp_string(value + valuelen);
740 valuelen = strcspn(value, " \t");
3c3801c2
JF
741 if (set_color(&info->bg, value, valuelen) == ERR) {
742 config_msg = "Unknown color";
660e09ad 743 return ERR;
3c3801c2 744 }
660e09ad
JF
745
746 value = chomp_string(value + valuelen);
747 if (*value &&
3c3801c2
JF
748 set_attribute(&info->attr, value, strlen(value)) == ERR) {
749 config_msg = "Unknown attribute";
660e09ad 750 return ERR;
3c3801c2 751 }
660e09ad
JF
752
753 return OK;
754 }
755
756 return ERR;
757}
758
759static int
3c3801c2
JF
760read_option(char *opt, int optlen, char *value, int valuelen)
761{
762 config_lineno++;
763 config_msg = "Internal error";
764
765 optlen = strcspn(opt, "#;");
766 if (optlen == 0) {
767 /* The whole line is a commend or empty. */
768 return OK;
769
770 } else if (opt[optlen] != 0) {
771 /* Part of the option name is a comment, so the value part
772 * should be ignored. */
773 valuelen = 0;
774 opt[optlen] = value[valuelen] = 0;
775 } else {
776 /* Else look for comment endings in the value. */
777 valuelen = strcspn(value, "#;");
778 value[valuelen] = 0;
779 }
780
781 if (set_option(opt, optlen, value, valuelen) == ERR) {
782 fprintf(stderr, "Error on line %d, near '%.*s' option: %s\n",
783 config_lineno, optlen, opt, config_msg);
784 config_errors = TRUE;
785 }
786
787 /* Always keep going if errors are encountered. */
788 return OK;
789}
790
791static int
660e09ad
JF
792load_options(void)
793{
794 char *home = getenv("HOME");
795 char buf[1024];
796 FILE *file;
797
3c3801c2
JF
798 config_lineno = 0;
799 config_errors = FALSE;
800
660e09ad 801 if (!home ||
3df9850a 802 snprintf(buf, sizeof(buf), "%s/.tigrc", home) >= sizeof(buf))
660e09ad
JF
803 return ERR;
804
805 /* It's ok that the file doesn't exist. */
806 file = fopen(buf, "r");
807 if (!file)
808 return OK;
809
3c3801c2
JF
810 if (read_properties(file, " \t", read_option) == ERR ||
811 config_errors == TRUE)
812 fprintf(stderr, "Errors while loading %s.\n", buf);
813
814 return OK;
660e09ad
JF
815}
816
817
d839253b 818/*
468876c9 819 * The viewer
d839253b 820 */
c2124ccd
JF
821
822struct view;
fe7233c3 823struct view_ops;
c2124ccd
JF
824
825/* The display array of active views and the index of the current view. */
826static struct view *display[2];
827static unsigned int current_view;
828
829#define foreach_view(view, i) \
830 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
831
9f41488f 832#define displayed_views() (display[1] != NULL ? 2 : 1)
c2124ccd 833
d839253b 834/* Current head and commit ID */
c2124ccd
JF
835static char ref_commit[SIZEOF_REF] = "HEAD";
836static char ref_head[SIZEOF_REF] = "HEAD";
837
b801d8b2 838struct view {
03a93dbb 839 const char *name; /* View name */
4685845e
TH
840 const char *cmd_fmt; /* Default command line format */
841 const char *cmd_env; /* Command line set via environment */
842 const char *id; /* Points to either of ref_{head,commit} */
6b161b31 843
fe7233c3 844 struct view_ops *ops; /* View operations */
22f66b0a 845
03a93dbb 846 char cmd[SIZEOF_CMD]; /* Command buffer */
49f2b43f
JF
847 char ref[SIZEOF_REF]; /* Hovered commit reference */
848 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2e8488b4 849
8855ada4
JF
850 int height, width; /* The width and height of the main window */
851 WINDOW *win; /* The main window */
852 WINDOW *title; /* The title window living below the main window */
b801d8b2
JF
853
854 /* Navigation */
855 unsigned long offset; /* Offset of the window top */
856 unsigned long lineno; /* Current line number */
857
f6da0b66
JF
858 /* If non-NULL, points to the view that opened this view. If this view
859 * is closed tig will switch back to the parent view. */
860 struct view *parent;
861
b801d8b2
JF
862 /* Buffering */
863 unsigned long lines; /* Total number of lines */
fe7233c3 864 struct line *line; /* Line index */
e2c01617 865 unsigned long line_size;/* Total number of allocated lines */
8855ada4 866 unsigned int digits; /* Number of digits in the lines member. */
b801d8b2
JF
867
868 /* Loading */
869 FILE *pipe;
2e8488b4 870 time_t start_time;
b801d8b2
JF
871};
872
fe7233c3
JF
873struct view_ops {
874 /* What type of content being displayed. Used in the title bar. */
875 const char *type;
876 /* Draw one line; @lineno must be < view->height. */
877 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
878 /* Read one line; updates view->line. */
879 bool (*read)(struct view *view, struct line *prev, char *data);
880 /* Depending on view, change display based on current line. */
881 bool (*enter)(struct view *view, struct line *line);
882};
883
6b161b31
JF
884static struct view_ops pager_ops;
885static struct view_ops main_ops;
a28bcc22 886
95d7ddcd
JF
887#define VIEW_STR(name, cmd, env, ref, ops) \
888 { name, cmd, #env, ref, ops }
1ba2ae4b 889
95d7ddcd
JF
890#define VIEW_(id, name, ops, ref) \
891 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops)
1ba2ae4b 892
c2124ccd 893
b801d8b2 894static struct view views[] = {
95d7ddcd
JF
895 VIEW_(MAIN, "main", &main_ops, ref_head),
896 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
897 VIEW_(LOG, "log", &pager_ops, ref_head),
898 VIEW_(HELP, "help", &pager_ops, "static"),
899 VIEW_(PAGER, "pager", &pager_ops, "static"),
b801d8b2
JF
900};
901
a28bcc22
JF
902#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
903
4c6fabc2 904
fe7233c3
JF
905static bool
906draw_view_line(struct view *view, unsigned int lineno)
907{
908 if (view->offset + lineno >= view->lines)
909 return FALSE;
910
911 return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
912}
913
b801d8b2 914static void
82e78006 915redraw_view_from(struct view *view, int lineno)
b801d8b2 916{
82e78006 917 assert(0 <= lineno && lineno < view->height);
b801d8b2 918
82e78006 919 for (; lineno < view->height; lineno++) {
fe7233c3 920 if (!draw_view_line(view, lineno))
fd85fef1 921 break;
b801d8b2
JF
922 }
923
924 redrawwin(view->win);
925 wrefresh(view->win);
926}
927
b76c2afc 928static void
82e78006
JF
929redraw_view(struct view *view)
930{
931 wclear(view->win);
932 redraw_view_from(view, 0);
933}
934
c2124ccd 935
6b161b31 936static void
81030ec8
JF
937update_view_title(struct view *view)
938{
939 if (view == display[current_view])
940 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
941 else
942 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
943
944 werase(view->title);
945 wmove(view->title, 0, 0);
946
81030ec8
JF
947 if (*view->ref)
948 wprintw(view->title, "[%s] %s", view->name, view->ref);
949 else
950 wprintw(view->title, "[%s]", view->name);
951
c19f8017
JF
952 if (view->lines || view->pipe) {
953 unsigned int lines = view->lines
954 ? (view->lineno + 1) * 100 / view->lines
955 : 0;
956
81030ec8
JF
957 wprintw(view->title, " - %s %d of %d (%d%%)",
958 view->ops->type,
959 view->lineno + 1,
960 view->lines,
c19f8017 961 lines);
81030ec8
JF
962 }
963
f97f4012
JF
964 if (view->pipe) {
965 time_t secs = time(NULL) - view->start_time;
966
967 /* Three git seconds are a long time ... */
968 if (secs > 2)
969 wprintw(view->title, " %lds", secs);
970 }
971
976447f8 972 wmove(view->title, 0, view->width - 1);
81030ec8
JF
973 wrefresh(view->title);
974}
975
976static void
6b161b31 977resize_display(void)
b76c2afc 978{
03a93dbb 979 int offset, i;
6b161b31
JF
980 struct view *base = display[0];
981 struct view *view = display[1] ? display[1] : display[0];
b76c2afc 982
6b161b31 983 /* Setup window dimensions */
b76c2afc 984
03a93dbb 985 getmaxyx(stdscr, base->height, base->width);
b76c2afc 986
6b161b31 987 /* Make room for the status window. */
03a93dbb 988 base->height -= 1;
6b161b31
JF
989
990 if (view != base) {
03a93dbb
JF
991 /* Horizontal split. */
992 view->width = base->width;
6b161b31
JF
993 view->height = SCALE_SPLIT_VIEW(base->height);
994 base->height -= view->height;
995
996 /* Make room for the title bar. */
997 view->height -= 1;
998 }
999
1000 /* Make room for the title bar. */
1001 base->height -= 1;
1002
1003 offset = 0;
1004
1005 foreach_view (view, i) {
b76c2afc 1006 if (!view->win) {
c19f8017 1007 view->win = newwin(view->height, 0, offset, 0);
6b161b31
JF
1008 if (!view->win)
1009 die("Failed to create %s view", view->name);
1010
1011 scrollok(view->win, TRUE);
1012
1013 view->title = newwin(1, 0, offset + view->height, 0);
1014 if (!view->title)
1015 die("Failed to create title window");
1016
1017 } else {
c19f8017 1018 wresize(view->win, view->height, view->width);
6b161b31
JF
1019 mvwin(view->win, offset, 0);
1020 mvwin(view->title, offset + view->height, 0);
a28bcc22 1021 }
a28bcc22 1022
6b161b31 1023 offset += view->height + 1;
b76c2afc 1024 }
6b161b31 1025}
b76c2afc 1026
6b161b31 1027static void
20bb5e18
JF
1028redraw_display(void)
1029{
1030 struct view *view;
1031 int i;
1032
1033 foreach_view (view, i) {
1034 redraw_view(view);
1035 update_view_title(view);
1036 }
1037}
1038
85af6284
JF
1039static void
1040update_display_cursor(void)
1041{
1042 struct view *view = display[current_view];
1043
1044 /* Move the cursor to the right-most column of the cursor line.
1045 *
1046 * XXX: This could turn out to be a bit expensive, but it ensures that
1047 * the cursor does not jump around. */
1048 if (view->lines) {
1049 wmove(view->win, view->lineno - view->offset, view->width - 1);
1050 wrefresh(view->win);
1051 }
1052}
20bb5e18 1053
2e8488b4
JF
1054/*
1055 * Navigation
1056 */
1057
4a2909a7 1058/* Scrolling backend */
b801d8b2 1059static void
b3a54cba 1060do_scroll_view(struct view *view, int lines, bool redraw)
b801d8b2 1061{
fd85fef1
JF
1062 /* The rendering expects the new offset. */
1063 view->offset += lines;
1064
1065 assert(0 <= view->offset && view->offset < view->lines);
1066 assert(lines);
b801d8b2 1067
82e78006 1068 /* Redraw the whole screen if scrolling is pointless. */
4c6fabc2 1069 if (view->height < ABS(lines)) {
b76c2afc
JF
1070 redraw_view(view);
1071
1072 } else {
22f66b0a 1073 int line = lines > 0 ? view->height - lines : 0;
82e78006 1074 int end = line + ABS(lines);
fd85fef1
JF
1075
1076 wscrl(view->win, lines);
1077
22f66b0a 1078 for (; line < end; line++) {
fe7233c3 1079 if (!draw_view_line(view, line))
fd85fef1
JF
1080 break;
1081 }
1082 }
1083
1084 /* Move current line into the view. */
1085 if (view->lineno < view->offset) {
1086 view->lineno = view->offset;
fe7233c3 1087 draw_view_line(view, 0);
fd85fef1
JF
1088
1089 } else if (view->lineno >= view->offset + view->height) {
6706b2ba
JF
1090 if (view->lineno == view->offset + view->height) {
1091 /* Clear the hidden line so it doesn't show if the view
1092 * is scrolled up. */
1093 wmove(view->win, view->height, 0);
1094 wclrtoeol(view->win);
1095 }
fd85fef1 1096 view->lineno = view->offset + view->height - 1;
fe7233c3 1097 draw_view_line(view, view->lineno - view->offset);
fd85fef1
JF
1098 }
1099
4c6fabc2 1100 assert(view->offset <= view->lineno && view->lineno < view->lines);
fd85fef1 1101
b3a54cba
JF
1102 if (!redraw)
1103 return;
1104
fd85fef1
JF
1105 redrawwin(view->win);
1106 wrefresh(view->win);
9d3f5834 1107 report("");
fd85fef1 1108}
78c70acd 1109
4a2909a7 1110/* Scroll frontend */
fd85fef1 1111static void
6b161b31 1112scroll_view(struct view *view, enum request request)
fd85fef1
JF
1113{
1114 int lines = 1;
b801d8b2
JF
1115
1116 switch (request) {
4a2909a7 1117 case REQ_SCROLL_PAGE_DOWN:
fd85fef1 1118 lines = view->height;
4a2909a7 1119 case REQ_SCROLL_LINE_DOWN:
b801d8b2 1120 if (view->offset + lines > view->lines)
bde3653a 1121 lines = view->lines - view->offset;
b801d8b2 1122
fd85fef1 1123 if (lines == 0 || view->offset + view->height >= view->lines) {
eb98559e 1124 report("Cannot scroll beyond the last line");
b801d8b2
JF
1125 return;
1126 }
1127 break;
1128
4a2909a7 1129 case REQ_SCROLL_PAGE_UP:
fd85fef1 1130 lines = view->height;
4a2909a7 1131 case REQ_SCROLL_LINE_UP:
b801d8b2
JF
1132 if (lines > view->offset)
1133 lines = view->offset;
1134
1135 if (lines == 0) {
eb98559e 1136 report("Cannot scroll beyond the first line");
b801d8b2
JF
1137 return;
1138 }
1139
fd85fef1 1140 lines = -lines;
b801d8b2 1141 break;
03a93dbb 1142
6b161b31
JF
1143 default:
1144 die("request %d not handled in switch", request);
b801d8b2
JF
1145 }
1146
b3a54cba 1147 do_scroll_view(view, lines, TRUE);
fd85fef1 1148}
b801d8b2 1149
4a2909a7 1150/* Cursor moving */
fd85fef1 1151static void
b3a54cba 1152move_view(struct view *view, enum request request, bool redraw)
fd85fef1
JF
1153{
1154 int steps;
b801d8b2 1155
fd85fef1 1156 switch (request) {
4a2909a7 1157 case REQ_MOVE_FIRST_LINE:
78c70acd
JF
1158 steps = -view->lineno;
1159 break;
1160
4a2909a7 1161 case REQ_MOVE_LAST_LINE:
78c70acd
JF
1162 steps = view->lines - view->lineno - 1;
1163 break;
1164
4a2909a7 1165 case REQ_MOVE_PAGE_UP:
78c70acd
JF
1166 steps = view->height > view->lineno
1167 ? -view->lineno : -view->height;
1168 break;
1169
4a2909a7 1170 case REQ_MOVE_PAGE_DOWN:
78c70acd
JF
1171 steps = view->lineno + view->height >= view->lines
1172 ? view->lines - view->lineno - 1 : view->height;
1173 break;
1174
4a2909a7 1175 case REQ_MOVE_UP:
fd85fef1
JF
1176 steps = -1;
1177 break;
b801d8b2 1178
4a2909a7 1179 case REQ_MOVE_DOWN:
fd85fef1
JF
1180 steps = 1;
1181 break;
6b161b31
JF
1182
1183 default:
1184 die("request %d not handled in switch", request);
78c70acd 1185 }
b801d8b2 1186
4c6fabc2 1187 if (steps <= 0 && view->lineno == 0) {
eb98559e 1188 report("Cannot move beyond the first line");
78c70acd 1189 return;
b801d8b2 1190
6908bdbd 1191 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
eb98559e 1192 report("Cannot move beyond the last line");
78c70acd 1193 return;
fd85fef1
JF
1194 }
1195
4c6fabc2 1196 /* Move the current line */
fd85fef1 1197 view->lineno += steps;
4c6fabc2
JF
1198 assert(0 <= view->lineno && view->lineno < view->lines);
1199
1200 /* Repaint the old "current" line if we be scrolling */
2e8488b4
JF
1201 if (ABS(steps) < view->height) {
1202 int prev_lineno = view->lineno - steps - view->offset;
1203
1204 wmove(view->win, prev_lineno, 0);
1205 wclrtoeol(view->win);
fe7233c3 1206 draw_view_line(view, prev_lineno);
2e8488b4 1207 }
fd85fef1 1208
4c6fabc2 1209 /* Check whether the view needs to be scrolled */
fd85fef1
JF
1210 if (view->lineno < view->offset ||
1211 view->lineno >= view->offset + view->height) {
1212 if (steps < 0 && -steps > view->offset) {
1213 steps = -view->offset;
b76c2afc
JF
1214
1215 } else if (steps > 0) {
1216 if (view->lineno == view->lines - 1 &&
1217 view->lines > view->height) {
1218 steps = view->lines - view->offset - 1;
1219 if (steps >= view->height)
1220 steps -= view->height - 1;
1221 }
b801d8b2 1222 }
78c70acd 1223
b3a54cba 1224 do_scroll_view(view, steps, redraw);
fd85fef1 1225 return;
b801d8b2
JF
1226 }
1227
4c6fabc2 1228 /* Draw the current line */
fe7233c3 1229 draw_view_line(view, view->lineno - view->offset);
fd85fef1 1230
b3a54cba
JF
1231 if (!redraw)
1232 return;
1233
b801d8b2
JF
1234 redrawwin(view->win);
1235 wrefresh(view->win);
9d3f5834 1236 report("");
b801d8b2
JF
1237}
1238
b801d8b2 1239
2e8488b4
JF
1240/*
1241 * Incremental updating
1242 */
b801d8b2 1243
199d1288
JF
1244static void
1245end_update(struct view *view)
1246{
1247 if (!view->pipe)
1248 return;
1249 set_nonblocking_input(FALSE);
1250 if (view->pipe == stdin)
1251 fclose(view->pipe);
1252 else
1253 pclose(view->pipe);
1254 view->pipe = NULL;
1255}
1256
03a93dbb 1257static bool
b801d8b2
JF
1258begin_update(struct view *view)
1259{
4685845e 1260 const char *id = view->id;
fd85fef1 1261
199d1288
JF
1262 if (view->pipe)
1263 end_update(view);
1264
03a93dbb
JF
1265 if (opt_cmd[0]) {
1266 string_copy(view->cmd, opt_cmd);
1267 opt_cmd[0] = 0;
8855ada4
JF
1268 /* When running random commands, the view ref could have become
1269 * invalid so clear it. */
1270 view->ref[0] = 0;
03a93dbb 1271 } else {
4685845e 1272 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1ba2ae4b
JF
1273
1274 if (snprintf(view->cmd, sizeof(view->cmd), format,
1275 id, id, id, id, id) >= sizeof(view->cmd))
03a93dbb
JF
1276 return FALSE;
1277 }
b801d8b2 1278
6908bdbd
JF
1279 /* Special case for the pager view. */
1280 if (opt_pipe) {
1281 view->pipe = opt_pipe;
1282 opt_pipe = NULL;
1283 } else {
1284 view->pipe = popen(view->cmd, "r");
1285 }
1286
2e8488b4
JF
1287 if (!view->pipe)
1288 return FALSE;
b801d8b2 1289
6b161b31 1290 set_nonblocking_input(TRUE);
b801d8b2
JF
1291
1292 view->offset = 0;
1293 view->lines = 0;
1294 view->lineno = 0;
49f2b43f 1295 string_copy(view->vid, id);
b801d8b2 1296
2e8488b4
JF
1297 if (view->line) {
1298 int i;
1299
1300 for (i = 0; i < view->lines; i++)
fe7233c3
JF
1301 if (view->line[i].data)
1302 free(view->line[i].data);
2e8488b4
JF
1303
1304 free(view->line);
1305 view->line = NULL;
1306 }
1307
1308 view->start_time = time(NULL);
1309
b801d8b2
JF
1310 return TRUE;
1311}
1312
e2c01617
JF
1313static struct line *
1314realloc_lines(struct view *view, size_t line_size)
1315{
1316 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1317
1318 if (!tmp)
1319 return NULL;
1320
1321 view->line = tmp;
1322 view->line_size = line_size;
1323 return view->line;
1324}
1325
03a93dbb 1326static bool
b801d8b2
JF
1327update_view(struct view *view)
1328{
1329 char buffer[BUFSIZ];
1330 char *line;
82e78006
JF
1331 /* The number of lines to read. If too low it will cause too much
1332 * redrawing (and possible flickering), if too high responsiveness
1333 * will suffer. */
8855ada4 1334 unsigned long lines = view->height;
82e78006 1335 int redraw_from = -1;
b801d8b2
JF
1336
1337 if (!view->pipe)
1338 return TRUE;
1339
82e78006
JF
1340 /* Only redraw if lines are visible. */
1341 if (view->offset + view->height >= view->lines)
1342 redraw_from = view->lines - view->offset;
b801d8b2 1343
e2c01617 1344 if (!realloc_lines(view, view->lines + lines))
b801d8b2
JF
1345 goto alloc_error;
1346
b801d8b2 1347 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
c34d9c9f 1348 int linelen = strlen(line);
b801d8b2 1349
fe7233c3
JF
1350 struct line *prev = view->lines
1351 ? &view->line[view->lines - 1]
1352 : NULL;
1353
b801d8b2
JF
1354 if (linelen)
1355 line[linelen - 1] = 0;
1356
fe7233c3 1357 if (!view->ops->read(view, prev, line))
b801d8b2 1358 goto alloc_error;
fd85fef1
JF
1359
1360 if (lines-- == 1)
1361 break;
b801d8b2
JF
1362 }
1363
8855ada4
JF
1364 {
1365 int digits;
1366
1367 lines = view->lines;
1368 for (digits = 0; lines; digits++)
1369 lines /= 10;
1370
1371 /* Keep the displayed view in sync with line number scaling. */
1372 if (digits != view->digits) {
1373 view->digits = digits;
1374 redraw_from = 0;
1375 }
1376 }
1377
82e78006
JF
1378 if (redraw_from >= 0) {
1379 /* If this is an incremental update, redraw the previous line
a28bcc22
JF
1380 * since for commits some members could have changed when
1381 * loading the main view. */
82e78006
JF
1382 if (redraw_from > 0)
1383 redraw_from--;
1384
1385 /* Incrementally draw avoids flickering. */
1386 redraw_view_from(view, redraw_from);
4c6fabc2 1387 }
b801d8b2 1388
eb98559e
JF
1389 /* Update the title _after_ the redraw so that if the redraw picks up a
1390 * commit reference in view->ref it'll be available here. */
1391 update_view_title(view);
1392
b801d8b2 1393 if (ferror(view->pipe)) {
03a93dbb 1394 report("Failed to read: %s", strerror(errno));
b801d8b2
JF
1395 goto end;
1396
1397 } else if (feof(view->pipe)) {
f97f4012 1398 report("");
b801d8b2
JF
1399 goto end;
1400 }
1401
1402 return TRUE;
1403
1404alloc_error:
2e8488b4 1405 report("Allocation failure");
b801d8b2
JF
1406
1407end:
1408 end_update(view);
1409 return FALSE;
1410}
1411
49f2b43f
JF
1412enum open_flags {
1413 OPEN_DEFAULT = 0, /* Use default view switching. */
1414 OPEN_SPLIT = 1, /* Split current view. */
1415 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
1416 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
1417};
1418
6b161b31 1419static void
49f2b43f 1420open_view(struct view *prev, enum request request, enum open_flags flags)
b801d8b2 1421{
49f2b43f
JF
1422 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1423 bool split = !!(flags & OPEN_SPLIT);
1424 bool reload = !!(flags & OPEN_RELOAD);
a28bcc22 1425 struct view *view = VIEW(request);
9f41488f 1426 int nviews = displayed_views();
6e950a52 1427 struct view *base_view = display[0];
b801d8b2 1428
49f2b43f 1429 if (view == prev && nviews == 1 && !reload) {
6b161b31
JF
1430 report("Already in %s view", view->name);
1431 return;
1432 }
b801d8b2 1433
8855ada4 1434 if ((reload || strcmp(view->vid, view->id)) &&
03a93dbb 1435 !begin_update(view)) {
6b161b31
JF
1436 report("Failed to load %s view", view->name);
1437 return;
1438 }
a28bcc22 1439
6b161b31 1440 if (split) {
8d741c06 1441 display[1] = view;
6b161b31 1442 if (!backgrounded)
8d741c06 1443 current_view = 1;
6b161b31
JF
1444 } else {
1445 /* Maximize the current view. */
1446 memset(display, 0, sizeof(display));
1447 current_view = 0;
1448 display[current_view] = view;
a28bcc22 1449 }
b801d8b2 1450
6e950a52
JF
1451 /* Resize the view when switching between split- and full-screen,
1452 * or when switching between two different full-screen views. */
1453 if (nviews != displayed_views() ||
1454 (nviews == 1 && base_view != display[0]))
a006db63 1455 resize_display();
b801d8b2 1456
a8891802 1457 if (split && prev->lineno - prev->offset >= prev->height) {
03a93dbb 1458 /* Take the title line into account. */
eb98559e 1459 int lines = prev->lineno - prev->offset - prev->height + 1;
03a93dbb
JF
1460
1461 /* Scroll the view that was split if the current line is
1462 * outside the new limited view. */
b3a54cba 1463 do_scroll_view(prev, lines, TRUE);
03a93dbb
JF
1464 }
1465
6b161b31 1466 if (prev && view != prev) {
9b995f0c 1467 if (split && !backgrounded) {
f0b3ab80
JF
1468 /* "Blur" the previous view. */
1469 update_view_title(prev);
9f396969 1470 }
f0b3ab80 1471
f6da0b66 1472 view->parent = prev;
b801d8b2
JF
1473 }
1474
24b5b3e0
JF
1475 if (view == VIEW(REQ_VIEW_HELP))
1476 load_help_page();
1477
9f396969 1478 if (view->pipe && view->lines == 0) {
03a93dbb
JF
1479 /* Clear the old view and let the incremental updating refill
1480 * the screen. */
1481 wclear(view->win);
f97f4012 1482 report("");
03a93dbb
JF
1483 } else {
1484 redraw_view(view);
24b5b3e0 1485 report("");
03a93dbb 1486 }
6706b2ba
JF
1487
1488 /* If the view is backgrounded the above calls to report()
1489 * won't redraw the view title. */
1490 if (backgrounded)
1491 update_view_title(view);
b801d8b2
JF
1492}
1493
1494
6b161b31
JF
1495/*
1496 * User request switch noodle
1497 */
1498
b801d8b2 1499static int
6b161b31 1500view_driver(struct view *view, enum request request)
b801d8b2 1501{
b801d8b2
JF
1502 int i;
1503
1504 switch (request) {
4a2909a7
JF
1505 case REQ_MOVE_UP:
1506 case REQ_MOVE_DOWN:
1507 case REQ_MOVE_PAGE_UP:
1508 case REQ_MOVE_PAGE_DOWN:
1509 case REQ_MOVE_FIRST_LINE:
1510 case REQ_MOVE_LAST_LINE:
b3a54cba 1511 move_view(view, request, TRUE);
fd85fef1
JF
1512 break;
1513
4a2909a7
JF
1514 case REQ_SCROLL_LINE_DOWN:
1515 case REQ_SCROLL_LINE_UP:
1516 case REQ_SCROLL_PAGE_DOWN:
1517 case REQ_SCROLL_PAGE_UP:
a28bcc22 1518 scroll_view(view, request);
b801d8b2
JF
1519 break;
1520
4a2909a7 1521 case REQ_VIEW_MAIN:
4a2909a7 1522 case REQ_VIEW_DIFF:
2e8488b4
JF
1523 case REQ_VIEW_LOG:
1524 case REQ_VIEW_HELP:
6908bdbd 1525 case REQ_VIEW_PAGER:
49f2b43f 1526 open_view(view, request, OPEN_DEFAULT);
b801d8b2
JF
1527 break;
1528
b3a54cba
JF
1529 case REQ_NEXT:
1530 case REQ_PREVIOUS:
1531 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1532
1533 if (view == VIEW(REQ_VIEW_DIFF) &&
1534 view->parent == VIEW(REQ_VIEW_MAIN)) {
f0b3ab80 1535 bool redraw = display[1] == view;
b3a54cba
JF
1536
1537 view = view->parent;
1538 move_view(view, request, redraw);
f0b3ab80
JF
1539 if (redraw)
1540 update_view_title(view);
b3a54cba
JF
1541 } else {
1542 move_view(view, request, TRUE);
1543 break;
1544 }
6706b2ba
JF
1545 /* Fall-through */
1546
6b161b31 1547 case REQ_ENTER:
6908bdbd
JF
1548 if (!view->lines) {
1549 report("Nothing to enter");
1550 break;
1551 }
fe7233c3 1552 return view->ops->enter(view, &view->line[view->lineno]);
6b161b31 1553
03a93dbb
JF
1554 case REQ_VIEW_NEXT:
1555 {
9f41488f 1556 int nviews = displayed_views();
03a93dbb
JF
1557 int next_view = (current_view + 1) % nviews;
1558
1559 if (next_view == current_view) {
1560 report("Only one view is displayed");
1561 break;
1562 }
1563
1564 current_view = next_view;
1565 /* Blur out the title of the previous view. */
1566 update_view_title(view);
6734f6b9 1567 report("");
03a93dbb
JF
1568 break;
1569 }
24b5b3e0 1570 case REQ_TOGGLE_LINENO:
b76c2afc 1571 opt_line_number = !opt_line_number;
20bb5e18 1572 redraw_display();
b801d8b2
JF
1573 break;
1574
03a93dbb 1575 case REQ_PROMPT:
8855ada4 1576 /* Always reload^Wrerun commands from the prompt. */
49f2b43f 1577 open_view(view, opt_request, OPEN_RELOAD);
03a93dbb
JF
1578 break;
1579
4a2909a7 1580 case REQ_STOP_LOADING:
59a45d3a
JF
1581 for (i = 0; i < ARRAY_SIZE(views); i++) {
1582 view = &views[i];
2e8488b4 1583 if (view->pipe)
6a7bb912 1584 report("Stopped loading the %s view", view->name),
03a93dbb
JF
1585 end_update(view);
1586 }
b801d8b2
JF
1587 break;
1588
4a2909a7 1589 case REQ_SHOW_VERSION:
6cb291b7 1590 report("%s (built %s)", VERSION, __DATE__);
b801d8b2
JF
1591 return TRUE;
1592
fac7db6c
JF
1593 case REQ_SCREEN_RESIZE:
1594 resize_display();
1595 /* Fall-through */
4a2909a7 1596 case REQ_SCREEN_REDRAW:
20bb5e18 1597 redraw_display();
4a2909a7
JF
1598 break;
1599
1600 case REQ_SCREEN_UPDATE:
b801d8b2
JF
1601 doupdate();
1602 return TRUE;
1603
4f9b667a 1604 case REQ_VIEW_CLOSE:
2fcf5401
JF
1605 /* XXX: Mark closed views by letting view->parent point to the
1606 * view itself. Parents to closed view should never be
1607 * followed. */
1608 if (view->parent &&
1609 view->parent->parent != view->parent) {
4f9b667a
JF
1610 memset(display, 0, sizeof(display));
1611 current_view = 0;
f6da0b66 1612 display[current_view] = view->parent;
2fcf5401 1613 view->parent = view;
4f9b667a
JF
1614 resize_display();
1615 redraw_display();
1616 break;
1617 }
1618 /* Fall-through */
b801d8b2
JF
1619 case REQ_QUIT:
1620 return FALSE;
1621
1622 default:
2e8488b4 1623 /* An unknown key will show most commonly used commands. */
468876c9 1624 report("Unknown key, press 'h' for help");
b801d8b2
JF
1625 return TRUE;
1626 }
1627
1628 return TRUE;
1629}
1630
1631
1632/*
ff26aa29 1633 * Pager backend
b801d8b2
JF
1634 */
1635
6b161b31 1636static bool
fe7233c3 1637pager_draw(struct view *view, struct line *line, unsigned int lineno)
b801d8b2 1638{
fe7233c3
JF
1639 char *text = line->data;
1640 enum line_type type = line->type;
1641 int textlen = strlen(text);
78c70acd 1642 int attr;
b801d8b2 1643
6706b2ba
JF
1644 wmove(view->win, lineno, 0);
1645
fd85fef1 1646 if (view->offset + lineno == view->lineno) {
8855ada4 1647 if (type == LINE_COMMIT) {
fe7233c3 1648 string_copy(view->ref, text + 7);
03a93dbb
JF
1649 string_copy(ref_commit, view->ref);
1650 }
8855ada4 1651
78c70acd 1652 type = LINE_CURSOR;
6706b2ba 1653 wchgat(view->win, -1, 0, type, NULL);
fd85fef1
JF
1654 }
1655
78c70acd 1656 attr = get_line_attr(type);
b801d8b2 1657 wattrset(view->win, attr);
b76c2afc 1658
6706b2ba
JF
1659 if (opt_line_number || opt_tab_size < TABSIZE) {
1660 static char spaces[] = " ";
1661 int col_offset = 0, col = 0;
1662
1663 if (opt_line_number) {
1664 unsigned long real_lineno = view->offset + lineno + 1;
82e78006 1665
6706b2ba
JF
1666 if (real_lineno == 1 ||
1667 (real_lineno % opt_num_interval) == 0) {
1668 wprintw(view->win, "%.*d", view->digits, real_lineno);
8855ada4 1669
6706b2ba
JF
1670 } else {
1671 waddnstr(view->win, spaces,
1672 MIN(view->digits, STRING_SIZE(spaces)));
1673 }
1674 waddstr(view->win, ": ");
1675 col_offset = view->digits + 2;
1676 }
8855ada4 1677
fe7233c3 1678 while (text && col_offset + col < view->width) {
6706b2ba 1679 int cols_max = view->width - col_offset - col;
fe7233c3 1680 char *pos = text;
6706b2ba 1681 int cols;
4c6fabc2 1682
fe7233c3
JF
1683 if (*text == '\t') {
1684 text++;
6706b2ba 1685 assert(sizeof(spaces) > TABSIZE);
fe7233c3 1686 pos = spaces;
6706b2ba 1687 cols = opt_tab_size - (col % opt_tab_size);
82e78006 1688
b76c2afc 1689 } else {
fe7233c3
JF
1690 text = strchr(text, '\t');
1691 cols = line ? text - pos : strlen(pos);
b76c2afc 1692 }
6706b2ba 1693
fe7233c3 1694 waddnstr(view->win, pos, MIN(cols, cols_max));
6706b2ba 1695 col += cols;
b76c2afc 1696 }
b76c2afc
JF
1697
1698 } else {
6706b2ba 1699 int col = 0, pos = 0;
b801d8b2 1700
fe7233c3
JF
1701 for (; pos < textlen && col < view->width; pos++, col++)
1702 if (text[pos] == '\t')
6706b2ba
JF
1703 col += TABSIZE - (col % TABSIZE) - 1;
1704
fe7233c3 1705 waddnstr(view->win, text, pos);
6706b2ba 1706 }
2e8488b4 1707
b801d8b2
JF
1708 return TRUE;
1709}
1710
6b161b31 1711static bool
fe7233c3 1712pager_read(struct view *view, struct line *prev, char *line)
22f66b0a 1713{
fe7233c3
JF
1714 view->line[view->lines].data = strdup(line);
1715 if (!view->line[view->lines].data)
22f66b0a
JF
1716 return FALSE;
1717
fe7233c3
JF
1718 view->line[view->lines].type = get_line_type(line);
1719
22f66b0a
JF
1720 view->lines++;
1721 return TRUE;
1722}
1723
6b161b31 1724static bool
fe7233c3 1725pager_enter(struct view *view, struct line *line)
6b161b31 1726{
91e8e277 1727 int split = 0;
6b161b31 1728
9fbbd28f
JF
1729 if (line->type == LINE_COMMIT &&
1730 (view == VIEW(REQ_VIEW_LOG) ||
1731 view == VIEW(REQ_VIEW_PAGER))) {
91e8e277
JF
1732 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1733 split = 1;
67e48ac5
JF
1734 }
1735
91e8e277
JF
1736 /* Always scroll the view even if it was split. That way
1737 * you can use Enter to scroll through the log view and
1738 * split open each commit diff. */
1739 scroll_view(view, REQ_SCROLL_LINE_DOWN);
1740
1741 /* FIXME: A minor workaround. Scrolling the view will call report("")
9d82d824
JF
1742 * but if we are scrolling a non-current view this won't properly
1743 * update the view title. */
91e8e277
JF
1744 if (split)
1745 update_view_title(view);
6b161b31
JF
1746
1747 return TRUE;
1748}
1749
6b161b31 1750static struct view_ops pager_ops = {
6734f6b9 1751 "line",
6b161b31
JF
1752 pager_draw,
1753 pager_read,
1754 pager_enter,
1755};
1756
80ce96ea 1757
ff26aa29
JF
1758/*
1759 * Main view backend
1760 */
1761
1762struct commit {
1763 char id[41]; /* SHA1 ID. */
1764 char title[75]; /* The first line of the commit message. */
1765 char author[75]; /* The author of the commit. */
1766 struct tm time; /* Date from the author ident. */
1767 struct ref **refs; /* Repository references; tags & branch heads. */
1768};
c34d9c9f 1769
6b161b31 1770static bool
fe7233c3 1771main_draw(struct view *view, struct line *line, unsigned int lineno)
22f66b0a 1772{
2e8488b4 1773 char buf[DATE_COLS + 1];
fe7233c3 1774 struct commit *commit = line->data;
78c70acd 1775 enum line_type type;
6706b2ba 1776 int col = 0;
b76c2afc 1777 size_t timelen;
10e290ee 1778 size_t authorlen;
9989bf60 1779 int trimmed = 1;
22f66b0a 1780
4c6fabc2
JF
1781 if (!*commit->author)
1782 return FALSE;
22f66b0a 1783
6706b2ba
JF
1784 wmove(view->win, lineno, col);
1785
22f66b0a 1786 if (view->offset + lineno == view->lineno) {
03a93dbb 1787 string_copy(view->ref, commit->id);
49f2b43f 1788 string_copy(ref_commit, view->ref);
78c70acd 1789 type = LINE_CURSOR;
6706b2ba
JF
1790 wattrset(view->win, get_line_attr(type));
1791 wchgat(view->win, -1, 0, type, NULL);
1792
78c70acd 1793 } else {
b76c2afc 1794 type = LINE_MAIN_COMMIT;
6706b2ba 1795 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
b76c2afc
JF
1796 }
1797
4c6fabc2 1798 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
b76c2afc 1799 waddnstr(view->win, buf, timelen);
4c6fabc2 1800 waddstr(view->win, " ");
b76c2afc 1801
6706b2ba
JF
1802 col += DATE_COLS;
1803 wmove(view->win, lineno, col);
1804 if (type != LINE_CURSOR)
1805 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
b76c2afc 1806
9989bf60
JF
1807 if (opt_utf8) {
1808 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1809 } else {
1810 authorlen = strlen(commit->author);
1811 if (authorlen > AUTHOR_COLS - 2) {
1812 authorlen = AUTHOR_COLS - 2;
1813 trimmed = 1;
1814 }
1815 }
10e290ee
JF
1816
1817 if (trimmed) {
1818 waddnstr(view->win, commit->author, authorlen);
6706b2ba
JF
1819 if (type != LINE_CURSOR)
1820 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
b76c2afc
JF
1821 waddch(view->win, '~');
1822 } else {
1823 waddstr(view->win, commit->author);
22f66b0a
JF
1824 }
1825
10e290ee 1826 col += AUTHOR_COLS;
6706b2ba
JF
1827 if (type != LINE_CURSOR)
1828 wattrset(view->win, A_NORMAL);
1829
1830 mvwaddch(view->win, lineno, col, ACS_LTEE);
1831 wmove(view->win, lineno, col + 2);
1832 col += 2;
c34d9c9f
JF
1833
1834 if (commit->refs) {
1835 size_t i = 0;
1836
1837 do {
6706b2ba
JF
1838 if (type == LINE_CURSOR)
1839 ;
1840 else if (commit->refs[i]->tag)
c34d9c9f
JF
1841 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1842 else
1843 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1844 waddstr(view->win, "[");
1845 waddstr(view->win, commit->refs[i]->name);
1846 waddstr(view->win, "]");
6706b2ba
JF
1847 if (type != LINE_CURSOR)
1848 wattrset(view->win, A_NORMAL);
c34d9c9f 1849 waddstr(view->win, " ");
6706b2ba 1850 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
c34d9c9f
JF
1851 } while (commit->refs[i++]->next);
1852 }
1853
6706b2ba
JF
1854 if (type != LINE_CURSOR)
1855 wattrset(view->win, get_line_attr(type));
1856
1857 {
1858 int titlelen = strlen(commit->title);
1859
1860 if (col + titlelen > view->width)
1861 titlelen = view->width - col;
1862
1863 waddnstr(view->win, commit->title, titlelen);
1864 }
22f66b0a
JF
1865
1866 return TRUE;
1867}
1868
4c6fabc2 1869/* Reads git log --pretty=raw output and parses it into the commit struct. */
6b161b31 1870static bool
fe7233c3 1871main_read(struct view *view, struct line *prev, char *line)
22f66b0a 1872{
78c70acd
JF
1873 enum line_type type = get_line_type(line);
1874 struct commit *commit;
22f66b0a 1875
78c70acd
JF
1876 switch (type) {
1877 case LINE_COMMIT:
22f66b0a
JF
1878 commit = calloc(1, sizeof(struct commit));
1879 if (!commit)
1880 return FALSE;
1881
4c6fabc2 1882 line += STRING_SIZE("commit ");
b76c2afc 1883
fe7233c3 1884 view->line[view->lines++].data = commit;
82e78006 1885 string_copy(commit->id, line);
c34d9c9f 1886 commit->refs = get_refs(commit->id);
78c70acd 1887 break;
22f66b0a 1888
8855ada4 1889 case LINE_AUTHOR:
b76c2afc 1890 {
4c6fabc2 1891 char *ident = line + STRING_SIZE("author ");
b76c2afc
JF
1892 char *end = strchr(ident, '<');
1893
fe7233c3
JF
1894 if (!prev)
1895 break;
1896
1897 commit = prev->data;
1898
b76c2afc
JF
1899 if (end) {
1900 for (; end > ident && isspace(end[-1]); end--) ;
1901 *end = 0;
1902 }
1903
82e78006 1904 string_copy(commit->author, ident);
b76c2afc 1905
4c6fabc2 1906 /* Parse epoch and timezone */
b76c2afc
JF
1907 if (end) {
1908 char *secs = strchr(end + 1, '>');
1909 char *zone;
1910 time_t time;
1911
1912 if (!secs || secs[1] != ' ')
1913 break;
1914
1915 secs += 2;
1916 time = (time_t) atol(secs);
1917 zone = strchr(secs, ' ');
4c6fabc2 1918 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
b76c2afc
JF
1919 long tz;
1920
1921 zone++;
1922 tz = ('0' - zone[1]) * 60 * 60 * 10;
1923 tz += ('0' - zone[2]) * 60 * 60;
1924 tz += ('0' - zone[3]) * 60;
1925 tz += ('0' - zone[4]) * 60;
1926
1927 if (zone[0] == '-')
1928 tz = -tz;
1929
1930 time -= tz;
1931 }
1932 gmtime_r(&time, &commit->time);
1933 }
1934 break;
1935 }
78c70acd 1936 default:
fe7233c3 1937 if (!prev)
2e8488b4
JF
1938 break;
1939
fe7233c3
JF
1940 commit = prev->data;
1941
2e8488b4 1942 /* Fill in the commit title if it has not already been set. */
2e8488b4
JF
1943 if (commit->title[0])
1944 break;
1945
1946 /* Require titles to start with a non-space character at the
1947 * offset used by git log. */
eb98559e
JF
1948 /* FIXME: More gracefull handling of titles; append "..." to
1949 * shortened titles, etc. */
2e8488b4 1950 if (strncmp(line, " ", 4) ||
eb98559e 1951 isspace(line[4]))
82e78006
JF
1952 break;
1953
1954 string_copy(commit->title, line + 4);
22f66b0a
JF
1955 }
1956
1957 return TRUE;
1958}
1959
6b161b31 1960static bool
fe7233c3 1961main_enter(struct view *view, struct line *line)
b801d8b2 1962{
b3a54cba
JF
1963 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
1964
1965 open_view(view, REQ_VIEW_DIFF, flags);
6b161b31 1966 return TRUE;
b801d8b2
JF
1967}
1968
6b161b31 1969static struct view_ops main_ops = {
6734f6b9 1970 "commit",
6b161b31
JF
1971 main_draw,
1972 main_read,
1973 main_enter,
1974};
2e8488b4 1975
c34d9c9f 1976
d839253b
JF
1977/*
1978 * Keys
1979 */
468876c9
JF
1980
1981struct keymap {
1982 int alias;
1983 int request;
1984};
1985
3a91b75e 1986static struct keymap keymap[] = {
d839253b 1987 /* View switching */
b3a54cba
JF
1988 { 'm', REQ_VIEW_MAIN },
1989 { 'd', REQ_VIEW_DIFF },
1990 { 'l', REQ_VIEW_LOG },
1991 { 'p', REQ_VIEW_PAGER },
1992 { 'h', REQ_VIEW_HELP },
24b5b3e0 1993 { '?', REQ_VIEW_HELP },
b3a54cba 1994
d839253b 1995 /* View manipulation */
4f9b667a 1996 { 'q', REQ_VIEW_CLOSE },
468876c9
JF
1997 { KEY_TAB, REQ_VIEW_NEXT },
1998 { KEY_RETURN, REQ_ENTER },
b3a54cba
JF
1999 { KEY_UP, REQ_PREVIOUS },
2000 { KEY_DOWN, REQ_NEXT },
468876c9 2001
d839253b 2002 /* Cursor navigation */
b3a54cba
JF
2003 { 'k', REQ_MOVE_UP },
2004 { 'j', REQ_MOVE_DOWN },
468876c9
JF
2005 { KEY_HOME, REQ_MOVE_FIRST_LINE },
2006 { KEY_END, REQ_MOVE_LAST_LINE },
2007 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
c622eefa 2008 { ' ', REQ_MOVE_PAGE_DOWN },
468876c9 2009 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
c622eefa 2010 { 'b', REQ_MOVE_PAGE_UP },
0df811cb 2011 { '-', REQ_MOVE_PAGE_UP },
468876c9 2012
d839253b 2013 /* Scrolling */
468876c9
JF
2014 { KEY_IC, REQ_SCROLL_LINE_UP },
2015 { KEY_DC, REQ_SCROLL_LINE_DOWN },
2016 { 'w', REQ_SCROLL_PAGE_UP },
2017 { 's', REQ_SCROLL_PAGE_DOWN },
2018
d839253b 2019 /* Misc */
d9d1c722 2020 { 'Q', REQ_QUIT },
468876c9
JF
2021 { 'z', REQ_STOP_LOADING },
2022 { 'v', REQ_SHOW_VERSION },
2023 { 'r', REQ_SCREEN_REDRAW },
24b5b3e0 2024 { 'n', REQ_TOGGLE_LINENO },
468876c9
JF
2025 { ':', REQ_PROMPT },
2026
2027 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
2028 { ERR, REQ_SCREEN_UPDATE },
2029
2030 /* Use the ncurses SIGWINCH handler. */
2031 { KEY_RESIZE, REQ_SCREEN_RESIZE },
2032};
2033
2034static enum request
2035get_request(int key)
2036{
2037 int i;
2038
2039 for (i = 0; i < ARRAY_SIZE(keymap); i++)
2040 if (keymap[i].alias == key)
2041 return keymap[i].request;
2042
2043 return (enum request) key;
2044}
2045
24b5b3e0
JF
2046struct key {
2047 char *name;
2048 int value;
2049};
2050
2051static struct key key_table[] = {
2052 { "Enter", KEY_RETURN },
2053 { "Space", ' ' },
2054 { "Backspace", KEY_BACKSPACE },
2055 { "Tab", KEY_TAB },
2056 { "Escape", KEY_ESC },
2057 { "Left", KEY_LEFT },
2058 { "Right", KEY_RIGHT },
2059 { "Up", KEY_UP },
2060 { "Down", KEY_DOWN },
2061 { "Insert", KEY_IC },
2062 { "Delete", KEY_DC },
2063 { "Home", KEY_HOME },
2064 { "End", KEY_END },
2065 { "PageUp", KEY_PPAGE },
2066 { "PageDown", KEY_NPAGE },
2067 { "F1", KEY_F(1) },
2068 { "F2", KEY_F(2) },
2069 { "F3", KEY_F(3) },
2070 { "F4", KEY_F(4) },
2071 { "F5", KEY_F(5) },
2072 { "F6", KEY_F(6) },
2073 { "F7", KEY_F(7) },
2074 { "F8", KEY_F(8) },
2075 { "F9", KEY_F(9) },
2076 { "F10", KEY_F(10) },
2077 { "F11", KEY_F(11) },
2078 { "F12", KEY_F(12) },
2079};
2080
2081static char *
2082get_key(enum request request)
2083{
2084 static char buf[BUFSIZ];
2085 static char key_char[] = "'X'";
2086 int pos = 0;
2087 char *sep = " ";
2088 int i;
2089
2090 buf[pos] = 0;
2091
2092 for (i = 0; i < ARRAY_SIZE(keymap); i++) {
2093 char *seq = NULL;
2094 int key;
2095
2096 if (keymap[i].request != request)
2097 continue;
2098
2099 for (key = 0; key < ARRAY_SIZE(key_table); key++)
2100 if (key_table[key].value == keymap[i].alias)
2101 seq = key_table[key].name;
2102
2103 if (seq == NULL &&
2104 keymap[i].alias < 127 &&
2105 isprint(keymap[i].alias)) {
2106 key_char[1] = (char) keymap[i].alias;
2107 seq = key_char;
2108 }
2109
2110 if (!seq)
2111 seq = "'?'";
2112
2113 pos += snprintf(buf + pos, sizeof(buf) - pos, "%s%s", sep, seq);
2114 if (pos >= sizeof(buf))
2115 return "Too many keybindings!";
2116 sep = ", ";
2117 }
2118
2119 return buf;
2120}
2121
2122static void load_help_page(void)
2123{
2124 char buf[BUFSIZ];
2125 struct view *view = VIEW(REQ_VIEW_HELP);
2126 int lines = ARRAY_SIZE(req_info) + 2;
2127 int i;
2128
2129 if (view->lines > 0)
2130 return;
2131
2132 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2133 if (!req_info[i].request)
2134 lines++;
2135
2136 view->line = calloc(lines, sizeof(*view->line));
2137 if (!view->line) {
2138 report("Allocation failure");
2139 return;
2140 }
2141
2142 pager_read(view, NULL, "Quick reference for tig keybindings:");
2143
2144 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2145 char *key;
2146
2147 if (!req_info[i].request) {
2148 pager_read(view, NULL, "");
2149 pager_read(view, NULL, req_info[i].help);
2150 continue;
2151 }
2152
2153 key = get_key(req_info[i].request);
2154 if (snprintf(buf, sizeof(buf), "%-25s %s", key, req_info[i].help)
2155 >= sizeof(buf))
2156 continue;
2157
2158 pager_read(view, NULL, buf);
2159 }
2160}
2161
468876c9 2162
6b161b31 2163/*
10e290ee
JF
2164 * Unicode / UTF-8 handling
2165 *
2166 * NOTE: Much of the following code for dealing with unicode is derived from
2167 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2168 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2169 */
2170
2171/* I've (over)annotated a lot of code snippets because I am not entirely
2172 * confident that the approach taken by this small UTF-8 interface is correct.
2173 * --jonas */
2174
2175static inline int
2176unicode_width(unsigned long c)
2177{
2178 if (c >= 0x1100 &&
2179 (c <= 0x115f /* Hangul Jamo */
2180 || c == 0x2329
2181 || c == 0x232a
2182 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
f97f4012 2183 /* CJK ... Yi */
10e290ee
JF
2184 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
2185 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
2186 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
2187 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
2188 || (c >= 0xffe0 && c <= 0xffe6)
2189 || (c >= 0x20000 && c <= 0x2fffd)
2190 || (c >= 0x30000 && c <= 0x3fffd)))
2191 return 2;
2192
2193 return 1;
2194}
2195
2196/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2197 * Illegal bytes are set one. */
2198static const unsigned char utf8_bytes[256] = {
2199 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,
2200 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,
2201 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,
2202 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,
2203 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,
2204 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,
2205 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,
2206 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,
2207};
2208
2209/* Decode UTF-8 multi-byte representation into a unicode character. */
2210static inline unsigned long
2211utf8_to_unicode(const char *string, size_t length)
2212{
2213 unsigned long unicode;
2214
2215 switch (length) {
2216 case 1:
2217 unicode = string[0];
2218 break;
2219 case 2:
2220 unicode = (string[0] & 0x1f) << 6;
2221 unicode += (string[1] & 0x3f);
2222 break;
2223 case 3:
2224 unicode = (string[0] & 0x0f) << 12;
2225 unicode += ((string[1] & 0x3f) << 6);
2226 unicode += (string[2] & 0x3f);
2227 break;
2228 case 4:
2229 unicode = (string[0] & 0x0f) << 18;
2230 unicode += ((string[1] & 0x3f) << 12);
2231 unicode += ((string[2] & 0x3f) << 6);
2232 unicode += (string[3] & 0x3f);
2233 break;
2234 case 5:
2235 unicode = (string[0] & 0x0f) << 24;
2236 unicode += ((string[1] & 0x3f) << 18);
2237 unicode += ((string[2] & 0x3f) << 12);
2238 unicode += ((string[3] & 0x3f) << 6);
2239 unicode += (string[4] & 0x3f);
2240 break;
68b6e0eb 2241 case 6:
10e290ee
JF
2242 unicode = (string[0] & 0x01) << 30;
2243 unicode += ((string[1] & 0x3f) << 24);
2244 unicode += ((string[2] & 0x3f) << 18);
2245 unicode += ((string[3] & 0x3f) << 12);
2246 unicode += ((string[4] & 0x3f) << 6);
2247 unicode += (string[5] & 0x3f);
2248 break;
2249 default:
2250 die("Invalid unicode length");
2251 }
2252
2253 /* Invalid characters could return the special 0xfffd value but NUL
2254 * should be just as good. */
2255 return unicode > 0xffff ? 0 : unicode;
2256}
2257
2258/* Calculates how much of string can be shown within the given maximum width
2259 * and sets trimmed parameter to non-zero value if all of string could not be
2260 * shown.
2261 *
2262 * Additionally, adds to coloffset how many many columns to move to align with
2263 * the expected position. Takes into account how multi-byte and double-width
2264 * characters will effect the cursor position.
2265 *
2266 * Returns the number of bytes to output from string to satisfy max_width. */
2267static size_t
2268utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2269{
2270 const char *start = string;
2271 const char *end = strchr(string, '\0');
2272 size_t mbwidth = 0;
2273 size_t width = 0;
2274
2275 *trimmed = 0;
2276
2277 while (string < end) {
2278 int c = *(unsigned char *) string;
2279 unsigned char bytes = utf8_bytes[c];
2280 size_t ucwidth;
2281 unsigned long unicode;
2282
2283 if (string + bytes > end)
2284 break;
2285
2286 /* Change representation to figure out whether
2287 * it is a single- or double-width character. */
2288
2289 unicode = utf8_to_unicode(string, bytes);
2290 /* FIXME: Graceful handling of invalid unicode character. */
2291 if (!unicode)
2292 break;
2293
2294 ucwidth = unicode_width(unicode);
2295 width += ucwidth;
2296 if (width > max_width) {
2297 *trimmed = 1;
2298 break;
2299 }
2300
2301 /* The column offset collects the differences between the
2302 * number of bytes encoding a character and the number of
2303 * columns will be used for rendering said character.
2304 *
2305 * So if some character A is encoded in 2 bytes, but will be
2306 * represented on the screen using only 1 byte this will and up
2307 * adding 1 to the multi-byte column offset.
2308 *
2309 * Assumes that no double-width character can be encoding in
2310 * less than two bytes. */
2311 if (bytes > ucwidth)
2312 mbwidth += bytes - ucwidth;
2313
2314 string += bytes;
2315 }
2316
2317 *coloffset += mbwidth;
2318
2319 return string - start;
2320}
2321
2322
2323/*
6b161b31
JF
2324 * Status management
2325 */
2e8488b4 2326
8855ada4 2327/* Whether or not the curses interface has been initialized. */
68b6e0eb 2328static bool cursed = FALSE;
8855ada4 2329
6b161b31
JF
2330/* The status window is used for polling keystrokes. */
2331static WINDOW *status_win;
4a2909a7 2332
2e8488b4 2333/* Update status and title window. */
4a2909a7
JF
2334static void
2335report(const char *msg, ...)
2336{
6706b2ba
JF
2337 static bool empty = TRUE;
2338 struct view *view = display[current_view];
b76c2afc 2339
6706b2ba
JF
2340 if (!empty || *msg) {
2341 va_list args;
4a2909a7 2342
6706b2ba 2343 va_start(args, msg);
4b76734f 2344
6706b2ba
JF
2345 werase(status_win);
2346 wmove(status_win, 0, 0);
2347 if (*msg) {
2348 vwprintw(status_win, msg, args);
2349 empty = FALSE;
2350 } else {
2351 empty = TRUE;
2352 }
2353 wrefresh(status_win);
b801d8b2 2354
6706b2ba
JF
2355 va_end(args);
2356 }
2357
2358 update_view_title(view);
85af6284 2359 update_display_cursor();
b801d8b2
JF
2360}
2361
6b161b31
JF
2362/* Controls when nodelay should be in effect when polling user input. */
2363static void
1ba2ae4b 2364set_nonblocking_input(bool loading)
b801d8b2 2365{
6706b2ba 2366 static unsigned int loading_views;
b801d8b2 2367
6706b2ba
JF
2368 if ((loading == FALSE && loading_views-- == 1) ||
2369 (loading == TRUE && loading_views++ == 0))
1ba2ae4b 2370 nodelay(status_win, loading);
6b161b31
JF
2371}
2372
2373static void
2374init_display(void)
2375{
2376 int x, y;
b76c2afc 2377
6908bdbd
JF
2378 /* Initialize the curses library */
2379 if (isatty(STDIN_FILENO)) {
8855ada4 2380 cursed = !!initscr();
6908bdbd
JF
2381 } else {
2382 /* Leave stdin and stdout alone when acting as a pager. */
2383 FILE *io = fopen("/dev/tty", "r+");
2384
8855ada4 2385 cursed = !!newterm(NULL, io, io);
6908bdbd
JF
2386 }
2387
8855ada4
JF
2388 if (!cursed)
2389 die("Failed to initialize curses");
2390
2e8488b4
JF
2391 nonl(); /* Tell curses not to do NL->CR/NL on output */
2392 cbreak(); /* Take input chars one at a time, no wait for \n */
2393 noecho(); /* Don't echo input */
b801d8b2 2394 leaveok(stdscr, TRUE);
b801d8b2
JF
2395
2396 if (has_colors())
2397 init_colors();
2398
2399 getmaxyx(stdscr, y, x);
2400 status_win = newwin(1, 0, y - 1, 0);
2401 if (!status_win)
2402 die("Failed to create status window");
2403
2404 /* Enable keyboard mapping */
2405 keypad(status_win, TRUE);
78c70acd 2406 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6b161b31
JF
2407}
2408
c34d9c9f
JF
2409
2410/*
2411 * Repository references
2412 */
2413
2414static struct ref *refs;
3a91b75e 2415static size_t refs_size;
c34d9c9f 2416
1307df1a
JF
2417/* Id <-> ref store */
2418static struct ref ***id_refs;
2419static size_t id_refs_size;
2420
c34d9c9f
JF
2421static struct ref **
2422get_refs(char *id)
2423{
1307df1a
JF
2424 struct ref ***tmp_id_refs;
2425 struct ref **ref_list = NULL;
2426 size_t ref_list_size = 0;
c34d9c9f
JF
2427 size_t i;
2428
1307df1a
JF
2429 for (i = 0; i < id_refs_size; i++)
2430 if (!strcmp(id, id_refs[i][0]->id))
2431 return id_refs[i];
2432
2433 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2434 if (!tmp_id_refs)
2435 return NULL;
2436
2437 id_refs = tmp_id_refs;
2438
c34d9c9f
JF
2439 for (i = 0; i < refs_size; i++) {
2440 struct ref **tmp;
2441
2442 if (strcmp(id, refs[i].id))
2443 continue;
2444
1307df1a 2445 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
c34d9c9f 2446 if (!tmp) {
1307df1a
JF
2447 if (ref_list)
2448 free(ref_list);
c34d9c9f
JF
2449 return NULL;
2450 }
2451
1307df1a
JF
2452 ref_list = tmp;
2453 if (ref_list_size > 0)
2454 ref_list[ref_list_size - 1]->next = 1;
2455 ref_list[ref_list_size] = &refs[i];
3af8774e
JF
2456
2457 /* XXX: The properties of the commit chains ensures that we can
2458 * safely modify the shared ref. The repo references will
2459 * always be similar for the same id. */
1307df1a
JF
2460 ref_list[ref_list_size]->next = 0;
2461 ref_list_size++;
c34d9c9f
JF
2462 }
2463
1307df1a
JF
2464 if (ref_list)
2465 id_refs[id_refs_size++] = ref_list;
2466
2467 return ref_list;
c34d9c9f
JF
2468}
2469
2470static int
d0cea5f9 2471read_ref(char *id, int idlen, char *name, int namelen)
c34d9c9f 2472{
d0cea5f9
JF
2473 struct ref *ref;
2474 bool tag = FALSE;
2475 bool tag_commit = FALSE;
2476
2477 /* Commits referenced by tags has "^{}" appended. */
2478 if (name[namelen - 1] == '}') {
2479 while (namelen > 0 && name[namelen] != '^')
2480 namelen--;
2481 if (namelen > 0)
2482 tag_commit = TRUE;
2483 name[namelen] = 0;
2484 }
c34d9c9f 2485
d0cea5f9
JF
2486 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2487 if (!tag_commit)
2488 return OK;
2489 name += STRING_SIZE("refs/tags/");
2490 tag = TRUE;
c34d9c9f 2491
d0cea5f9
JF
2492 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2493 name += STRING_SIZE("refs/heads/");
c34d9c9f 2494
d0cea5f9
JF
2495 } else if (!strcmp(name, "HEAD")) {
2496 return OK;
2497 }
6706b2ba 2498
d0cea5f9
JF
2499 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2500 if (!refs)
2501 return ERR;
c34d9c9f 2502
d0cea5f9
JF
2503 ref = &refs[refs_size++];
2504 ref->name = strdup(name);
2505 if (!ref->name)
2506 return ERR;
3af8774e 2507
d0cea5f9
JF
2508 ref->tag = tag;
2509 string_copy(ref->id, id);
3af8774e 2510
d0cea5f9
JF
2511 return OK;
2512}
c34d9c9f 2513
d0cea5f9
JF
2514static int
2515load_refs(void)
2516{
2517 const char *cmd_env = getenv("TIG_LS_REMOTE");
2518 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
c34d9c9f 2519
4a63c884 2520 return read_properties(popen(cmd, "r"), "\t", read_ref);
d0cea5f9 2521}
c34d9c9f 2522
d0cea5f9 2523static int
14c778a6 2524read_repo_config_option(char *name, int namelen, char *value, int valuelen)
d0cea5f9
JF
2525{
2526 if (!strcmp(name, "i18n.commitencoding")) {
2527 string_copy(opt_encoding, value);
c34d9c9f
JF
2528 }
2529
c34d9c9f
JF
2530 return OK;
2531}
2532
4670cf89 2533static int
14c778a6 2534load_repo_config(void)
4670cf89 2535{
66749723 2536 return read_properties(popen("git repo-config --list", "r"),
14c778a6 2537 "=", read_repo_config_option);
d0cea5f9
JF
2538}
2539
2540static int
4a63c884 2541read_properties(FILE *pipe, const char *separators,
d0cea5f9
JF
2542 int (*read_property)(char *, int, char *, int))
2543{
4670cf89
JF
2544 char buffer[BUFSIZ];
2545 char *name;
d0cea5f9 2546 int state = OK;
4670cf89
JF
2547
2548 if (!pipe)
2549 return ERR;
2550
d0cea5f9 2551 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4a63c884
JF
2552 char *value;
2553 size_t namelen;
2554 size_t valuelen;
4670cf89 2555
4a63c884
JF
2556 name = chomp_string(name);
2557 namelen = strcspn(name, separators);
2558
2559 if (name[namelen]) {
2560 name[namelen] = 0;
2561 value = chomp_string(name + namelen + 1);
d0cea5f9 2562 valuelen = strlen(value);
4670cf89 2563
d0cea5f9 2564 } else {
d0cea5f9
JF
2565 value = "";
2566 valuelen = 0;
4670cf89 2567 }
d0cea5f9 2568
3c3801c2 2569 state = read_property(name, namelen, value, valuelen);
4670cf89
JF
2570 }
2571
d0cea5f9
JF
2572 if (state != ERR && ferror(pipe))
2573 state = ERR;
4670cf89
JF
2574
2575 pclose(pipe);
2576
d0cea5f9 2577 return state;
4670cf89
JF
2578}
2579
d0cea5f9 2580
6b161b31
JF
2581/*
2582 * Main
2583 */
2584
b5c9e67f
TH
2585#if __GNUC__ >= 3
2586#define __NORETURN __attribute__((__noreturn__))
2587#else
2588#define __NORETURN
2589#endif
2590
2591static void __NORETURN
6b161b31
JF
2592quit(int sig)
2593{
8855ada4
JF
2594 /* XXX: Restore tty modes and let the OS cleanup the rest! */
2595 if (cursed)
2596 endwin();
6b161b31
JF
2597 exit(0);
2598}
2599
c6704a4e
JF
2600static void __NORETURN
2601die(const char *err, ...)
6b161b31
JF
2602{
2603 va_list args;
2604
2605 endwin();
2606
2607 va_start(args, err);
2608 fputs("tig: ", stderr);
2609 vfprintf(stderr, err, args);
2610 fputs("\n", stderr);
2611 va_end(args);
2612
2613 exit(1);
2614}
2615
2616int
2617main(int argc, char *argv[])
2618{
1ba2ae4b 2619 struct view *view;
6b161b31 2620 enum request request;
1ba2ae4b 2621 size_t i;
6b161b31
JF
2622
2623 signal(SIGINT, quit);
2624
660e09ad
JF
2625 if (load_options() == ERR)
2626 die("Failed to load user config.");
2627
2628 /* Load the repo config file so options can be overwritten from
afdc35b3 2629 * the command line. */
14c778a6 2630 if (load_repo_config() == ERR)
afdc35b3
JF
2631 die("Failed to load repo config.");
2632
8855ada4 2633 if (!parse_options(argc, argv))
6b161b31
JF
2634 return 0;
2635
c34d9c9f
JF
2636 if (load_refs() == ERR)
2637 die("Failed to load refs.");
2638
7bb55251
JF
2639 /* Require a git repository unless when running in pager mode. */
2640 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2641 die("Not a git repository");
2642
1ba2ae4b
JF
2643 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2644 view->cmd_env = getenv(view->cmd_env);
2645
6b161b31
JF
2646 request = opt_request;
2647
2648 init_display();
b801d8b2
JF
2649
2650 while (view_driver(display[current_view], request)) {
6b161b31 2651 int key;
b801d8b2
JF
2652 int i;
2653
6b161b31
JF
2654 foreach_view (view, i)
2655 update_view(view);
b801d8b2
JF
2656
2657 /* Refresh, accept single keystroke of input */
6b161b31
JF
2658 key = wgetch(status_win);
2659 request = get_request(key);
03a93dbb 2660
6706b2ba 2661 /* Some low-level request handling. This keeps access to
fac7db6c
JF
2662 * status_win restricted. */
2663 switch (request) {
2664 case REQ_PROMPT:
6908bdbd
JF
2665 report(":");
2666 /* Temporarily switch to line-oriented and echoed
2667 * input. */
03a93dbb
JF
2668 nocbreak();
2669 echo();
49f2b43f
JF
2670
2671 if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2672 memcpy(opt_cmd, "git ", 4);
2673 opt_request = REQ_VIEW_PAGER;
2674 } else {
6a7bb912
JF
2675 report("Prompt interrupted by loading view, "
2676 "press 'z' to stop loading views");
2677 request = REQ_SCREEN_UPDATE;
49f2b43f
JF
2678 }
2679
6908bdbd
JF
2680 noecho();
2681 cbreak();
fac7db6c
JF
2682 break;
2683
2684 case REQ_SCREEN_RESIZE:
2685 {
2686 int height, width;
2687
2688 getmaxyx(stdscr, height, width);
2689
2690 /* Resize the status view and let the view driver take
2691 * care of resizing the displayed views. */
2692 wresize(status_win, 1, width);
2693 mvwin(status_win, height - 1, 0);
2694 wrefresh(status_win);
2695 break;
2696 }
2697 default:
2698 break;
03a93dbb 2699 }
b801d8b2
JF
2700 }
2701
2702 quit(0);
2703
2704 return 0;
2705}
2706
2707/**
e34f45d4 2708 * include::BUGS[]
965537fa 2709 *
b801d8b2
JF
2710 * COPYRIGHT
2711 * ---------
192d9a60 2712 * Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
b801d8b2
JF
2713 *
2714 * This program is free software; you can redistribute it and/or modify
2715 * it under the terms of the GNU General Public License as published by
2716 * the Free Software Foundation; either version 2 of the License, or
2717 * (at your option) any later version.
2718 *
2719 * SEE ALSO
2720 * --------
f84f9d28
JF
2721 * - link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
2722 * - link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
2723 *
2724 * Other git repository browsers:
5cfbde75 2725 *
f84f9d28
JF
2726 * - gitk(1)
2727 * - qgit(1)
2728 * - gitview(1)
d839253b
JF
2729 *
2730 * Sites:
2731 *
2732 * include::SITES[]
b801d8b2 2733 **/