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