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