Cleanup status_request to make it reload the status view by default
[tig] / tig.c
... / ...
CommitLineData
1/* Copyright (c) 2006-2007 Jonas Fonseca <fonseca@diku.dk>
2 *
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.
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 */
13
14#ifdef HAVE_CONFIG_H
15#include "config.h"
16#endif
17
18#ifndef TIG_VERSION
19#define TIG_VERSION "unknown-version"
20#endif
21
22#ifndef DEBUG
23#define NDEBUG
24#endif
25
26#include <assert.h>
27#include <errno.h>
28#include <ctype.h>
29#include <signal.h>
30#include <stdarg.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <sys/types.h>
35#include <sys/stat.h>
36#include <unistd.h>
37#include <time.h>
38
39#include <regex.h>
40
41#include <locale.h>
42#include <langinfo.h>
43#include <iconv.h>
44
45#include <curses.h>
46
47#if __GNUC__ >= 3
48#define __NORETURN __attribute__((__noreturn__))
49#else
50#define __NORETURN
51#endif
52
53static void __NORETURN die(const char *err, ...);
54static void report(const char *msg, ...);
55static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
56static void set_nonblocking_input(bool loading);
57static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
58
59#define ABS(x) ((x) >= 0 ? (x) : -(x))
60#define MIN(x, y) ((x) < (y) ? (x) : (y))
61
62#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
63#define STRING_SIZE(x) (sizeof(x) - 1)
64
65#define SIZEOF_STR 1024 /* Default string size. */
66#define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
67#define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
68
69/* Revision graph */
70
71#define REVGRAPH_INIT 'I'
72#define REVGRAPH_MERGE 'M'
73#define REVGRAPH_BRANCH '+'
74#define REVGRAPH_COMMIT '*'
75#define REVGRAPH_LINE '|'
76
77#define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
78
79/* This color name can be used to refer to the default term colors. */
80#define COLOR_DEFAULT (-1)
81
82#define ICONV_NONE ((iconv_t) -1)
83#ifndef ICONV_CONST
84#define ICONV_CONST /* nothing */
85#endif
86
87/* The format and size of the date column in the main view. */
88#define DATE_FORMAT "%Y-%m-%d %H:%M"
89#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
90
91#define AUTHOR_COLS 20
92
93/* The default interval between line numbers. */
94#define NUMBER_INTERVAL 1
95
96#define TABSIZE 8
97
98#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
99
100#ifndef GIT_CONFIG
101#define GIT_CONFIG "git config"
102#endif
103
104#define TIG_LS_REMOTE \
105 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
106
107#define TIG_DIFF_CMD \
108 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
109
110#define TIG_LOG_CMD \
111 "git log --cc --stat -n100 %s 2>/dev/null"
112
113#define TIG_MAIN_CMD \
114 "git log --topo-order --pretty=raw %s 2>/dev/null"
115
116#define TIG_TREE_CMD \
117 "git ls-tree %s %s"
118
119#define TIG_BLOB_CMD \
120 "git cat-file blob %s"
121
122/* XXX: Needs to be defined to the empty string. */
123#define TIG_HELP_CMD ""
124#define TIG_PAGER_CMD ""
125#define TIG_STATUS_CMD ""
126#define TIG_STAGE_CMD ""
127
128/* Some ascii-shorthands fitted into the ncurses namespace. */
129#define KEY_TAB '\t'
130#define KEY_RETURN '\r'
131#define KEY_ESC 27
132
133
134struct ref {
135 char *name; /* Ref name; tag or head names are shortened. */
136 char id[SIZEOF_REV]; /* Commit SHA1 ID */
137 unsigned int tag:1; /* Is it a tag? */
138 unsigned int remote:1; /* Is it a remote ref? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
140};
141
142static struct ref **get_refs(char *id);
143
144struct int_map {
145 const char *name;
146 int namelen;
147 int value;
148};
149
150static int
151set_from_int_map(struct int_map *map, size_t map_size,
152 int *value, const char *name, int namelen)
153{
154
155 int i;
156
157 for (i = 0; i < map_size; i++)
158 if (namelen == map[i].namelen &&
159 !strncasecmp(name, map[i].name, namelen)) {
160 *value = map[i].value;
161 return OK;
162 }
163
164 return ERR;
165}
166
167
168/*
169 * String helpers
170 */
171
172static inline void
173string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
174{
175 if (srclen > dstlen - 1)
176 srclen = dstlen - 1;
177
178 strncpy(dst, src, srclen);
179 dst[srclen] = 0;
180}
181
182/* Shorthands for safely copying into a fixed buffer. */
183
184#define string_copy(dst, src) \
185 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
186
187#define string_ncopy(dst, src, srclen) \
188 string_ncopy_do(dst, sizeof(dst), src, srclen)
189
190#define string_copy_rev(dst, src) \
191 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
192
193#define string_add(dst, from, src) \
194 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
195
196static char *
197chomp_string(char *name)
198{
199 int namelen;
200
201 while (isspace(*name))
202 name++;
203
204 namelen = strlen(name) - 1;
205 while (namelen > 0 && isspace(name[namelen]))
206 name[namelen--] = 0;
207
208 return name;
209}
210
211static bool
212string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
213{
214 va_list args;
215 size_t pos = bufpos ? *bufpos : 0;
216
217 va_start(args, fmt);
218 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
219 va_end(args);
220
221 if (bufpos)
222 *bufpos = pos;
223
224 return pos >= bufsize ? FALSE : TRUE;
225}
226
227#define string_format(buf, fmt, args...) \
228 string_nformat(buf, sizeof(buf), NULL, fmt, args)
229
230#define string_format_from(buf, from, fmt, args...) \
231 string_nformat(buf, sizeof(buf), from, fmt, args)
232
233static int
234string_enum_compare(const char *str1, const char *str2, int len)
235{
236 size_t i;
237
238#define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
239
240 /* Diff-Header == DIFF_HEADER */
241 for (i = 0; i < len; i++) {
242 if (toupper(str1[i]) == toupper(str2[i]))
243 continue;
244
245 if (string_enum_sep(str1[i]) &&
246 string_enum_sep(str2[i]))
247 continue;
248
249 return str1[i] - str2[i];
250 }
251
252 return 0;
253}
254
255/* Shell quoting
256 *
257 * NOTE: The following is a slightly modified copy of the git project's shell
258 * quoting routines found in the quote.c file.
259 *
260 * Help to copy the thing properly quoted for the shell safety. any single
261 * quote is replaced with '\'', any exclamation point is replaced with '\!',
262 * and the whole thing is enclosed in a
263 *
264 * E.g.
265 * original sq_quote result
266 * name ==> name ==> 'name'
267 * a b ==> a b ==> 'a b'
268 * a'b ==> a'\''b ==> 'a'\''b'
269 * a!b ==> a'\!'b ==> 'a'\!'b'
270 */
271
272static size_t
273sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
274{
275 char c;
276
277#define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
278
279 BUFPUT('\'');
280 while ((c = *src++)) {
281 if (c == '\'' || c == '!') {
282 BUFPUT('\'');
283 BUFPUT('\\');
284 BUFPUT(c);
285 BUFPUT('\'');
286 } else {
287 BUFPUT(c);
288 }
289 }
290 BUFPUT('\'');
291
292 if (bufsize < SIZEOF_STR)
293 buf[bufsize] = 0;
294
295 return bufsize;
296}
297
298
299/*
300 * User requests
301 */
302
303#define REQ_INFO \
304 /* XXX: Keep the view request first and in sync with views[]. */ \
305 REQ_GROUP("View switching") \
306 REQ_(VIEW_MAIN, "Show main view"), \
307 REQ_(VIEW_DIFF, "Show diff view"), \
308 REQ_(VIEW_LOG, "Show log view"), \
309 REQ_(VIEW_TREE, "Show tree view"), \
310 REQ_(VIEW_BLOB, "Show blob view"), \
311 REQ_(VIEW_HELP, "Show help page"), \
312 REQ_(VIEW_PAGER, "Show pager view"), \
313 REQ_(VIEW_STATUS, "Show status view"), \
314 REQ_(VIEW_STAGE, "Show stage view"), \
315 \
316 REQ_GROUP("View manipulation") \
317 REQ_(ENTER, "Enter current line and scroll"), \
318 REQ_(NEXT, "Move to next"), \
319 REQ_(PREVIOUS, "Move to previous"), \
320 REQ_(VIEW_NEXT, "Move focus to next view"), \
321 REQ_(REFRESH, "Reload and refresh"), \
322 REQ_(VIEW_CLOSE, "Close the current view"), \
323 REQ_(QUIT, "Close all views and quit"), \
324 \
325 REQ_GROUP("Cursor navigation") \
326 REQ_(MOVE_UP, "Move cursor one line up"), \
327 REQ_(MOVE_DOWN, "Move cursor one line down"), \
328 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
329 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
330 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
331 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
332 \
333 REQ_GROUP("Scrolling") \
334 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
335 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
336 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
337 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
338 \
339 REQ_GROUP("Searching") \
340 REQ_(SEARCH, "Search the view"), \
341 REQ_(SEARCH_BACK, "Search backwards in the view"), \
342 REQ_(FIND_NEXT, "Find next search match"), \
343 REQ_(FIND_PREV, "Find previous search match"), \
344 \
345 REQ_GROUP("Misc") \
346 REQ_(NONE, "Do nothing"), \
347 REQ_(PROMPT, "Bring up the prompt"), \
348 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
349 REQ_(SCREEN_RESIZE, "Resize the screen"), \
350 REQ_(SHOW_VERSION, "Show version information"), \
351 REQ_(STOP_LOADING, "Stop all loading views"), \
352 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
353 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
354 REQ_(STATUS_UPDATE, "Update file status"), \
355 REQ_(STATUS_MERGE, "Merge file using external tool"), \
356 REQ_(EDIT, "Open in editor"), \
357 REQ_(CHERRY_PICK, "Cherry-pick commit to current branch")
358
359
360/* User action requests. */
361enum request {
362#define REQ_GROUP(help)
363#define REQ_(req, help) REQ_##req
364
365 /* Offset all requests to avoid conflicts with ncurses getch values. */
366 REQ_OFFSET = KEY_MAX + 1,
367 REQ_INFO,
368 REQ_UNKNOWN,
369
370#undef REQ_GROUP
371#undef REQ_
372};
373
374struct request_info {
375 enum request request;
376 char *name;
377 int namelen;
378 char *help;
379};
380
381static struct request_info req_info[] = {
382#define REQ_GROUP(help) { 0, NULL, 0, (help) },
383#define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
384 REQ_INFO
385#undef REQ_GROUP
386#undef REQ_
387};
388
389static enum request
390get_request(const char *name)
391{
392 int namelen = strlen(name);
393 int i;
394
395 for (i = 0; i < ARRAY_SIZE(req_info); i++)
396 if (req_info[i].namelen == namelen &&
397 !string_enum_compare(req_info[i].name, name, namelen))
398 return req_info[i].request;
399
400 return REQ_UNKNOWN;
401}
402
403
404/*
405 * Options
406 */
407
408static const char usage[] =
409"tig " TIG_VERSION " (" __DATE__ ")\n"
410"\n"
411"Usage: tig [options]\n"
412" or: tig [options] [--] [git log options]\n"
413" or: tig [options] log [git log options]\n"
414" or: tig [options] diff [git diff options]\n"
415" or: tig [options] show [git show options]\n"
416" or: tig [options] < [git command output]\n"
417"\n"
418"Options:\n"
419" -l Start up in log view\n"
420" -d Start up in diff view\n"
421" -S Start up in status view\n"
422" -n[I], --line-number[=I] Show line numbers with given interval\n"
423" -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
424" -- Mark end of tig options\n"
425" -v, --version Show version and exit\n"
426" -h, --help Show help message and exit\n";
427
428/* Option and state variables. */
429static bool opt_line_number = FALSE;
430static bool opt_rev_graph = FALSE;
431static int opt_num_interval = NUMBER_INTERVAL;
432static int opt_tab_size = TABSIZE;
433static enum request opt_request = REQ_VIEW_MAIN;
434static char opt_cmd[SIZEOF_STR] = "";
435static char opt_path[SIZEOF_STR] = "";
436static FILE *opt_pipe = NULL;
437static char opt_encoding[20] = "UTF-8";
438static bool opt_utf8 = TRUE;
439static char opt_codeset[20] = "UTF-8";
440static iconv_t opt_iconv = ICONV_NONE;
441static char opt_search[SIZEOF_STR] = "";
442static char opt_cdup[SIZEOF_STR] = "";
443static char opt_git_dir[SIZEOF_STR] = "";
444static char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
445static char opt_editor[SIZEOF_STR] = "";
446
447enum option_type {
448 OPT_NONE,
449 OPT_INT,
450};
451
452static bool
453check_option(char *opt, char short_name, char *name, enum option_type type, ...)
454{
455 va_list args;
456 char *value = "";
457 int *number;
458
459 if (opt[0] != '-')
460 return FALSE;
461
462 if (opt[1] == '-') {
463 int namelen = strlen(name);
464
465 opt += 2;
466
467 if (strncmp(opt, name, namelen))
468 return FALSE;
469
470 if (opt[namelen] == '=')
471 value = opt + namelen + 1;
472
473 } else {
474 if (!short_name || opt[1] != short_name)
475 return FALSE;
476 value = opt + 2;
477 }
478
479 va_start(args, type);
480 if (type == OPT_INT) {
481 number = va_arg(args, int *);
482 if (isdigit(*value))
483 *number = atoi(value);
484 }
485 va_end(args);
486
487 return TRUE;
488}
489
490/* Returns the index of log or diff command or -1 to exit. */
491static bool
492parse_options(int argc, char *argv[])
493{
494 int i;
495
496 for (i = 1; i < argc; i++) {
497 char *opt = argv[i];
498
499 if (!strcmp(opt, "log") ||
500 !strcmp(opt, "diff") ||
501 !strcmp(opt, "show")) {
502 opt_request = opt[0] == 'l'
503 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
504 break;
505 }
506
507 if (opt[0] && opt[0] != '-')
508 break;
509
510 if (!strcmp(opt, "-l")) {
511 opt_request = REQ_VIEW_LOG;
512 continue;
513 }
514
515 if (!strcmp(opt, "-d")) {
516 opt_request = REQ_VIEW_DIFF;
517 continue;
518 }
519
520 if (!strcmp(opt, "-S")) {
521 opt_request = REQ_VIEW_STATUS;
522 continue;
523 }
524
525 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
526 opt_line_number = TRUE;
527 continue;
528 }
529
530 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
531 opt_tab_size = MIN(opt_tab_size, TABSIZE);
532 continue;
533 }
534
535 if (check_option(opt, 'v', "version", OPT_NONE)) {
536 printf("tig version %s\n", TIG_VERSION);
537 return FALSE;
538 }
539
540 if (check_option(opt, 'h', "help", OPT_NONE)) {
541 printf(usage);
542 return FALSE;
543 }
544
545 if (!strcmp(opt, "--")) {
546 i++;
547 break;
548 }
549
550 die("unknown option '%s'\n\n%s", opt, usage);
551 }
552
553 if (!isatty(STDIN_FILENO)) {
554 opt_request = REQ_VIEW_PAGER;
555 opt_pipe = stdin;
556
557 } else if (i < argc) {
558 size_t buf_size;
559
560 if (opt_request == REQ_VIEW_MAIN)
561 /* XXX: This is vulnerable to the user overriding
562 * options required for the main view parser. */
563 string_copy(opt_cmd, "git log --pretty=raw");
564 else
565 string_copy(opt_cmd, "git");
566 buf_size = strlen(opt_cmd);
567
568 while (buf_size < sizeof(opt_cmd) && i < argc) {
569 opt_cmd[buf_size++] = ' ';
570 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
571 }
572
573 if (buf_size >= sizeof(opt_cmd))
574 die("command too long");
575
576 opt_cmd[buf_size] = 0;
577 }
578
579 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
580 opt_utf8 = FALSE;
581
582 return TRUE;
583}
584
585
586/*
587 * Line-oriented content detection.
588 */
589
590#define LINE_INFO \
591LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
592LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
593LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
594LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
595LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
596LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
597LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
598LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
599LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
600LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
601LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
602LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
603LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
604LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
605LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
606LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
607LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
608LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
609LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
610LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
611LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
612LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
613LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
614LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
615LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
616LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
617LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
618LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
619LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
620LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
621LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
622LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
623LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
624LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
625LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
626LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
627LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
628LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
629LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
630LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
631LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
632LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
633LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
634LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
635LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
636LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
637LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
638
639enum line_type {
640#define LINE(type, line, fg, bg, attr) \
641 LINE_##type
642 LINE_INFO
643#undef LINE
644};
645
646struct line_info {
647 const char *name; /* Option name. */
648 int namelen; /* Size of option name. */
649 const char *line; /* The start of line to match. */
650 int linelen; /* Size of string to match. */
651 int fg, bg, attr; /* Color and text attributes for the lines. */
652};
653
654static struct line_info line_info[] = {
655#define LINE(type, line, fg, bg, attr) \
656 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
657 LINE_INFO
658#undef LINE
659};
660
661static enum line_type
662get_line_type(char *line)
663{
664 int linelen = strlen(line);
665 enum line_type type;
666
667 for (type = 0; type < ARRAY_SIZE(line_info); type++)
668 /* Case insensitive search matches Signed-off-by lines better. */
669 if (linelen >= line_info[type].linelen &&
670 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
671 return type;
672
673 return LINE_DEFAULT;
674}
675
676static inline int
677get_line_attr(enum line_type type)
678{
679 assert(type < ARRAY_SIZE(line_info));
680 return COLOR_PAIR(type) | line_info[type].attr;
681}
682
683static struct line_info *
684get_line_info(char *name, int namelen)
685{
686 enum line_type type;
687
688 for (type = 0; type < ARRAY_SIZE(line_info); type++)
689 if (namelen == line_info[type].namelen &&
690 !string_enum_compare(line_info[type].name, name, namelen))
691 return &line_info[type];
692
693 return NULL;
694}
695
696static void
697init_colors(void)
698{
699 int default_bg = COLOR_BLACK;
700 int default_fg = COLOR_WHITE;
701 enum line_type type;
702
703 start_color();
704
705 if (use_default_colors() != ERR) {
706 default_bg = -1;
707 default_fg = -1;
708 }
709
710 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
711 struct line_info *info = &line_info[type];
712 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
713 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
714
715 init_pair(type, fg, bg);
716 }
717}
718
719struct line {
720 enum line_type type;
721
722 /* State flags */
723 unsigned int selected:1;
724
725 void *data; /* User data */
726};
727
728
729/*
730 * Keys
731 */
732
733struct keybinding {
734 int alias;
735 enum request request;
736 struct keybinding *next;
737};
738
739static struct keybinding default_keybindings[] = {
740 /* View switching */
741 { 'm', REQ_VIEW_MAIN },
742 { 'd', REQ_VIEW_DIFF },
743 { 'l', REQ_VIEW_LOG },
744 { 't', REQ_VIEW_TREE },
745 { 'f', REQ_VIEW_BLOB },
746 { 'p', REQ_VIEW_PAGER },
747 { 'h', REQ_VIEW_HELP },
748 { 'S', REQ_VIEW_STATUS },
749 { 'c', REQ_VIEW_STAGE },
750
751 /* View manipulation */
752 { 'q', REQ_VIEW_CLOSE },
753 { KEY_TAB, REQ_VIEW_NEXT },
754 { KEY_RETURN, REQ_ENTER },
755 { KEY_UP, REQ_PREVIOUS },
756 { KEY_DOWN, REQ_NEXT },
757 { 'R', REQ_REFRESH },
758
759 /* Cursor navigation */
760 { 'k', REQ_MOVE_UP },
761 { 'j', REQ_MOVE_DOWN },
762 { KEY_HOME, REQ_MOVE_FIRST_LINE },
763 { KEY_END, REQ_MOVE_LAST_LINE },
764 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
765 { ' ', REQ_MOVE_PAGE_DOWN },
766 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
767 { 'b', REQ_MOVE_PAGE_UP },
768 { '-', REQ_MOVE_PAGE_UP },
769
770 /* Scrolling */
771 { KEY_IC, REQ_SCROLL_LINE_UP },
772 { KEY_DC, REQ_SCROLL_LINE_DOWN },
773 { 'w', REQ_SCROLL_PAGE_UP },
774 { 's', REQ_SCROLL_PAGE_DOWN },
775
776 /* Searching */
777 { '/', REQ_SEARCH },
778 { '?', REQ_SEARCH_BACK },
779 { 'n', REQ_FIND_NEXT },
780 { 'N', REQ_FIND_PREV },
781
782 /* Misc */
783 { 'Q', REQ_QUIT },
784 { 'z', REQ_STOP_LOADING },
785 { 'v', REQ_SHOW_VERSION },
786 { 'r', REQ_SCREEN_REDRAW },
787 { '.', REQ_TOGGLE_LINENO },
788 { 'g', REQ_TOGGLE_REV_GRAPH },
789 { ':', REQ_PROMPT },
790 { 'u', REQ_STATUS_UPDATE },
791 { 'M', REQ_STATUS_MERGE },
792 { 'e', REQ_EDIT },
793 { 'C', REQ_CHERRY_PICK },
794
795 /* Using the ncurses SIGWINCH handler. */
796 { KEY_RESIZE, REQ_SCREEN_RESIZE },
797};
798
799#define KEYMAP_INFO \
800 KEYMAP_(GENERIC), \
801 KEYMAP_(MAIN), \
802 KEYMAP_(DIFF), \
803 KEYMAP_(LOG), \
804 KEYMAP_(TREE), \
805 KEYMAP_(BLOB), \
806 KEYMAP_(PAGER), \
807 KEYMAP_(HELP), \
808 KEYMAP_(STATUS), \
809 KEYMAP_(STAGE)
810
811enum keymap {
812#define KEYMAP_(name) KEYMAP_##name
813 KEYMAP_INFO
814#undef KEYMAP_
815};
816
817static struct int_map keymap_table[] = {
818#define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
819 KEYMAP_INFO
820#undef KEYMAP_
821};
822
823#define set_keymap(map, name) \
824 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
825
826static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
827
828static void
829add_keybinding(enum keymap keymap, enum request request, int key)
830{
831 struct keybinding *keybinding;
832
833 keybinding = calloc(1, sizeof(*keybinding));
834 if (!keybinding)
835 die("Failed to allocate keybinding");
836
837 keybinding->alias = key;
838 keybinding->request = request;
839 keybinding->next = keybindings[keymap];
840 keybindings[keymap] = keybinding;
841}
842
843/* Looks for a key binding first in the given map, then in the generic map, and
844 * lastly in the default keybindings. */
845static enum request
846get_keybinding(enum keymap keymap, int key)
847{
848 struct keybinding *kbd;
849 int i;
850
851 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
852 if (kbd->alias == key)
853 return kbd->request;
854
855 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
856 if (kbd->alias == key)
857 return kbd->request;
858
859 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
860 if (default_keybindings[i].alias == key)
861 return default_keybindings[i].request;
862
863 return (enum request) key;
864}
865
866
867struct key {
868 char *name;
869 int value;
870};
871
872static struct key key_table[] = {
873 { "Enter", KEY_RETURN },
874 { "Space", ' ' },
875 { "Backspace", KEY_BACKSPACE },
876 { "Tab", KEY_TAB },
877 { "Escape", KEY_ESC },
878 { "Left", KEY_LEFT },
879 { "Right", KEY_RIGHT },
880 { "Up", KEY_UP },
881 { "Down", KEY_DOWN },
882 { "Insert", KEY_IC },
883 { "Delete", KEY_DC },
884 { "Hash", '#' },
885 { "Home", KEY_HOME },
886 { "End", KEY_END },
887 { "PageUp", KEY_PPAGE },
888 { "PageDown", KEY_NPAGE },
889 { "F1", KEY_F(1) },
890 { "F2", KEY_F(2) },
891 { "F3", KEY_F(3) },
892 { "F4", KEY_F(4) },
893 { "F5", KEY_F(5) },
894 { "F6", KEY_F(6) },
895 { "F7", KEY_F(7) },
896 { "F8", KEY_F(8) },
897 { "F9", KEY_F(9) },
898 { "F10", KEY_F(10) },
899 { "F11", KEY_F(11) },
900 { "F12", KEY_F(12) },
901};
902
903static int
904get_key_value(const char *name)
905{
906 int i;
907
908 for (i = 0; i < ARRAY_SIZE(key_table); i++)
909 if (!strcasecmp(key_table[i].name, name))
910 return key_table[i].value;
911
912 if (strlen(name) == 1 && isprint(*name))
913 return (int) *name;
914
915 return ERR;
916}
917
918static char *
919get_key(enum request request)
920{
921 static char buf[BUFSIZ];
922 static char key_char[] = "'X'";
923 size_t pos = 0;
924 char *sep = "";
925 int i;
926
927 buf[pos] = 0;
928
929 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
930 struct keybinding *keybinding = &default_keybindings[i];
931 char *seq = NULL;
932 int key;
933
934 if (keybinding->request != request)
935 continue;
936
937 for (key = 0; key < ARRAY_SIZE(key_table); key++)
938 if (key_table[key].value == keybinding->alias)
939 seq = key_table[key].name;
940
941 if (seq == NULL &&
942 keybinding->alias < 127 &&
943 isprint(keybinding->alias)) {
944 key_char[1] = (char) keybinding->alias;
945 seq = key_char;
946 }
947
948 if (!seq)
949 seq = "'?'";
950
951 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
952 return "Too many keybindings!";
953 sep = ", ";
954 }
955
956 return buf;
957}
958
959
960/*
961 * User config file handling.
962 */
963
964static struct int_map color_map[] = {
965#define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
966 COLOR_MAP(DEFAULT),
967 COLOR_MAP(BLACK),
968 COLOR_MAP(BLUE),
969 COLOR_MAP(CYAN),
970 COLOR_MAP(GREEN),
971 COLOR_MAP(MAGENTA),
972 COLOR_MAP(RED),
973 COLOR_MAP(WHITE),
974 COLOR_MAP(YELLOW),
975};
976
977#define set_color(color, name) \
978 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
979
980static struct int_map attr_map[] = {
981#define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
982 ATTR_MAP(NORMAL),
983 ATTR_MAP(BLINK),
984 ATTR_MAP(BOLD),
985 ATTR_MAP(DIM),
986 ATTR_MAP(REVERSE),
987 ATTR_MAP(STANDOUT),
988 ATTR_MAP(UNDERLINE),
989};
990
991#define set_attribute(attr, name) \
992 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
993
994static int config_lineno;
995static bool config_errors;
996static char *config_msg;
997
998/* Wants: object fgcolor bgcolor [attr] */
999static int
1000option_color_command(int argc, char *argv[])
1001{
1002 struct line_info *info;
1003
1004 if (argc != 3 && argc != 4) {
1005 config_msg = "Wrong number of arguments given to color command";
1006 return ERR;
1007 }
1008
1009 info = get_line_info(argv[0], strlen(argv[0]));
1010 if (!info) {
1011 config_msg = "Unknown color name";
1012 return ERR;
1013 }
1014
1015 if (set_color(&info->fg, argv[1]) == ERR ||
1016 set_color(&info->bg, argv[2]) == ERR) {
1017 config_msg = "Unknown color";
1018 return ERR;
1019 }
1020
1021 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1022 config_msg = "Unknown attribute";
1023 return ERR;
1024 }
1025
1026 return OK;
1027}
1028
1029/* Wants: name = value */
1030static int
1031option_set_command(int argc, char *argv[])
1032{
1033 if (argc != 3) {
1034 config_msg = "Wrong number of arguments given to set command";
1035 return ERR;
1036 }
1037
1038 if (strcmp(argv[1], "=")) {
1039 config_msg = "No value assigned";
1040 return ERR;
1041 }
1042
1043 if (!strcmp(argv[0], "show-rev-graph")) {
1044 opt_rev_graph = (!strcmp(argv[2], "1") ||
1045 !strcmp(argv[2], "true") ||
1046 !strcmp(argv[2], "yes"));
1047 return OK;
1048 }
1049
1050 if (!strcmp(argv[0], "line-number-interval")) {
1051 opt_num_interval = atoi(argv[2]);
1052 return OK;
1053 }
1054
1055 if (!strcmp(argv[0], "tab-size")) {
1056 opt_tab_size = atoi(argv[2]);
1057 return OK;
1058 }
1059
1060 if (!strcmp(argv[0], "commit-encoding")) {
1061 char *arg = argv[2];
1062 int delimiter = *arg;
1063 int i;
1064
1065 switch (delimiter) {
1066 case '"':
1067 case '\'':
1068 for (arg++, i = 0; arg[i]; i++)
1069 if (arg[i] == delimiter) {
1070 arg[i] = 0;
1071 break;
1072 }
1073 default:
1074 string_ncopy(opt_encoding, arg, strlen(arg));
1075 return OK;
1076 }
1077 }
1078
1079 config_msg = "Unknown variable name";
1080 return ERR;
1081}
1082
1083/* Wants: mode request key */
1084static int
1085option_bind_command(int argc, char *argv[])
1086{
1087 enum request request;
1088 int keymap;
1089 int key;
1090
1091 if (argc != 3) {
1092 config_msg = "Wrong number of arguments given to bind command";
1093 return ERR;
1094 }
1095
1096 if (set_keymap(&keymap, argv[0]) == ERR) {
1097 config_msg = "Unknown key map";
1098 return ERR;
1099 }
1100
1101 key = get_key_value(argv[1]);
1102 if (key == ERR) {
1103 config_msg = "Unknown key";
1104 return ERR;
1105 }
1106
1107 request = get_request(argv[2]);
1108 if (request == REQ_UNKNOWN) {
1109 config_msg = "Unknown request name";
1110 return ERR;
1111 }
1112
1113 add_keybinding(keymap, request, key);
1114
1115 return OK;
1116}
1117
1118static int
1119set_option(char *opt, char *value)
1120{
1121 char *argv[16];
1122 int valuelen;
1123 int argc = 0;
1124
1125 /* Tokenize */
1126 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1127 argv[argc++] = value;
1128
1129 value += valuelen;
1130 if (!*value)
1131 break;
1132
1133 *value++ = 0;
1134 while (isspace(*value))
1135 value++;
1136 }
1137
1138 if (!strcmp(opt, "color"))
1139 return option_color_command(argc, argv);
1140
1141 if (!strcmp(opt, "set"))
1142 return option_set_command(argc, argv);
1143
1144 if (!strcmp(opt, "bind"))
1145 return option_bind_command(argc, argv);
1146
1147 config_msg = "Unknown option command";
1148 return ERR;
1149}
1150
1151static int
1152read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1153{
1154 int status = OK;
1155
1156 config_lineno++;
1157 config_msg = "Internal error";
1158
1159 /* Check for comment markers, since read_properties() will
1160 * only ensure opt and value are split at first " \t". */
1161 optlen = strcspn(opt, "#");
1162 if (optlen == 0)
1163 return OK;
1164
1165 if (opt[optlen] != 0) {
1166 config_msg = "No option value";
1167 status = ERR;
1168
1169 } else {
1170 /* Look for comment endings in the value. */
1171 size_t len = strcspn(value, "#");
1172
1173 if (len < valuelen) {
1174 valuelen = len;
1175 value[valuelen] = 0;
1176 }
1177
1178 status = set_option(opt, value);
1179 }
1180
1181 if (status == ERR) {
1182 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1183 config_lineno, (int) optlen, opt, config_msg);
1184 config_errors = TRUE;
1185 }
1186
1187 /* Always keep going if errors are encountered. */
1188 return OK;
1189}
1190
1191static int
1192load_options(void)
1193{
1194 char *home = getenv("HOME");
1195 char buf[SIZEOF_STR];
1196 FILE *file;
1197
1198 config_lineno = 0;
1199 config_errors = FALSE;
1200
1201 if (!home || !string_format(buf, "%s/.tigrc", home))
1202 return ERR;
1203
1204 /* It's ok that the file doesn't exist. */
1205 file = fopen(buf, "r");
1206 if (!file)
1207 return OK;
1208
1209 if (read_properties(file, " \t", read_option) == ERR ||
1210 config_errors == TRUE)
1211 fprintf(stderr, "Errors while loading %s.\n", buf);
1212
1213 return OK;
1214}
1215
1216
1217/*
1218 * The viewer
1219 */
1220
1221struct view;
1222struct view_ops;
1223
1224/* The display array of active views and the index of the current view. */
1225static struct view *display[2];
1226static unsigned int current_view;
1227
1228/* Reading from the prompt? */
1229static bool input_mode = FALSE;
1230
1231#define foreach_displayed_view(view, i) \
1232 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1233
1234#define displayed_views() (display[1] != NULL ? 2 : 1)
1235
1236/* Current head and commit ID */
1237static char ref_blob[SIZEOF_REF] = "";
1238static char ref_commit[SIZEOF_REF] = "HEAD";
1239static char ref_head[SIZEOF_REF] = "HEAD";
1240
1241struct view {
1242 const char *name; /* View name */
1243 const char *cmd_fmt; /* Default command line format */
1244 const char *cmd_env; /* Command line set via environment */
1245 const char *id; /* Points to either of ref_{head,commit,blob} */
1246
1247 struct view_ops *ops; /* View operations */
1248
1249 enum keymap keymap; /* What keymap does this view have */
1250
1251 char cmd[SIZEOF_STR]; /* Command buffer */
1252 char ref[SIZEOF_REF]; /* Hovered commit reference */
1253 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1254
1255 int height, width; /* The width and height of the main window */
1256 WINDOW *win; /* The main window */
1257 WINDOW *title; /* The title window living below the main window */
1258
1259 /* Navigation */
1260 unsigned long offset; /* Offset of the window top */
1261 unsigned long lineno; /* Current line number */
1262
1263 /* Searching */
1264 char grep[SIZEOF_STR]; /* Search string */
1265 regex_t *regex; /* Pre-compiled regex */
1266
1267 /* If non-NULL, points to the view that opened this view. If this view
1268 * is closed tig will switch back to the parent view. */
1269 struct view *parent;
1270
1271 /* Buffering */
1272 unsigned long lines; /* Total number of lines */
1273 struct line *line; /* Line index */
1274 unsigned long line_size;/* Total number of allocated lines */
1275 unsigned int digits; /* Number of digits in the lines member. */
1276
1277 /* Loading */
1278 FILE *pipe;
1279 time_t start_time;
1280};
1281
1282struct view_ops {
1283 /* What type of content being displayed. Used in the title bar. */
1284 const char *type;
1285 /* Open and reads in all view content. */
1286 bool (*open)(struct view *view);
1287 /* Read one line; updates view->line. */
1288 bool (*read)(struct view *view, char *data);
1289 /* Draw one line; @lineno must be < view->height. */
1290 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1291 /* Depending on view handle a special requests. */
1292 enum request (*request)(struct view *view, enum request request, struct line *line);
1293 /* Search for regex in a line. */
1294 bool (*grep)(struct view *view, struct line *line);
1295 /* Select line */
1296 void (*select)(struct view *view, struct line *line);
1297};
1298
1299static struct view_ops pager_ops;
1300static struct view_ops main_ops;
1301static struct view_ops tree_ops;
1302static struct view_ops blob_ops;
1303static struct view_ops help_ops;
1304static struct view_ops status_ops;
1305static struct view_ops stage_ops;
1306
1307#define VIEW_STR(name, cmd, env, ref, ops, map) \
1308 { name, cmd, #env, ref, ops, map}
1309
1310#define VIEW_(id, name, ops, ref) \
1311 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1312
1313
1314static struct view views[] = {
1315 VIEW_(MAIN, "main", &main_ops, ref_head),
1316 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1317 VIEW_(LOG, "log", &pager_ops, ref_head),
1318 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1319 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1320 VIEW_(HELP, "help", &help_ops, ""),
1321 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1322 VIEW_(STATUS, "status", &status_ops, ""),
1323 VIEW_(STAGE, "stage", &stage_ops, ""),
1324};
1325
1326#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1327
1328#define foreach_view(view, i) \
1329 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1330
1331#define view_is_displayed(view) \
1332 (view == display[0] || view == display[1])
1333
1334static bool
1335draw_view_line(struct view *view, unsigned int lineno)
1336{
1337 struct line *line;
1338 bool selected = (view->offset + lineno == view->lineno);
1339 bool draw_ok;
1340
1341 assert(view_is_displayed(view));
1342
1343 if (view->offset + lineno >= view->lines)
1344 return FALSE;
1345
1346 line = &view->line[view->offset + lineno];
1347
1348 if (selected) {
1349 line->selected = TRUE;
1350 view->ops->select(view, line);
1351 } else if (line->selected) {
1352 line->selected = FALSE;
1353 wmove(view->win, lineno, 0);
1354 wclrtoeol(view->win);
1355 }
1356
1357 scrollok(view->win, FALSE);
1358 draw_ok = view->ops->draw(view, line, lineno, selected);
1359 scrollok(view->win, TRUE);
1360
1361 return draw_ok;
1362}
1363
1364static void
1365redraw_view_from(struct view *view, int lineno)
1366{
1367 assert(0 <= lineno && lineno < view->height);
1368
1369 for (; lineno < view->height; lineno++) {
1370 if (!draw_view_line(view, lineno))
1371 break;
1372 }
1373
1374 redrawwin(view->win);
1375 if (input_mode)
1376 wnoutrefresh(view->win);
1377 else
1378 wrefresh(view->win);
1379}
1380
1381static void
1382redraw_view(struct view *view)
1383{
1384 wclear(view->win);
1385 redraw_view_from(view, 0);
1386}
1387
1388
1389static void
1390update_view_title(struct view *view)
1391{
1392 char buf[SIZEOF_STR];
1393 char state[SIZEOF_STR];
1394 size_t bufpos = 0, statelen = 0;
1395
1396 assert(view_is_displayed(view));
1397
1398 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1399 unsigned int view_lines = view->offset + view->height;
1400 unsigned int lines = view->lines
1401 ? MIN(view_lines, view->lines) * 100 / view->lines
1402 : 0;
1403
1404 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1405 view->ops->type,
1406 view->lineno + 1,
1407 view->lines,
1408 lines);
1409
1410 if (view->pipe) {
1411 time_t secs = time(NULL) - view->start_time;
1412
1413 /* Three git seconds are a long time ... */
1414 if (secs > 2)
1415 string_format_from(state, &statelen, " %lds", secs);
1416 }
1417 }
1418
1419 string_format_from(buf, &bufpos, "[%s]", view->name);
1420 if (*view->ref && bufpos < view->width) {
1421 size_t refsize = strlen(view->ref);
1422 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1423
1424 if (minsize < view->width)
1425 refsize = view->width - minsize + 7;
1426 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1427 }
1428
1429 if (statelen && bufpos < view->width) {
1430 string_format_from(buf, &bufpos, " %s", state);
1431 }
1432
1433 if (view == display[current_view])
1434 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1435 else
1436 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1437
1438 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1439 wclrtoeol(view->title);
1440 wmove(view->title, 0, view->width - 1);
1441
1442 if (input_mode)
1443 wnoutrefresh(view->title);
1444 else
1445 wrefresh(view->title);
1446}
1447
1448static void
1449resize_display(void)
1450{
1451 int offset, i;
1452 struct view *base = display[0];
1453 struct view *view = display[1] ? display[1] : display[0];
1454
1455 /* Setup window dimensions */
1456
1457 getmaxyx(stdscr, base->height, base->width);
1458
1459 /* Make room for the status window. */
1460 base->height -= 1;
1461
1462 if (view != base) {
1463 /* Horizontal split. */
1464 view->width = base->width;
1465 view->height = SCALE_SPLIT_VIEW(base->height);
1466 base->height -= view->height;
1467
1468 /* Make room for the title bar. */
1469 view->height -= 1;
1470 }
1471
1472 /* Make room for the title bar. */
1473 base->height -= 1;
1474
1475 offset = 0;
1476
1477 foreach_displayed_view (view, i) {
1478 if (!view->win) {
1479 view->win = newwin(view->height, 0, offset, 0);
1480 if (!view->win)
1481 die("Failed to create %s view", view->name);
1482
1483 scrollok(view->win, TRUE);
1484
1485 view->title = newwin(1, 0, offset + view->height, 0);
1486 if (!view->title)
1487 die("Failed to create title window");
1488
1489 } else {
1490 wresize(view->win, view->height, view->width);
1491 mvwin(view->win, offset, 0);
1492 mvwin(view->title, offset + view->height, 0);
1493 }
1494
1495 offset += view->height + 1;
1496 }
1497}
1498
1499static void
1500redraw_display(void)
1501{
1502 struct view *view;
1503 int i;
1504
1505 foreach_displayed_view (view, i) {
1506 redraw_view(view);
1507 update_view_title(view);
1508 }
1509}
1510
1511static void
1512update_display_cursor(struct view *view)
1513{
1514 /* Move the cursor to the right-most column of the cursor line.
1515 *
1516 * XXX: This could turn out to be a bit expensive, but it ensures that
1517 * the cursor does not jump around. */
1518 if (view->lines) {
1519 wmove(view->win, view->lineno - view->offset, view->width - 1);
1520 wrefresh(view->win);
1521 }
1522}
1523
1524/*
1525 * Navigation
1526 */
1527
1528/* Scrolling backend */
1529static void
1530do_scroll_view(struct view *view, int lines)
1531{
1532 bool redraw_current_line = FALSE;
1533
1534 /* The rendering expects the new offset. */
1535 view->offset += lines;
1536
1537 assert(0 <= view->offset && view->offset < view->lines);
1538 assert(lines);
1539
1540 /* Move current line into the view. */
1541 if (view->lineno < view->offset) {
1542 view->lineno = view->offset;
1543 redraw_current_line = TRUE;
1544 } else if (view->lineno >= view->offset + view->height) {
1545 view->lineno = view->offset + view->height - 1;
1546 redraw_current_line = TRUE;
1547 }
1548
1549 assert(view->offset <= view->lineno && view->lineno < view->lines);
1550
1551 /* Redraw the whole screen if scrolling is pointless. */
1552 if (view->height < ABS(lines)) {
1553 redraw_view(view);
1554
1555 } else {
1556 int line = lines > 0 ? view->height - lines : 0;
1557 int end = line + ABS(lines);
1558
1559 wscrl(view->win, lines);
1560
1561 for (; line < end; line++) {
1562 if (!draw_view_line(view, line))
1563 break;
1564 }
1565
1566 if (redraw_current_line)
1567 draw_view_line(view, view->lineno - view->offset);
1568 }
1569
1570 redrawwin(view->win);
1571 wrefresh(view->win);
1572 report("");
1573}
1574
1575/* Scroll frontend */
1576static void
1577scroll_view(struct view *view, enum request request)
1578{
1579 int lines = 1;
1580
1581 assert(view_is_displayed(view));
1582
1583 switch (request) {
1584 case REQ_SCROLL_PAGE_DOWN:
1585 lines = view->height;
1586 case REQ_SCROLL_LINE_DOWN:
1587 if (view->offset + lines > view->lines)
1588 lines = view->lines - view->offset;
1589
1590 if (lines == 0 || view->offset + view->height >= view->lines) {
1591 report("Cannot scroll beyond the last line");
1592 return;
1593 }
1594 break;
1595
1596 case REQ_SCROLL_PAGE_UP:
1597 lines = view->height;
1598 case REQ_SCROLL_LINE_UP:
1599 if (lines > view->offset)
1600 lines = view->offset;
1601
1602 if (lines == 0) {
1603 report("Cannot scroll beyond the first line");
1604 return;
1605 }
1606
1607 lines = -lines;
1608 break;
1609
1610 default:
1611 die("request %d not handled in switch", request);
1612 }
1613
1614 do_scroll_view(view, lines);
1615}
1616
1617/* Cursor moving */
1618static void
1619move_view(struct view *view, enum request request)
1620{
1621 int scroll_steps = 0;
1622 int steps;
1623
1624 switch (request) {
1625 case REQ_MOVE_FIRST_LINE:
1626 steps = -view->lineno;
1627 break;
1628
1629 case REQ_MOVE_LAST_LINE:
1630 steps = view->lines - view->lineno - 1;
1631 break;
1632
1633 case REQ_MOVE_PAGE_UP:
1634 steps = view->height > view->lineno
1635 ? -view->lineno : -view->height;
1636 break;
1637
1638 case REQ_MOVE_PAGE_DOWN:
1639 steps = view->lineno + view->height >= view->lines
1640 ? view->lines - view->lineno - 1 : view->height;
1641 break;
1642
1643 case REQ_MOVE_UP:
1644 steps = -1;
1645 break;
1646
1647 case REQ_MOVE_DOWN:
1648 steps = 1;
1649 break;
1650
1651 default:
1652 die("request %d not handled in switch", request);
1653 }
1654
1655 if (steps <= 0 && view->lineno == 0) {
1656 report("Cannot move beyond the first line");
1657 return;
1658
1659 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1660 report("Cannot move beyond the last line");
1661 return;
1662 }
1663
1664 /* Move the current line */
1665 view->lineno += steps;
1666 assert(0 <= view->lineno && view->lineno < view->lines);
1667
1668 /* Check whether the view needs to be scrolled */
1669 if (view->lineno < view->offset ||
1670 view->lineno >= view->offset + view->height) {
1671 scroll_steps = steps;
1672 if (steps < 0 && -steps > view->offset) {
1673 scroll_steps = -view->offset;
1674
1675 } else if (steps > 0) {
1676 if (view->lineno == view->lines - 1 &&
1677 view->lines > view->height) {
1678 scroll_steps = view->lines - view->offset - 1;
1679 if (scroll_steps >= view->height)
1680 scroll_steps -= view->height - 1;
1681 }
1682 }
1683 }
1684
1685 if (!view_is_displayed(view)) {
1686 view->offset += scroll_steps;
1687 assert(0 <= view->offset && view->offset < view->lines);
1688 view->ops->select(view, &view->line[view->lineno]);
1689 return;
1690 }
1691
1692 /* Repaint the old "current" line if we be scrolling */
1693 if (ABS(steps) < view->height)
1694 draw_view_line(view, view->lineno - steps - view->offset);
1695
1696 if (scroll_steps) {
1697 do_scroll_view(view, scroll_steps);
1698 return;
1699 }
1700
1701 /* Draw the current line */
1702 draw_view_line(view, view->lineno - view->offset);
1703
1704 redrawwin(view->win);
1705 wrefresh(view->win);
1706 report("");
1707}
1708
1709
1710/*
1711 * Searching
1712 */
1713
1714static void search_view(struct view *view, enum request request);
1715
1716static bool
1717find_next_line(struct view *view, unsigned long lineno, struct line *line)
1718{
1719 assert(view_is_displayed(view));
1720
1721 if (!view->ops->grep(view, line))
1722 return FALSE;
1723
1724 if (lineno - view->offset >= view->height) {
1725 view->offset = lineno;
1726 view->lineno = lineno;
1727 redraw_view(view);
1728
1729 } else {
1730 unsigned long old_lineno = view->lineno - view->offset;
1731
1732 view->lineno = lineno;
1733 draw_view_line(view, old_lineno);
1734
1735 draw_view_line(view, view->lineno - view->offset);
1736 redrawwin(view->win);
1737 wrefresh(view->win);
1738 }
1739
1740 report("Line %ld matches '%s'", lineno + 1, view->grep);
1741 return TRUE;
1742}
1743
1744static void
1745find_next(struct view *view, enum request request)
1746{
1747 unsigned long lineno = view->lineno;
1748 int direction;
1749
1750 if (!*view->grep) {
1751 if (!*opt_search)
1752 report("No previous search");
1753 else
1754 search_view(view, request);
1755 return;
1756 }
1757
1758 switch (request) {
1759 case REQ_SEARCH:
1760 case REQ_FIND_NEXT:
1761 direction = 1;
1762 break;
1763
1764 case REQ_SEARCH_BACK:
1765 case REQ_FIND_PREV:
1766 direction = -1;
1767 break;
1768
1769 default:
1770 return;
1771 }
1772
1773 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1774 lineno += direction;
1775
1776 /* Note, lineno is unsigned long so will wrap around in which case it
1777 * will become bigger than view->lines. */
1778 for (; lineno < view->lines; lineno += direction) {
1779 struct line *line = &view->line[lineno];
1780
1781 if (find_next_line(view, lineno, line))
1782 return;
1783 }
1784
1785 report("No match found for '%s'", view->grep);
1786}
1787
1788static void
1789search_view(struct view *view, enum request request)
1790{
1791 int regex_err;
1792
1793 if (view->regex) {
1794 regfree(view->regex);
1795 *view->grep = 0;
1796 } else {
1797 view->regex = calloc(1, sizeof(*view->regex));
1798 if (!view->regex)
1799 return;
1800 }
1801
1802 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1803 if (regex_err != 0) {
1804 char buf[SIZEOF_STR] = "unknown error";
1805
1806 regerror(regex_err, view->regex, buf, sizeof(buf));
1807 report("Search failed: %s", buf);
1808 return;
1809 }
1810
1811 string_copy(view->grep, opt_search);
1812
1813 find_next(view, request);
1814}
1815
1816/*
1817 * Incremental updating
1818 */
1819
1820static void
1821end_update(struct view *view)
1822{
1823 if (!view->pipe)
1824 return;
1825 set_nonblocking_input(FALSE);
1826 if (view->pipe == stdin)
1827 fclose(view->pipe);
1828 else
1829 pclose(view->pipe);
1830 view->pipe = NULL;
1831}
1832
1833static bool
1834begin_update(struct view *view)
1835{
1836 if (view->pipe)
1837 end_update(view);
1838
1839 if (opt_cmd[0]) {
1840 string_copy(view->cmd, opt_cmd);
1841 opt_cmd[0] = 0;
1842 /* When running random commands, initially show the
1843 * command in the title. However, it maybe later be
1844 * overwritten if a commit line is selected. */
1845 if (view == VIEW(REQ_VIEW_PAGER))
1846 string_copy(view->ref, view->cmd);
1847 else
1848 view->ref[0] = 0;
1849
1850 } else if (view == VIEW(REQ_VIEW_TREE)) {
1851 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1852 char path[SIZEOF_STR];
1853
1854 if (strcmp(view->vid, view->id))
1855 opt_path[0] = path[0] = 0;
1856 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1857 return FALSE;
1858
1859 if (!string_format(view->cmd, format, view->id, path))
1860 return FALSE;
1861
1862 } else {
1863 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1864 const char *id = view->id;
1865
1866 if (!string_format(view->cmd, format, id, id, id, id, id))
1867 return FALSE;
1868
1869 /* Put the current ref_* value to the view title ref
1870 * member. This is needed by the blob view. Most other
1871 * views sets it automatically after loading because the
1872 * first line is a commit line. */
1873 string_copy_rev(view->ref, view->id);
1874 }
1875
1876 /* Special case for the pager view. */
1877 if (opt_pipe) {
1878 view->pipe = opt_pipe;
1879 opt_pipe = NULL;
1880 } else {
1881 view->pipe = popen(view->cmd, "r");
1882 }
1883
1884 if (!view->pipe)
1885 return FALSE;
1886
1887 set_nonblocking_input(TRUE);
1888
1889 view->offset = 0;
1890 view->lines = 0;
1891 view->lineno = 0;
1892 string_copy_rev(view->vid, view->id);
1893
1894 if (view->line) {
1895 int i;
1896
1897 for (i = 0; i < view->lines; i++)
1898 if (view->line[i].data)
1899 free(view->line[i].data);
1900
1901 free(view->line);
1902 view->line = NULL;
1903 }
1904
1905 view->start_time = time(NULL);
1906
1907 return TRUE;
1908}
1909
1910static struct line *
1911realloc_lines(struct view *view, size_t line_size)
1912{
1913 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1914
1915 if (!tmp)
1916 return NULL;
1917
1918 view->line = tmp;
1919 view->line_size = line_size;
1920 return view->line;
1921}
1922
1923static bool
1924update_view(struct view *view)
1925{
1926 char in_buffer[BUFSIZ];
1927 char out_buffer[BUFSIZ * 2];
1928 char *line;
1929 /* The number of lines to read. If too low it will cause too much
1930 * redrawing (and possible flickering), if too high responsiveness
1931 * will suffer. */
1932 unsigned long lines = view->height;
1933 int redraw_from = -1;
1934
1935 if (!view->pipe)
1936 return TRUE;
1937
1938 /* Only redraw if lines are visible. */
1939 if (view->offset + view->height >= view->lines)
1940 redraw_from = view->lines - view->offset;
1941
1942 /* FIXME: This is probably not perfect for backgrounded views. */
1943 if (!realloc_lines(view, view->lines + lines))
1944 goto alloc_error;
1945
1946 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1947 size_t linelen = strlen(line);
1948
1949 if (linelen)
1950 line[linelen - 1] = 0;
1951
1952 if (opt_iconv != ICONV_NONE) {
1953 ICONV_CONST char *inbuf = line;
1954 size_t inlen = linelen;
1955
1956 char *outbuf = out_buffer;
1957 size_t outlen = sizeof(out_buffer);
1958
1959 size_t ret;
1960
1961 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1962 if (ret != (size_t) -1) {
1963 line = out_buffer;
1964 linelen = strlen(out_buffer);
1965 }
1966 }
1967
1968 if (!view->ops->read(view, line))
1969 goto alloc_error;
1970
1971 if (lines-- == 1)
1972 break;
1973 }
1974
1975 {
1976 int digits;
1977
1978 lines = view->lines;
1979 for (digits = 0; lines; digits++)
1980 lines /= 10;
1981
1982 /* Keep the displayed view in sync with line number scaling. */
1983 if (digits != view->digits) {
1984 view->digits = digits;
1985 redraw_from = 0;
1986 }
1987 }
1988
1989 if (!view_is_displayed(view))
1990 goto check_pipe;
1991
1992 if (view == VIEW(REQ_VIEW_TREE)) {
1993 /* Clear the view and redraw everything since the tree sorting
1994 * might have rearranged things. */
1995 redraw_view(view);
1996
1997 } else if (redraw_from >= 0) {
1998 /* If this is an incremental update, redraw the previous line
1999 * since for commits some members could have changed when
2000 * loading the main view. */
2001 if (redraw_from > 0)
2002 redraw_from--;
2003
2004 /* Since revision graph visualization requires knowledge
2005 * about the parent commit, it causes a further one-off
2006 * needed to be redrawn for incremental updates. */
2007 if (redraw_from > 0 && opt_rev_graph)
2008 redraw_from--;
2009
2010 /* Incrementally draw avoids flickering. */
2011 redraw_view_from(view, redraw_from);
2012 }
2013
2014 /* Update the title _after_ the redraw so that if the redraw picks up a
2015 * commit reference in view->ref it'll be available here. */
2016 update_view_title(view);
2017
2018check_pipe:
2019 if (ferror(view->pipe)) {
2020 report("Failed to read: %s", strerror(errno));
2021 goto end;
2022
2023 } else if (feof(view->pipe)) {
2024 report("");
2025 goto end;
2026 }
2027
2028 return TRUE;
2029
2030alloc_error:
2031 report("Allocation failure");
2032
2033end:
2034 view->ops->read(view, NULL);
2035 end_update(view);
2036 return FALSE;
2037}
2038
2039static struct line *
2040add_line_data(struct view *view, void *data, enum line_type type)
2041{
2042 struct line *line = &view->line[view->lines++];
2043
2044 memset(line, 0, sizeof(*line));
2045 line->type = type;
2046 line->data = data;
2047
2048 return line;
2049}
2050
2051static struct line *
2052add_line_text(struct view *view, char *data, enum line_type type)
2053{
2054 if (data)
2055 data = strdup(data);
2056
2057 return data ? add_line_data(view, data, type) : NULL;
2058}
2059
2060
2061/*
2062 * View opening
2063 */
2064
2065enum open_flags {
2066 OPEN_DEFAULT = 0, /* Use default view switching. */
2067 OPEN_SPLIT = 1, /* Split current view. */
2068 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2069 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2070};
2071
2072static void
2073open_view(struct view *prev, enum request request, enum open_flags flags)
2074{
2075 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2076 bool split = !!(flags & OPEN_SPLIT);
2077 bool reload = !!(flags & OPEN_RELOAD);
2078 struct view *view = VIEW(request);
2079 int nviews = displayed_views();
2080 struct view *base_view = display[0];
2081
2082 if (view == prev && nviews == 1 && !reload) {
2083 report("Already in %s view", view->name);
2084 return;
2085 }
2086
2087 if (view->ops->open) {
2088 if (!view->ops->open(view)) {
2089 report("Failed to load %s view", view->name);
2090 return;
2091 }
2092
2093 } else if ((reload || strcmp(view->vid, view->id)) &&
2094 !begin_update(view)) {
2095 report("Failed to load %s view", view->name);
2096 return;
2097 }
2098
2099 if (split) {
2100 display[1] = view;
2101 if (!backgrounded)
2102 current_view = 1;
2103 } else {
2104 /* Maximize the current view. */
2105 memset(display, 0, sizeof(display));
2106 current_view = 0;
2107 display[current_view] = view;
2108 }
2109
2110 /* Resize the view when switching between split- and full-screen,
2111 * or when switching between two different full-screen views. */
2112 if (nviews != displayed_views() ||
2113 (nviews == 1 && base_view != display[0]))
2114 resize_display();
2115
2116 if (split && prev->lineno - prev->offset >= prev->height) {
2117 /* Take the title line into account. */
2118 int lines = prev->lineno - prev->offset - prev->height + 1;
2119
2120 /* Scroll the view that was split if the current line is
2121 * outside the new limited view. */
2122 do_scroll_view(prev, lines);
2123 }
2124
2125 if (prev && view != prev) {
2126 if (split && !backgrounded) {
2127 /* "Blur" the previous view. */
2128 update_view_title(prev);
2129 }
2130
2131 view->parent = prev;
2132 }
2133
2134 if (view->pipe && view->lines == 0) {
2135 /* Clear the old view and let the incremental updating refill
2136 * the screen. */
2137 wclear(view->win);
2138 report("");
2139 } else {
2140 redraw_view(view);
2141 report("");
2142 }
2143
2144 /* If the view is backgrounded the above calls to report()
2145 * won't redraw the view title. */
2146 if (backgrounded)
2147 update_view_title(view);
2148}
2149
2150static void
2151open_external_viewer(const char *cmd)
2152{
2153 def_prog_mode(); /* save current tty modes */
2154 endwin(); /* restore original tty modes */
2155 system(cmd);
2156 fprintf(stderr, "Press Enter to continue");
2157 getc(stdin);
2158 reset_prog_mode();
2159 redraw_display();
2160}
2161
2162static void
2163open_mergetool(const char *file)
2164{
2165 char cmd[SIZEOF_STR];
2166 char file_sq[SIZEOF_STR];
2167
2168 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2169 string_format(cmd, "git mergetool %s", file_sq)) {
2170 open_external_viewer(cmd);
2171 }
2172}
2173
2174static void
2175open_editor(bool from_root, const char *file)
2176{
2177 char cmd[SIZEOF_STR];
2178 char file_sq[SIZEOF_STR];
2179 char *editor;
2180 char *prefix = from_root ? opt_cdup : "";
2181
2182 editor = getenv("GIT_EDITOR");
2183 if (!editor && *opt_editor)
2184 editor = opt_editor;
2185 if (!editor)
2186 editor = getenv("VISUAL");
2187 if (!editor)
2188 editor = getenv("EDITOR");
2189 if (!editor)
2190 editor = "vi";
2191
2192 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2193 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2194 open_external_viewer(cmd);
2195 }
2196}
2197
2198/*
2199 * User request switch noodle
2200 */
2201
2202static int
2203view_driver(struct view *view, enum request request)
2204{
2205 int i;
2206
2207 if (request == REQ_NONE) {
2208 doupdate();
2209 return TRUE;
2210 }
2211
2212 if (view && view->lines) {
2213 request = view->ops->request(view, request, &view->line[view->lineno]);
2214 if (request == REQ_NONE)
2215 return TRUE;
2216 }
2217
2218 switch (request) {
2219 case REQ_MOVE_UP:
2220 case REQ_MOVE_DOWN:
2221 case REQ_MOVE_PAGE_UP:
2222 case REQ_MOVE_PAGE_DOWN:
2223 case REQ_MOVE_FIRST_LINE:
2224 case REQ_MOVE_LAST_LINE:
2225 move_view(view, request);
2226 break;
2227
2228 case REQ_SCROLL_LINE_DOWN:
2229 case REQ_SCROLL_LINE_UP:
2230 case REQ_SCROLL_PAGE_DOWN:
2231 case REQ_SCROLL_PAGE_UP:
2232 scroll_view(view, request);
2233 break;
2234
2235 case REQ_VIEW_BLOB:
2236 if (!ref_blob[0]) {
2237 report("No file chosen, press %s to open tree view",
2238 get_key(REQ_VIEW_TREE));
2239 break;
2240 }
2241 open_view(view, request, OPEN_DEFAULT);
2242 break;
2243
2244 case REQ_VIEW_PAGER:
2245 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2246 report("No pager content, press %s to run command from prompt",
2247 get_key(REQ_PROMPT));
2248 break;
2249 }
2250 open_view(view, request, OPEN_DEFAULT);
2251 break;
2252
2253 case REQ_VIEW_STAGE:
2254 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2255 report("No stage content, press %s to open the status view and choose file",
2256 get_key(REQ_VIEW_STATUS));
2257 break;
2258 }
2259 open_view(view, request, OPEN_DEFAULT);
2260 break;
2261
2262 case REQ_VIEW_STATUS:
2263 if (opt_is_inside_work_tree == FALSE) {
2264 report("The status view requires a working tree");
2265 break;
2266 }
2267 open_view(view, request, OPEN_DEFAULT);
2268 break;
2269
2270 case REQ_VIEW_MAIN:
2271 case REQ_VIEW_DIFF:
2272 case REQ_VIEW_LOG:
2273 case REQ_VIEW_TREE:
2274 case REQ_VIEW_HELP:
2275 open_view(view, request, OPEN_DEFAULT);
2276 break;
2277
2278 case REQ_NEXT:
2279 case REQ_PREVIOUS:
2280 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2281
2282 if ((view == VIEW(REQ_VIEW_DIFF) &&
2283 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2284 (view == VIEW(REQ_VIEW_STAGE) &&
2285 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2286 (view == VIEW(REQ_VIEW_BLOB) &&
2287 view->parent == VIEW(REQ_VIEW_TREE))) {
2288 int line;
2289
2290 view = view->parent;
2291 line = view->lineno;
2292 move_view(view, request);
2293 if (view_is_displayed(view))
2294 update_view_title(view);
2295 if (line != view->lineno)
2296 view->ops->request(view, REQ_ENTER,
2297 &view->line[view->lineno]);
2298
2299 } else {
2300 move_view(view, request);
2301 }
2302 break;
2303
2304 case REQ_VIEW_NEXT:
2305 {
2306 int nviews = displayed_views();
2307 int next_view = (current_view + 1) % nviews;
2308
2309 if (next_view == current_view) {
2310 report("Only one view is displayed");
2311 break;
2312 }
2313
2314 current_view = next_view;
2315 /* Blur out the title of the previous view. */
2316 update_view_title(view);
2317 report("");
2318 break;
2319 }
2320 case REQ_REFRESH:
2321 report("Refreshing is not yet supported for the %s view", view->name);
2322 break;
2323
2324 case REQ_TOGGLE_LINENO:
2325 opt_line_number = !opt_line_number;
2326 redraw_display();
2327 break;
2328
2329 case REQ_TOGGLE_REV_GRAPH:
2330 opt_rev_graph = !opt_rev_graph;
2331 redraw_display();
2332 break;
2333
2334 case REQ_PROMPT:
2335 /* Always reload^Wrerun commands from the prompt. */
2336 open_view(view, opt_request, OPEN_RELOAD);
2337 break;
2338
2339 case REQ_SEARCH:
2340 case REQ_SEARCH_BACK:
2341 search_view(view, request);
2342 break;
2343
2344 case REQ_FIND_NEXT:
2345 case REQ_FIND_PREV:
2346 find_next(view, request);
2347 break;
2348
2349 case REQ_STOP_LOADING:
2350 for (i = 0; i < ARRAY_SIZE(views); i++) {
2351 view = &views[i];
2352 if (view->pipe)
2353 report("Stopped loading the %s view", view->name),
2354 end_update(view);
2355 }
2356 break;
2357
2358 case REQ_SHOW_VERSION:
2359 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2360 return TRUE;
2361
2362 case REQ_SCREEN_RESIZE:
2363 resize_display();
2364 /* Fall-through */
2365 case REQ_SCREEN_REDRAW:
2366 redraw_display();
2367 break;
2368
2369 case REQ_EDIT:
2370 report("Nothing to edit");
2371 break;
2372
2373 case REQ_CHERRY_PICK:
2374 report("Nothing to cherry-pick");
2375 break;
2376
2377 case REQ_ENTER:
2378 report("Nothing to enter");
2379 break;
2380
2381
2382 case REQ_VIEW_CLOSE:
2383 /* XXX: Mark closed views by letting view->parent point to the
2384 * view itself. Parents to closed view should never be
2385 * followed. */
2386 if (view->parent &&
2387 view->parent->parent != view->parent) {
2388 memset(display, 0, sizeof(display));
2389 current_view = 0;
2390 display[current_view] = view->parent;
2391 view->parent = view;
2392 resize_display();
2393 redraw_display();
2394 break;
2395 }
2396 /* Fall-through */
2397 case REQ_QUIT:
2398 return FALSE;
2399
2400 default:
2401 /* An unknown key will show most commonly used commands. */
2402 report("Unknown key, press 'h' for help");
2403 return TRUE;
2404 }
2405
2406 return TRUE;
2407}
2408
2409
2410/*
2411 * Pager backend
2412 */
2413
2414static bool
2415pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2416{
2417 char *text = line->data;
2418 enum line_type type = line->type;
2419 int textlen = strlen(text);
2420 int attr;
2421
2422 wmove(view->win, lineno, 0);
2423
2424 if (selected) {
2425 type = LINE_CURSOR;
2426 wchgat(view->win, -1, 0, type, NULL);
2427 }
2428
2429 attr = get_line_attr(type);
2430 wattrset(view->win, attr);
2431
2432 if (opt_line_number || opt_tab_size < TABSIZE) {
2433 static char spaces[] = " ";
2434 int col_offset = 0, col = 0;
2435
2436 if (opt_line_number) {
2437 unsigned long real_lineno = view->offset + lineno + 1;
2438
2439 if (real_lineno == 1 ||
2440 (real_lineno % opt_num_interval) == 0) {
2441 wprintw(view->win, "%.*d", view->digits, real_lineno);
2442
2443 } else {
2444 waddnstr(view->win, spaces,
2445 MIN(view->digits, STRING_SIZE(spaces)));
2446 }
2447 waddstr(view->win, ": ");
2448 col_offset = view->digits + 2;
2449 }
2450
2451 while (text && col_offset + col < view->width) {
2452 int cols_max = view->width - col_offset - col;
2453 char *pos = text;
2454 int cols;
2455
2456 if (*text == '\t') {
2457 text++;
2458 assert(sizeof(spaces) > TABSIZE);
2459 pos = spaces;
2460 cols = opt_tab_size - (col % opt_tab_size);
2461
2462 } else {
2463 text = strchr(text, '\t');
2464 cols = line ? text - pos : strlen(pos);
2465 }
2466
2467 waddnstr(view->win, pos, MIN(cols, cols_max));
2468 col += cols;
2469 }
2470
2471 } else {
2472 int col = 0, pos = 0;
2473
2474 for (; pos < textlen && col < view->width; pos++, col++)
2475 if (text[pos] == '\t')
2476 col += TABSIZE - (col % TABSIZE) - 1;
2477
2478 waddnstr(view->win, text, pos);
2479 }
2480
2481 return TRUE;
2482}
2483
2484static bool
2485add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2486{
2487 char refbuf[SIZEOF_STR];
2488 char *ref = NULL;
2489 FILE *pipe;
2490
2491 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2492 return TRUE;
2493
2494 pipe = popen(refbuf, "r");
2495 if (!pipe)
2496 return TRUE;
2497
2498 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2499 ref = chomp_string(ref);
2500 pclose(pipe);
2501
2502 if (!ref || !*ref)
2503 return TRUE;
2504
2505 /* This is the only fatal call, since it can "corrupt" the buffer. */
2506 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2507 return FALSE;
2508
2509 return TRUE;
2510}
2511
2512static void
2513add_pager_refs(struct view *view, struct line *line)
2514{
2515 char buf[SIZEOF_STR];
2516 char *commit_id = line->data + STRING_SIZE("commit ");
2517 struct ref **refs;
2518 size_t bufpos = 0, refpos = 0;
2519 const char *sep = "Refs: ";
2520 bool is_tag = FALSE;
2521
2522 assert(line->type == LINE_COMMIT);
2523
2524 refs = get_refs(commit_id);
2525 if (!refs) {
2526 if (view == VIEW(REQ_VIEW_DIFF))
2527 goto try_add_describe_ref;
2528 return;
2529 }
2530
2531 do {
2532 struct ref *ref = refs[refpos];
2533 char *fmt = ref->tag ? "%s[%s]" :
2534 ref->remote ? "%s<%s>" : "%s%s";
2535
2536 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2537 return;
2538 sep = ", ";
2539 if (ref->tag)
2540 is_tag = TRUE;
2541 } while (refs[refpos++]->next);
2542
2543 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2544try_add_describe_ref:
2545 /* Add <tag>-g<commit_id> "fake" reference. */
2546 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2547 return;
2548 }
2549
2550 if (bufpos == 0)
2551 return;
2552
2553 if (!realloc_lines(view, view->line_size + 1))
2554 return;
2555
2556 add_line_text(view, buf, LINE_PP_REFS);
2557}
2558
2559static bool
2560pager_read(struct view *view, char *data)
2561{
2562 struct line *line;
2563
2564 if (!data)
2565 return TRUE;
2566
2567 line = add_line_text(view, data, get_line_type(data));
2568 if (!line)
2569 return FALSE;
2570
2571 if (line->type == LINE_COMMIT &&
2572 (view == VIEW(REQ_VIEW_DIFF) ||
2573 view == VIEW(REQ_VIEW_LOG)))
2574 add_pager_refs(view, line);
2575
2576 return TRUE;
2577}
2578
2579static enum request
2580pager_request(struct view *view, enum request request, struct line *line)
2581{
2582 int split = 0;
2583
2584 if (request != REQ_ENTER)
2585 return request;
2586
2587 if (line->type == LINE_COMMIT &&
2588 (view == VIEW(REQ_VIEW_LOG) ||
2589 view == VIEW(REQ_VIEW_PAGER))) {
2590 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2591 split = 1;
2592 }
2593
2594 /* Always scroll the view even if it was split. That way
2595 * you can use Enter to scroll through the log view and
2596 * split open each commit diff. */
2597 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2598
2599 /* FIXME: A minor workaround. Scrolling the view will call report("")
2600 * but if we are scrolling a non-current view this won't properly
2601 * update the view title. */
2602 if (split)
2603 update_view_title(view);
2604
2605 return REQ_NONE;
2606}
2607
2608static bool
2609pager_grep(struct view *view, struct line *line)
2610{
2611 regmatch_t pmatch;
2612 char *text = line->data;
2613
2614 if (!*text)
2615 return FALSE;
2616
2617 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2618 return FALSE;
2619
2620 return TRUE;
2621}
2622
2623static void
2624pager_select(struct view *view, struct line *line)
2625{
2626 if (line->type == LINE_COMMIT) {
2627 char *text = line->data + STRING_SIZE("commit ");
2628
2629 if (view != VIEW(REQ_VIEW_PAGER))
2630 string_copy_rev(view->ref, text);
2631 string_copy_rev(ref_commit, text);
2632 }
2633}
2634
2635static struct view_ops pager_ops = {
2636 "line",
2637 NULL,
2638 pager_read,
2639 pager_draw,
2640 pager_request,
2641 pager_grep,
2642 pager_select,
2643};
2644
2645
2646/*
2647 * Help backend
2648 */
2649
2650static bool
2651help_open(struct view *view)
2652{
2653 char buf[BUFSIZ];
2654 int lines = ARRAY_SIZE(req_info) + 2;
2655 int i;
2656
2657 if (view->lines > 0)
2658 return TRUE;
2659
2660 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2661 if (!req_info[i].request)
2662 lines++;
2663
2664 view->line = calloc(lines, sizeof(*view->line));
2665 if (!view->line)
2666 return FALSE;
2667
2668 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2669
2670 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2671 char *key;
2672
2673 if (req_info[i].request == REQ_NONE)
2674 continue;
2675
2676 if (!req_info[i].request) {
2677 add_line_text(view, "", LINE_DEFAULT);
2678 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2679 continue;
2680 }
2681
2682 key = get_key(req_info[i].request);
2683 if (!*key)
2684 key = "(no key defined)";
2685
2686 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2687 continue;
2688
2689 add_line_text(view, buf, LINE_DEFAULT);
2690 }
2691
2692 return TRUE;
2693}
2694
2695static struct view_ops help_ops = {
2696 "line",
2697 help_open,
2698 NULL,
2699 pager_draw,
2700 pager_request,
2701 pager_grep,
2702 pager_select,
2703};
2704
2705
2706/*
2707 * Tree backend
2708 */
2709
2710struct tree_stack_entry {
2711 struct tree_stack_entry *prev; /* Entry below this in the stack */
2712 unsigned long lineno; /* Line number to restore */
2713 char *name; /* Position of name in opt_path */
2714};
2715
2716/* The top of the path stack. */
2717static struct tree_stack_entry *tree_stack = NULL;
2718unsigned long tree_lineno = 0;
2719
2720static void
2721pop_tree_stack_entry(void)
2722{
2723 struct tree_stack_entry *entry = tree_stack;
2724
2725 tree_lineno = entry->lineno;
2726 entry->name[0] = 0;
2727 tree_stack = entry->prev;
2728 free(entry);
2729}
2730
2731static void
2732push_tree_stack_entry(char *name, unsigned long lineno)
2733{
2734 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2735 size_t pathlen = strlen(opt_path);
2736
2737 if (!entry)
2738 return;
2739
2740 entry->prev = tree_stack;
2741 entry->name = opt_path + pathlen;
2742 tree_stack = entry;
2743
2744 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2745 pop_tree_stack_entry();
2746 return;
2747 }
2748
2749 /* Move the current line to the first tree entry. */
2750 tree_lineno = 1;
2751 entry->lineno = lineno;
2752}
2753
2754/* Parse output from git-ls-tree(1):
2755 *
2756 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2757 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2758 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2759 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2760 */
2761
2762#define SIZEOF_TREE_ATTR \
2763 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2764
2765#define TREE_UP_FORMAT "040000 tree %s\t.."
2766
2767static int
2768tree_compare_entry(enum line_type type1, char *name1,
2769 enum line_type type2, char *name2)
2770{
2771 if (type1 != type2) {
2772 if (type1 == LINE_TREE_DIR)
2773 return -1;
2774 return 1;
2775 }
2776
2777 return strcmp(name1, name2);
2778}
2779
2780static bool
2781tree_read(struct view *view, char *text)
2782{
2783 size_t textlen = text ? strlen(text) : 0;
2784 char buf[SIZEOF_STR];
2785 unsigned long pos;
2786 enum line_type type;
2787 bool first_read = view->lines == 0;
2788
2789 if (textlen <= SIZEOF_TREE_ATTR)
2790 return FALSE;
2791
2792 type = text[STRING_SIZE("100644 ")] == 't'
2793 ? LINE_TREE_DIR : LINE_TREE_FILE;
2794
2795 if (first_read) {
2796 /* Add path info line */
2797 if (!string_format(buf, "Directory path /%s", opt_path) ||
2798 !realloc_lines(view, view->line_size + 1) ||
2799 !add_line_text(view, buf, LINE_DEFAULT))
2800 return FALSE;
2801
2802 /* Insert "link" to parent directory. */
2803 if (*opt_path) {
2804 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2805 !realloc_lines(view, view->line_size + 1) ||
2806 !add_line_text(view, buf, LINE_TREE_DIR))
2807 return FALSE;
2808 }
2809 }
2810
2811 /* Strip the path part ... */
2812 if (*opt_path) {
2813 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2814 size_t striplen = strlen(opt_path);
2815 char *path = text + SIZEOF_TREE_ATTR;
2816
2817 if (pathlen > striplen)
2818 memmove(path, path + striplen,
2819 pathlen - striplen + 1);
2820 }
2821
2822 /* Skip "Directory ..." and ".." line. */
2823 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2824 struct line *line = &view->line[pos];
2825 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2826 char *path2 = text + SIZEOF_TREE_ATTR;
2827 int cmp = tree_compare_entry(line->type, path1, type, path2);
2828
2829 if (cmp <= 0)
2830 continue;
2831
2832 text = strdup(text);
2833 if (!text)
2834 return FALSE;
2835
2836 if (view->lines > pos)
2837 memmove(&view->line[pos + 1], &view->line[pos],
2838 (view->lines - pos) * sizeof(*line));
2839
2840 line = &view->line[pos];
2841 line->data = text;
2842 line->type = type;
2843 view->lines++;
2844 return TRUE;
2845 }
2846
2847 if (!add_line_text(view, text, type))
2848 return FALSE;
2849
2850 if (tree_lineno > view->lineno) {
2851 view->lineno = tree_lineno;
2852 tree_lineno = 0;
2853 }
2854
2855 return TRUE;
2856}
2857
2858static enum request
2859tree_request(struct view *view, enum request request, struct line *line)
2860{
2861 enum open_flags flags;
2862
2863 if (request != REQ_ENTER)
2864 return request;
2865
2866 /* Cleanup the stack if the tree view is at a different tree. */
2867 while (!*opt_path && tree_stack)
2868 pop_tree_stack_entry();
2869
2870 switch (line->type) {
2871 case LINE_TREE_DIR:
2872 /* Depending on whether it is a subdir or parent (updir?) link
2873 * mangle the path buffer. */
2874 if (line == &view->line[1] && *opt_path) {
2875 pop_tree_stack_entry();
2876
2877 } else {
2878 char *data = line->data;
2879 char *basename = data + SIZEOF_TREE_ATTR;
2880
2881 push_tree_stack_entry(basename, view->lineno);
2882 }
2883
2884 /* Trees and subtrees share the same ID, so they are not not
2885 * unique like blobs. */
2886 flags = OPEN_RELOAD;
2887 request = REQ_VIEW_TREE;
2888 break;
2889
2890 case LINE_TREE_FILE:
2891 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2892 request = REQ_VIEW_BLOB;
2893 break;
2894
2895 default:
2896 return TRUE;
2897 }
2898
2899 open_view(view, request, flags);
2900 if (request == REQ_VIEW_TREE) {
2901 view->lineno = tree_lineno;
2902 }
2903
2904 return REQ_NONE;
2905}
2906
2907static void
2908tree_select(struct view *view, struct line *line)
2909{
2910 char *text = line->data + STRING_SIZE("100644 blob ");
2911
2912 if (line->type == LINE_TREE_FILE) {
2913 string_copy_rev(ref_blob, text);
2914
2915 } else if (line->type != LINE_TREE_DIR) {
2916 return;
2917 }
2918
2919 string_copy_rev(view->ref, text);
2920}
2921
2922static struct view_ops tree_ops = {
2923 "file",
2924 NULL,
2925 tree_read,
2926 pager_draw,
2927 tree_request,
2928 pager_grep,
2929 tree_select,
2930};
2931
2932static bool
2933blob_read(struct view *view, char *line)
2934{
2935 return add_line_text(view, line, LINE_DEFAULT) != NULL;
2936}
2937
2938static struct view_ops blob_ops = {
2939 "line",
2940 NULL,
2941 blob_read,
2942 pager_draw,
2943 pager_request,
2944 pager_grep,
2945 pager_select,
2946};
2947
2948
2949/*
2950 * Status backend
2951 */
2952
2953struct status {
2954 char status;
2955 struct {
2956 mode_t mode;
2957 char rev[SIZEOF_REV];
2958 } old;
2959 struct {
2960 mode_t mode;
2961 char rev[SIZEOF_REV];
2962 } new;
2963 char name[SIZEOF_STR];
2964};
2965
2966static struct status stage_status;
2967static enum line_type stage_line_type;
2968
2969/* Get fields from the diff line:
2970 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2971 */
2972static inline bool
2973status_get_diff(struct status *file, char *buf, size_t bufsize)
2974{
2975 char *old_mode = buf + 1;
2976 char *new_mode = buf + 8;
2977 char *old_rev = buf + 15;
2978 char *new_rev = buf + 56;
2979 char *status = buf + 97;
2980
2981 if (bufsize != 99 ||
2982 old_mode[-1] != ':' ||
2983 new_mode[-1] != ' ' ||
2984 old_rev[-1] != ' ' ||
2985 new_rev[-1] != ' ' ||
2986 status[-1] != ' ')
2987 return FALSE;
2988
2989 file->status = *status;
2990
2991 string_copy_rev(file->old.rev, old_rev);
2992 string_copy_rev(file->new.rev, new_rev);
2993
2994 file->old.mode = strtoul(old_mode, NULL, 8);
2995 file->new.mode = strtoul(new_mode, NULL, 8);
2996
2997 file->name[0] = 0;
2998
2999 return TRUE;
3000}
3001
3002static bool
3003status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3004{
3005 struct status *file = NULL;
3006 struct status *unmerged = NULL;
3007 char buf[SIZEOF_STR * 4];
3008 size_t bufsize = 0;
3009 FILE *pipe;
3010
3011 pipe = popen(cmd, "r");
3012 if (!pipe)
3013 return FALSE;
3014
3015 add_line_data(view, NULL, type);
3016
3017 while (!feof(pipe) && !ferror(pipe)) {
3018 char *sep;
3019 size_t readsize;
3020
3021 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3022 if (!readsize)
3023 break;
3024 bufsize += readsize;
3025
3026 /* Process while we have NUL chars. */
3027 while ((sep = memchr(buf, 0, bufsize))) {
3028 size_t sepsize = sep - buf + 1;
3029
3030 if (!file) {
3031 if (!realloc_lines(view, view->line_size + 1))
3032 goto error_out;
3033
3034 file = calloc(1, sizeof(*file));
3035 if (!file)
3036 goto error_out;
3037
3038 add_line_data(view, file, type);
3039 }
3040
3041 /* Parse diff info part. */
3042 if (!diff) {
3043 file->status = '?';
3044
3045 } else if (!file->status) {
3046 if (!status_get_diff(file, buf, sepsize))
3047 goto error_out;
3048
3049 bufsize -= sepsize;
3050 memmove(buf, sep + 1, bufsize);
3051
3052 sep = memchr(buf, 0, bufsize);
3053 if (!sep)
3054 break;
3055 sepsize = sep - buf + 1;
3056
3057 /* Collapse all 'M'odified entries that
3058 * follow a associated 'U'nmerged entry.
3059 */
3060 if (file->status == 'U') {
3061 unmerged = file;
3062
3063 } else if (unmerged) {
3064 int collapse = !strcmp(buf, unmerged->name);
3065
3066 unmerged = NULL;
3067 if (collapse) {
3068 free(file);
3069 view->lines--;
3070 continue;
3071 }
3072 }
3073 }
3074
3075 /* git-ls-files just delivers a NUL separated
3076 * list of file names similar to the second half
3077 * of the git-diff-* output. */
3078 string_ncopy(file->name, buf, sepsize);
3079 bufsize -= sepsize;
3080 memmove(buf, sep + 1, bufsize);
3081 file = NULL;
3082 }
3083 }
3084
3085 if (ferror(pipe)) {
3086error_out:
3087 pclose(pipe);
3088 return FALSE;
3089 }
3090
3091 if (!view->line[view->lines - 1].data)
3092 add_line_data(view, NULL, LINE_STAT_NONE);
3093
3094 pclose(pipe);
3095 return TRUE;
3096}
3097
3098/* Don't show unmerged entries in the staged section. */
3099#define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3100#define STATUS_DIFF_FILES_CMD "git diff-files -z"
3101#define STATUS_LIST_OTHER_CMD \
3102 "git ls-files -z --others --exclude-per-directory=.gitignore"
3103
3104#define STATUS_DIFF_SHOW_CMD \
3105 "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3106
3107/* First parse staged info using git-diff-index(1), then parse unstaged
3108 * info using git-diff-files(1), and finally untracked files using
3109 * git-ls-files(1). */
3110static bool
3111status_open(struct view *view)
3112{
3113 struct stat statbuf;
3114 char exclude[SIZEOF_STR];
3115 char cmd[SIZEOF_STR];
3116 unsigned long prev_lineno = view->lineno;
3117 size_t i;
3118
3119 for (i = 0; i < view->lines; i++)
3120 free(view->line[i].data);
3121 free(view->line);
3122 view->lines = view->line_size = view->lineno = 0;
3123 view->line = NULL;
3124
3125 if (!realloc_lines(view, view->line_size + 6))
3126 return FALSE;
3127
3128 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3129 return FALSE;
3130
3131 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3132
3133 if (stat(exclude, &statbuf) >= 0) {
3134 size_t cmdsize = strlen(cmd);
3135
3136 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3137 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3138 return FALSE;
3139 }
3140
3141 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3142 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3143 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3144 return FALSE;
3145
3146 /* If all went well restore the previous line number to stay in
3147 * the context. */
3148 if (prev_lineno < view->lines)
3149 view->lineno = prev_lineno;
3150 else
3151 view->lineno = view->lines - 1;
3152
3153 return TRUE;
3154}
3155
3156static bool
3157status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3158{
3159 struct status *status = line->data;
3160
3161 wmove(view->win, lineno, 0);
3162
3163 if (selected) {
3164 wattrset(view->win, get_line_attr(LINE_CURSOR));
3165 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3166
3167 } else if (!status && line->type != LINE_STAT_NONE) {
3168 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3169 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3170
3171 } else {
3172 wattrset(view->win, get_line_attr(line->type));
3173 }
3174
3175 if (!status) {
3176 char *text;
3177
3178 switch (line->type) {
3179 case LINE_STAT_STAGED:
3180 text = "Changes to be committed:";
3181 break;
3182
3183 case LINE_STAT_UNSTAGED:
3184 text = "Changed but not updated:";
3185 break;
3186
3187 case LINE_STAT_UNTRACKED:
3188 text = "Untracked files:";
3189 break;
3190
3191 case LINE_STAT_NONE:
3192 text = " (no files)";
3193 break;
3194
3195 default:
3196 return FALSE;
3197 }
3198
3199 waddstr(view->win, text);
3200 return TRUE;
3201 }
3202
3203 waddch(view->win, status->status);
3204 if (!selected)
3205 wattrset(view->win, A_NORMAL);
3206 wmove(view->win, lineno, 4);
3207 waddstr(view->win, status->name);
3208
3209 return TRUE;
3210}
3211
3212static enum request
3213status_enter(struct view *view, struct line *line)
3214{
3215 struct status *status = line->data;
3216 char path[SIZEOF_STR] = "";
3217 char *info;
3218 size_t cmdsize = 0;
3219
3220 if (line->type == LINE_STAT_NONE ||
3221 (!status && line[1].type == LINE_STAT_NONE)) {
3222 report("No file to diff");
3223 return REQ_NONE;
3224 }
3225
3226 if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3227 return REQ_QUIT;
3228
3229 if (opt_cdup[0] &&
3230 line->type != LINE_STAT_UNTRACKED &&
3231 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3232 return REQ_QUIT;
3233
3234 switch (line->type) {
3235 case LINE_STAT_STAGED:
3236 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3237 "--cached", path))
3238 return REQ_QUIT;
3239 if (status)
3240 info = "Staged changes to %s";
3241 else
3242 info = "Staged changes";
3243 break;
3244
3245 case LINE_STAT_UNSTAGED:
3246 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3247 "", path))
3248 return REQ_QUIT;
3249 if (status)
3250 info = "Unstaged changes to %s";
3251 else
3252 info = "Unstaged changes";
3253 break;
3254
3255 case LINE_STAT_UNTRACKED:
3256 if (opt_pipe)
3257 return REQ_QUIT;
3258
3259
3260 if (!status) {
3261 report("No file to show");
3262 return REQ_NONE;
3263 }
3264
3265 opt_pipe = fopen(status->name, "r");
3266 info = "Untracked file %s";
3267 break;
3268
3269 default:
3270 die("w00t");
3271 }
3272
3273 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3274 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3275 if (status) {
3276 stage_status = *status;
3277 } else {
3278 memset(&stage_status, 0, sizeof(stage_status));
3279 }
3280
3281 stage_line_type = line->type;
3282 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3283 }
3284
3285 return REQ_NONE;
3286}
3287
3288
3289static bool
3290status_update_file(struct view *view, struct status *status, enum line_type type)
3291{
3292 char cmd[SIZEOF_STR];
3293 char buf[SIZEOF_STR];
3294 size_t cmdsize = 0;
3295 size_t bufsize = 0;
3296 size_t written = 0;
3297 FILE *pipe;
3298
3299 if (opt_cdup[0] &&
3300 type != LINE_STAT_UNTRACKED &&
3301 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3302 return FALSE;
3303
3304 switch (type) {
3305 case LINE_STAT_STAGED:
3306 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3307 status->old.mode,
3308 status->old.rev,
3309 status->name, 0))
3310 return FALSE;
3311
3312 string_add(cmd, cmdsize, "git update-index -z --index-info");
3313 break;
3314
3315 case LINE_STAT_UNSTAGED:
3316 case LINE_STAT_UNTRACKED:
3317 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3318 return FALSE;
3319
3320 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3321 break;
3322
3323 default:
3324 die("w00t");
3325 }
3326
3327 pipe = popen(cmd, "w");
3328 if (!pipe)
3329 return FALSE;
3330
3331 while (!ferror(pipe) && written < bufsize) {
3332 written += fwrite(buf + written, 1, bufsize - written, pipe);
3333 }
3334
3335 pclose(pipe);
3336
3337 if (written != bufsize)
3338 return FALSE;
3339
3340 return TRUE;
3341}
3342
3343static void
3344status_update(struct view *view)
3345{
3346 struct line *line = &view->line[view->lineno];
3347
3348 assert(view->lines);
3349
3350 if (!line->data) {
3351 while (++line < view->line + view->lines && line->data) {
3352 if (!status_update_file(view, line->data, line->type))
3353 report("Failed to update file status");
3354 }
3355
3356 if (!line[-1].data) {
3357 report("Nothing to update");
3358 return;
3359 }
3360
3361 } else if (!status_update_file(view, line->data, line->type)) {
3362 report("Failed to update file status");
3363 }
3364}
3365
3366static enum request
3367status_request(struct view *view, enum request request, struct line *line)
3368{
3369 struct status *status = line->data;
3370
3371 switch (request) {
3372 case REQ_STATUS_UPDATE:
3373 status_update(view);
3374 break;
3375
3376 case REQ_STATUS_MERGE:
3377 open_mergetool(status->name);
3378 break;
3379
3380 case REQ_EDIT:
3381 if (!status)
3382 return request;
3383
3384 open_editor(status->status != '?', status->name);
3385 break;
3386
3387 case REQ_ENTER:
3388 /* After returning the status view has been split to
3389 * show the stage view. No further reloading is
3390 * necessary. */
3391 status_enter(view, line);
3392 return REQ_NONE;
3393
3394 case REQ_REFRESH:
3395 /* Simply reload the view. */
3396 break;
3397
3398 default:
3399 return request;
3400 }
3401
3402 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3403
3404 return REQ_NONE;
3405}
3406
3407static void
3408status_select(struct view *view, struct line *line)
3409{
3410 struct status *status = line->data;
3411 char file[SIZEOF_STR] = "all files";
3412 char *text;
3413 char *key;
3414
3415 if (status && !string_format(file, "'%s'", status->name))
3416 return;
3417
3418 if (!status && line[1].type == LINE_STAT_NONE)
3419 line++;
3420
3421 switch (line->type) {
3422 case LINE_STAT_STAGED:
3423 text = "Press %s to unstage %s for commit";
3424 break;
3425
3426 case LINE_STAT_UNSTAGED:
3427 text = "Press %s to stage %s for commit";
3428 break;
3429
3430 case LINE_STAT_UNTRACKED:
3431 text = "Press %s to stage %s for addition";
3432 break;
3433
3434 case LINE_STAT_NONE:
3435 text = "Nothing to update";
3436 break;
3437
3438 default:
3439 die("w00t");
3440 }
3441
3442 if (status && status->status == 'U') {
3443 text = "Press %s to resolve conflict in %s";
3444 key = get_key(REQ_STATUS_MERGE);
3445
3446 } else {
3447 key = get_key(REQ_STATUS_UPDATE);
3448 }
3449
3450 string_format(view->ref, text, key, file);
3451}
3452
3453static bool
3454status_grep(struct view *view, struct line *line)
3455{
3456 struct status *status = line->data;
3457 enum { S_STATUS, S_NAME, S_END } state;
3458 char buf[2] = "?";
3459 regmatch_t pmatch;
3460
3461 if (!status)
3462 return FALSE;
3463
3464 for (state = S_STATUS; state < S_END; state++) {
3465 char *text;
3466
3467 switch (state) {
3468 case S_NAME: text = status->name; break;
3469 case S_STATUS:
3470 buf[0] = status->status;
3471 text = buf;
3472 break;
3473
3474 default:
3475 return FALSE;
3476 }
3477
3478 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3479 return TRUE;
3480 }
3481
3482 return FALSE;
3483}
3484
3485static struct view_ops status_ops = {
3486 "file",
3487 status_open,
3488 NULL,
3489 status_draw,
3490 status_request,
3491 status_grep,
3492 status_select,
3493};
3494
3495
3496static bool
3497stage_diff_line(FILE *pipe, struct line *line)
3498{
3499 char *buf = line->data;
3500 size_t bufsize = strlen(buf);
3501 size_t written = 0;
3502
3503 while (!ferror(pipe) && written < bufsize) {
3504 written += fwrite(buf + written, 1, bufsize - written, pipe);
3505 }
3506
3507 fputc('\n', pipe);
3508
3509 return written == bufsize;
3510}
3511
3512static struct line *
3513stage_diff_hdr(struct view *view, struct line *line)
3514{
3515 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3516 struct line *diff_hdr;
3517
3518 if (line->type == LINE_DIFF_CHUNK)
3519 diff_hdr = line - 1;
3520 else
3521 diff_hdr = view->line + 1;
3522
3523 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3524 if (diff_hdr->type == LINE_DIFF_HEADER)
3525 return diff_hdr;
3526
3527 diff_hdr += diff_hdr_dir;
3528 }
3529
3530 return NULL;
3531}
3532
3533static bool
3534stage_update_chunk(struct view *view, struct line *line)
3535{
3536 char cmd[SIZEOF_STR];
3537 size_t cmdsize = 0;
3538 struct line *diff_hdr, *diff_chunk, *diff_end;
3539 FILE *pipe;
3540
3541 diff_hdr = stage_diff_hdr(view, line);
3542 if (!diff_hdr)
3543 return FALSE;
3544
3545 if (opt_cdup[0] &&
3546 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3547 return FALSE;
3548
3549 if (!string_format_from(cmd, &cmdsize,
3550 "git apply --cached %s - && "
3551 "git update-index -q --unmerged --refresh 2>/dev/null",
3552 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3553 return FALSE;
3554
3555 pipe = popen(cmd, "w");
3556 if (!pipe)
3557 return FALSE;
3558
3559 diff_end = view->line + view->lines;
3560 if (line->type != LINE_DIFF_CHUNK) {
3561 diff_chunk = diff_hdr;
3562
3563 } else {
3564 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3565 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3566 diff_chunk->type == LINE_DIFF_HEADER)
3567 diff_end = diff_chunk;
3568
3569 diff_chunk = line;
3570
3571 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3572 switch (diff_hdr->type) {
3573 case LINE_DIFF_HEADER:
3574 case LINE_DIFF_INDEX:
3575 case LINE_DIFF_ADD:
3576 case LINE_DIFF_DEL:
3577 break;
3578
3579 default:
3580 diff_hdr++;
3581 continue;
3582 }
3583
3584 if (!stage_diff_line(pipe, diff_hdr++)) {
3585 pclose(pipe);
3586 return FALSE;
3587 }
3588 }
3589 }
3590
3591 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3592 diff_chunk++;
3593
3594 pclose(pipe);
3595
3596 if (diff_chunk != diff_end)
3597 return FALSE;
3598
3599 return TRUE;
3600}
3601
3602static void
3603stage_update(struct view *view, struct line *line)
3604{
3605 if (stage_line_type != LINE_STAT_UNTRACKED &&
3606 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3607 if (!stage_update_chunk(view, line)) {
3608 report("Failed to apply chunk");
3609 return;
3610 }
3611
3612 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3613 report("Failed to update file");
3614 return;
3615 }
3616
3617 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3618
3619 view = VIEW(REQ_VIEW_STATUS);
3620 if (view_is_displayed(view))
3621 status_enter(view, &view->line[view->lineno]);
3622}
3623
3624static enum request
3625stage_request(struct view *view, enum request request, struct line *line)
3626{
3627 switch (request) {
3628 case REQ_STATUS_UPDATE:
3629 stage_update(view, line);
3630 break;
3631
3632 case REQ_EDIT:
3633 if (!stage_status.name[0])
3634 return request;
3635
3636 open_editor(stage_status.status != '?', stage_status.name);
3637 break;
3638
3639 case REQ_ENTER:
3640 pager_request(view, request, line);
3641 break;
3642
3643 default:
3644 return request;
3645 }
3646
3647 return REQ_NONE;
3648}
3649
3650static struct view_ops stage_ops = {
3651 "line",
3652 NULL,
3653 pager_read,
3654 pager_draw,
3655 stage_request,
3656 pager_grep,
3657 pager_select,
3658};
3659
3660
3661/*
3662 * Revision graph
3663 */
3664
3665struct commit {
3666 char id[SIZEOF_REV]; /* SHA1 ID. */
3667 char title[128]; /* First line of the commit message. */
3668 char author[75]; /* Author of the commit. */
3669 struct tm time; /* Date from the author ident. */
3670 struct ref **refs; /* Repository references. */
3671 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3672 size_t graph_size; /* The width of the graph array. */
3673};
3674
3675/* Size of rev graph with no "padding" columns */
3676#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3677
3678struct rev_graph {
3679 struct rev_graph *prev, *next, *parents;
3680 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3681 size_t size;
3682 struct commit *commit;
3683 size_t pos;
3684};
3685
3686/* Parents of the commit being visualized. */
3687static struct rev_graph graph_parents[4];
3688
3689/* The current stack of revisions on the graph. */
3690static struct rev_graph graph_stacks[4] = {
3691 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3692 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3693 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3694 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3695};
3696
3697static inline bool
3698graph_parent_is_merge(struct rev_graph *graph)
3699{
3700 return graph->parents->size > 1;
3701}
3702
3703static inline void
3704append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3705{
3706 struct commit *commit = graph->commit;
3707
3708 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3709 commit->graph[commit->graph_size++] = symbol;
3710}
3711
3712static void
3713done_rev_graph(struct rev_graph *graph)
3714{
3715 if (graph_parent_is_merge(graph) &&
3716 graph->pos < graph->size - 1 &&
3717 graph->next->size == graph->size + graph->parents->size - 1) {
3718 size_t i = graph->pos + graph->parents->size - 1;
3719
3720 graph->commit->graph_size = i * 2;
3721 while (i < graph->next->size - 1) {
3722 append_to_rev_graph(graph, ' ');
3723 append_to_rev_graph(graph, '\\');
3724 i++;
3725 }
3726 }
3727
3728 graph->size = graph->pos = 0;
3729 graph->commit = NULL;
3730 memset(graph->parents, 0, sizeof(*graph->parents));
3731}
3732
3733static void
3734push_rev_graph(struct rev_graph *graph, char *parent)
3735{
3736 int i;
3737
3738 /* "Collapse" duplicate parents lines.
3739 *
3740 * FIXME: This needs to also update update the drawn graph but
3741 * for now it just serves as a method for pruning graph lines. */
3742 for (i = 0; i < graph->size; i++)
3743 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3744 return;
3745
3746 if (graph->size < SIZEOF_REVITEMS) {
3747 string_copy_rev(graph->rev[graph->size++], parent);
3748 }
3749}
3750
3751static chtype
3752get_rev_graph_symbol(struct rev_graph *graph)
3753{
3754 chtype symbol;
3755
3756 if (graph->parents->size == 0)
3757 symbol = REVGRAPH_INIT;
3758 else if (graph_parent_is_merge(graph))
3759 symbol = REVGRAPH_MERGE;
3760 else if (graph->pos >= graph->size)
3761 symbol = REVGRAPH_BRANCH;
3762 else
3763 symbol = REVGRAPH_COMMIT;
3764
3765 return symbol;
3766}
3767
3768static void
3769draw_rev_graph(struct rev_graph *graph)
3770{
3771 struct rev_filler {
3772 chtype separator, line;
3773 };
3774 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3775 static struct rev_filler fillers[] = {
3776 { ' ', REVGRAPH_LINE },
3777 { '`', '.' },
3778 { '\'', ' ' },
3779 { '/', ' ' },
3780 };
3781 chtype symbol = get_rev_graph_symbol(graph);
3782 struct rev_filler *filler;
3783 size_t i;
3784
3785 filler = &fillers[DEFAULT];
3786
3787 for (i = 0; i < graph->pos; i++) {
3788 append_to_rev_graph(graph, filler->line);
3789 if (graph_parent_is_merge(graph->prev) &&
3790 graph->prev->pos == i)
3791 filler = &fillers[RSHARP];
3792
3793 append_to_rev_graph(graph, filler->separator);
3794 }
3795
3796 /* Place the symbol for this revision. */
3797 append_to_rev_graph(graph, symbol);
3798
3799 if (graph->prev->size > graph->size)
3800 filler = &fillers[RDIAG];
3801 else
3802 filler = &fillers[DEFAULT];
3803
3804 i++;
3805
3806 for (; i < graph->size; i++) {
3807 append_to_rev_graph(graph, filler->separator);
3808 append_to_rev_graph(graph, filler->line);
3809 if (graph_parent_is_merge(graph->prev) &&
3810 i < graph->prev->pos + graph->parents->size)
3811 filler = &fillers[RSHARP];
3812 if (graph->prev->size > graph->size)
3813 filler = &fillers[LDIAG];
3814 }
3815
3816 if (graph->prev->size > graph->size) {
3817 append_to_rev_graph(graph, filler->separator);
3818 if (filler->line != ' ')
3819 append_to_rev_graph(graph, filler->line);
3820 }
3821}
3822
3823/* Prepare the next rev graph */
3824static void
3825prepare_rev_graph(struct rev_graph *graph)
3826{
3827 size_t i;
3828
3829 /* First, traverse all lines of revisions up to the active one. */
3830 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3831 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3832 break;
3833
3834 push_rev_graph(graph->next, graph->rev[graph->pos]);
3835 }
3836
3837 /* Interleave the new revision parent(s). */
3838 for (i = 0; i < graph->parents->size; i++)
3839 push_rev_graph(graph->next, graph->parents->rev[i]);
3840
3841 /* Lastly, put any remaining revisions. */
3842 for (i = graph->pos + 1; i < graph->size; i++)
3843 push_rev_graph(graph->next, graph->rev[i]);
3844}
3845
3846static void
3847update_rev_graph(struct rev_graph *graph)
3848{
3849 /* If this is the finalizing update ... */
3850 if (graph->commit)
3851 prepare_rev_graph(graph);
3852
3853 /* Graph visualization needs a one rev look-ahead,
3854 * so the first update doesn't visualize anything. */
3855 if (!graph->prev->commit)
3856 return;
3857
3858 draw_rev_graph(graph->prev);
3859 done_rev_graph(graph->prev->prev);
3860}
3861
3862
3863/*
3864 * Main view backend
3865 */
3866
3867static bool
3868main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3869{
3870 char buf[DATE_COLS + 1];
3871 struct commit *commit = line->data;
3872 enum line_type type;
3873 int col = 0;
3874 size_t timelen;
3875 size_t authorlen;
3876 int trimmed = 1;
3877
3878 if (!*commit->author)
3879 return FALSE;
3880
3881 wmove(view->win, lineno, col);
3882
3883 if (selected) {
3884 type = LINE_CURSOR;
3885 wattrset(view->win, get_line_attr(type));
3886 wchgat(view->win, -1, 0, type, NULL);
3887
3888 } else {
3889 type = LINE_MAIN_COMMIT;
3890 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3891 }
3892
3893 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3894 waddnstr(view->win, buf, timelen);
3895 waddstr(view->win, " ");
3896
3897 col += DATE_COLS;
3898 wmove(view->win, lineno, col);
3899 if (type != LINE_CURSOR)
3900 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3901
3902 if (opt_utf8) {
3903 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3904 } else {
3905 authorlen = strlen(commit->author);
3906 if (authorlen > AUTHOR_COLS - 2) {
3907 authorlen = AUTHOR_COLS - 2;
3908 trimmed = 1;
3909 }
3910 }
3911
3912 if (trimmed) {
3913 waddnstr(view->win, commit->author, authorlen);
3914 if (type != LINE_CURSOR)
3915 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3916 waddch(view->win, '~');
3917 } else {
3918 waddstr(view->win, commit->author);
3919 }
3920
3921 col += AUTHOR_COLS;
3922 if (type != LINE_CURSOR)
3923 wattrset(view->win, A_NORMAL);
3924
3925 if (opt_rev_graph && commit->graph_size) {
3926 size_t i;
3927
3928 wmove(view->win, lineno, col);
3929 /* Using waddch() instead of waddnstr() ensures that
3930 * they'll be rendered correctly for the cursor line. */
3931 for (i = 0; i < commit->graph_size; i++)
3932 waddch(view->win, commit->graph[i]);
3933
3934 waddch(view->win, ' ');
3935 col += commit->graph_size + 1;
3936 }
3937
3938 wmove(view->win, lineno, col);
3939
3940 if (commit->refs) {
3941 size_t i = 0;
3942
3943 do {
3944 if (type == LINE_CURSOR)
3945 ;
3946 else if (commit->refs[i]->tag)
3947 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3948 else if (commit->refs[i]->remote)
3949 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3950 else
3951 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3952 waddstr(view->win, "[");
3953 waddstr(view->win, commit->refs[i]->name);
3954 waddstr(view->win, "]");
3955 if (type != LINE_CURSOR)
3956 wattrset(view->win, A_NORMAL);
3957 waddstr(view->win, " ");
3958 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3959 } while (commit->refs[i++]->next);
3960 }
3961
3962 if (type != LINE_CURSOR)
3963 wattrset(view->win, get_line_attr(type));
3964
3965 {
3966 int titlelen = strlen(commit->title);
3967
3968 if (col + titlelen > view->width)
3969 titlelen = view->width - col;
3970
3971 waddnstr(view->win, commit->title, titlelen);
3972 }
3973
3974 return TRUE;
3975}
3976
3977/* Reads git log --pretty=raw output and parses it into the commit struct. */
3978static bool
3979main_read(struct view *view, char *line)
3980{
3981 static struct rev_graph *graph = graph_stacks;
3982 enum line_type type;
3983 struct commit *commit;
3984
3985 if (!line) {
3986 update_rev_graph(graph);
3987 return TRUE;
3988 }
3989
3990 type = get_line_type(line);
3991 if (type == LINE_COMMIT) {
3992 commit = calloc(1, sizeof(struct commit));
3993 if (!commit)
3994 return FALSE;
3995
3996 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3997 commit->refs = get_refs(commit->id);
3998 graph->commit = commit;
3999 add_line_data(view, commit, LINE_MAIN_COMMIT);
4000 return TRUE;
4001 }
4002
4003 if (!view->lines)
4004 return TRUE;
4005 commit = view->line[view->lines - 1].data;
4006
4007 switch (type) {
4008 case LINE_PARENT:
4009 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4010 break;
4011
4012 case LINE_AUTHOR:
4013 {
4014 /* Parse author lines where the name may be empty:
4015 * author <email@address.tld> 1138474660 +0100
4016 */
4017 char *ident = line + STRING_SIZE("author ");
4018 char *nameend = strchr(ident, '<');
4019 char *emailend = strchr(ident, '>');
4020
4021 if (!nameend || !emailend)
4022 break;
4023
4024 update_rev_graph(graph);
4025 graph = graph->next;
4026
4027 *nameend = *emailend = 0;
4028 ident = chomp_string(ident);
4029 if (!*ident) {
4030 ident = chomp_string(nameend + 1);
4031 if (!*ident)
4032 ident = "Unknown";
4033 }
4034
4035 string_ncopy(commit->author, ident, strlen(ident));
4036
4037 /* Parse epoch and timezone */
4038 if (emailend[1] == ' ') {
4039 char *secs = emailend + 2;
4040 char *zone = strchr(secs, ' ');
4041 time_t time = (time_t) atol(secs);
4042
4043 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4044 long tz;
4045
4046 zone++;
4047 tz = ('0' - zone[1]) * 60 * 60 * 10;
4048 tz += ('0' - zone[2]) * 60 * 60;
4049 tz += ('0' - zone[3]) * 60;
4050 tz += ('0' - zone[4]) * 60;
4051
4052 if (zone[0] == '-')
4053 tz = -tz;
4054
4055 time -= tz;
4056 }
4057
4058 gmtime_r(&time, &commit->time);
4059 }
4060 break;
4061 }
4062 default:
4063 /* Fill in the commit title if it has not already been set. */
4064 if (commit->title[0])
4065 break;
4066
4067 /* Require titles to start with a non-space character at the
4068 * offset used by git log. */
4069 if (strncmp(line, " ", 4))
4070 break;
4071 line += 4;
4072 /* Well, if the title starts with a whitespace character,
4073 * try to be forgiving. Otherwise we end up with no title. */
4074 while (isspace(*line))
4075 line++;
4076 if (*line == '\0')
4077 break;
4078 /* FIXME: More graceful handling of titles; append "..." to
4079 * shortened titles, etc. */
4080
4081 string_ncopy(commit->title, line, strlen(line));
4082 }
4083
4084 return TRUE;
4085}
4086
4087static void
4088cherry_pick_commit(struct commit *commit)
4089{
4090 char cmd[SIZEOF_STR];
4091 char *cherry_pick = getenv("TIG_CHERRY_PICK");
4092
4093 if (!cherry_pick)
4094 cherry_pick = "git cherry-pick";
4095
4096 if (string_format(cmd, "%s %s", cherry_pick, commit->id)) {
4097 open_external_viewer(cmd);
4098 }
4099}
4100
4101static enum request
4102main_request(struct view *view, enum request request, struct line *line)
4103{
4104 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4105
4106 if (request == REQ_ENTER)
4107 open_view(view, REQ_VIEW_DIFF, flags);
4108 else if (request == REQ_CHERRY_PICK)
4109 cherry_pick_commit(line->data);
4110 else
4111 return request;
4112
4113 return REQ_NONE;
4114}
4115
4116static bool
4117main_grep(struct view *view, struct line *line)
4118{
4119 struct commit *commit = line->data;
4120 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4121 char buf[DATE_COLS + 1];
4122 regmatch_t pmatch;
4123
4124 for (state = S_TITLE; state < S_END; state++) {
4125 char *text;
4126
4127 switch (state) {
4128 case S_TITLE: text = commit->title; break;
4129 case S_AUTHOR: text = commit->author; break;
4130 case S_DATE:
4131 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4132 continue;
4133 text = buf;
4134 break;
4135
4136 default:
4137 return FALSE;
4138 }
4139
4140 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4141 return TRUE;
4142 }
4143
4144 return FALSE;
4145}
4146
4147static void
4148main_select(struct view *view, struct line *line)
4149{
4150 struct commit *commit = line->data;
4151
4152 string_copy_rev(view->ref, commit->id);
4153 string_copy_rev(ref_commit, view->ref);
4154}
4155
4156static struct view_ops main_ops = {
4157 "commit",
4158 NULL,
4159 main_read,
4160 main_draw,
4161 main_request,
4162 main_grep,
4163 main_select,
4164};
4165
4166
4167/*
4168 * Unicode / UTF-8 handling
4169 *
4170 * NOTE: Much of the following code for dealing with unicode is derived from
4171 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4172 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4173 */
4174
4175/* I've (over)annotated a lot of code snippets because I am not entirely
4176 * confident that the approach taken by this small UTF-8 interface is correct.
4177 * --jonas */
4178
4179static inline int
4180unicode_width(unsigned long c)
4181{
4182 if (c >= 0x1100 &&
4183 (c <= 0x115f /* Hangul Jamo */
4184 || c == 0x2329
4185 || c == 0x232a
4186 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
4187 /* CJK ... Yi */
4188 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4189 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4190 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4191 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4192 || (c >= 0xffe0 && c <= 0xffe6)
4193 || (c >= 0x20000 && c <= 0x2fffd)
4194 || (c >= 0x30000 && c <= 0x3fffd)))
4195 return 2;
4196
4197 return 1;
4198}
4199
4200/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4201 * Illegal bytes are set one. */
4202static const unsigned char utf8_bytes[256] = {
4203 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,
4204 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,
4205 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,
4206 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,
4207 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,
4208 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,
4209 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,
4210 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,
4211};
4212
4213/* Decode UTF-8 multi-byte representation into a unicode character. */
4214static inline unsigned long
4215utf8_to_unicode(const char *string, size_t length)
4216{
4217 unsigned long unicode;
4218
4219 switch (length) {
4220 case 1:
4221 unicode = string[0];
4222 break;
4223 case 2:
4224 unicode = (string[0] & 0x1f) << 6;
4225 unicode += (string[1] & 0x3f);
4226 break;
4227 case 3:
4228 unicode = (string[0] & 0x0f) << 12;
4229 unicode += ((string[1] & 0x3f) << 6);
4230 unicode += (string[2] & 0x3f);
4231 break;
4232 case 4:
4233 unicode = (string[0] & 0x0f) << 18;
4234 unicode += ((string[1] & 0x3f) << 12);
4235 unicode += ((string[2] & 0x3f) << 6);
4236 unicode += (string[3] & 0x3f);
4237 break;
4238 case 5:
4239 unicode = (string[0] & 0x0f) << 24;
4240 unicode += ((string[1] & 0x3f) << 18);
4241 unicode += ((string[2] & 0x3f) << 12);
4242 unicode += ((string[3] & 0x3f) << 6);
4243 unicode += (string[4] & 0x3f);
4244 break;
4245 case 6:
4246 unicode = (string[0] & 0x01) << 30;
4247 unicode += ((string[1] & 0x3f) << 24);
4248 unicode += ((string[2] & 0x3f) << 18);
4249 unicode += ((string[3] & 0x3f) << 12);
4250 unicode += ((string[4] & 0x3f) << 6);
4251 unicode += (string[5] & 0x3f);
4252 break;
4253 default:
4254 die("Invalid unicode length");
4255 }
4256
4257 /* Invalid characters could return the special 0xfffd value but NUL
4258 * should be just as good. */
4259 return unicode > 0xffff ? 0 : unicode;
4260}
4261
4262/* Calculates how much of string can be shown within the given maximum width
4263 * and sets trimmed parameter to non-zero value if all of string could not be
4264 * shown.
4265 *
4266 * Additionally, adds to coloffset how many many columns to move to align with
4267 * the expected position. Takes into account how multi-byte and double-width
4268 * characters will effect the cursor position.
4269 *
4270 * Returns the number of bytes to output from string to satisfy max_width. */
4271static size_t
4272utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4273{
4274 const char *start = string;
4275 const char *end = strchr(string, '\0');
4276 size_t mbwidth = 0;
4277 size_t width = 0;
4278
4279 *trimmed = 0;
4280
4281 while (string < end) {
4282 int c = *(unsigned char *) string;
4283 unsigned char bytes = utf8_bytes[c];
4284 size_t ucwidth;
4285 unsigned long unicode;
4286
4287 if (string + bytes > end)
4288 break;
4289
4290 /* Change representation to figure out whether
4291 * it is a single- or double-width character. */
4292
4293 unicode = utf8_to_unicode(string, bytes);
4294 /* FIXME: Graceful handling of invalid unicode character. */
4295 if (!unicode)
4296 break;
4297
4298 ucwidth = unicode_width(unicode);
4299 width += ucwidth;
4300 if (width > max_width) {
4301 *trimmed = 1;
4302 break;
4303 }
4304
4305 /* The column offset collects the differences between the
4306 * number of bytes encoding a character and the number of
4307 * columns will be used for rendering said character.
4308 *
4309 * So if some character A is encoded in 2 bytes, but will be
4310 * represented on the screen using only 1 byte this will and up
4311 * adding 1 to the multi-byte column offset.
4312 *
4313 * Assumes that no double-width character can be encoding in
4314 * less than two bytes. */
4315 if (bytes > ucwidth)
4316 mbwidth += bytes - ucwidth;
4317
4318 string += bytes;
4319 }
4320
4321 *coloffset += mbwidth;
4322
4323 return string - start;
4324}
4325
4326
4327/*
4328 * Status management
4329 */
4330
4331/* Whether or not the curses interface has been initialized. */
4332static bool cursed = FALSE;
4333
4334/* The status window is used for polling keystrokes. */
4335static WINDOW *status_win;
4336
4337static bool status_empty = TRUE;
4338
4339/* Update status and title window. */
4340static void
4341report(const char *msg, ...)
4342{
4343 struct view *view = display[current_view];
4344
4345 if (input_mode)
4346 return;
4347
4348 if (!view) {
4349 char buf[SIZEOF_STR];
4350 va_list args;
4351
4352 va_start(args, msg);
4353 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4354 buf[sizeof(buf) - 1] = 0;
4355 buf[sizeof(buf) - 2] = '.';
4356 buf[sizeof(buf) - 3] = '.';
4357 buf[sizeof(buf) - 4] = '.';
4358 }
4359 va_end(args);
4360 die("%s", buf);
4361 }
4362
4363 if (!status_empty || *msg) {
4364 va_list args;
4365
4366 va_start(args, msg);
4367
4368 wmove(status_win, 0, 0);
4369 if (*msg) {
4370 vwprintw(status_win, msg, args);
4371 status_empty = FALSE;
4372 } else {
4373 status_empty = TRUE;
4374 }
4375 wclrtoeol(status_win);
4376 wrefresh(status_win);
4377
4378 va_end(args);
4379 }
4380
4381 update_view_title(view);
4382 update_display_cursor(view);
4383}
4384
4385/* Controls when nodelay should be in effect when polling user input. */
4386static void
4387set_nonblocking_input(bool loading)
4388{
4389 static unsigned int loading_views;
4390
4391 if ((loading == FALSE && loading_views-- == 1) ||
4392 (loading == TRUE && loading_views++ == 0))
4393 nodelay(status_win, loading);
4394}
4395
4396static void
4397init_display(void)
4398{
4399 int x, y;
4400
4401 /* Initialize the curses library */
4402 if (isatty(STDIN_FILENO)) {
4403 cursed = !!initscr();
4404 } else {
4405 /* Leave stdin and stdout alone when acting as a pager. */
4406 FILE *io = fopen("/dev/tty", "r+");
4407
4408 if (!io)
4409 die("Failed to open /dev/tty");
4410 cursed = !!newterm(NULL, io, io);
4411 }
4412
4413 if (!cursed)
4414 die("Failed to initialize curses");
4415
4416 nonl(); /* Tell curses not to do NL->CR/NL on output */
4417 cbreak(); /* Take input chars one at a time, no wait for \n */
4418 noecho(); /* Don't echo input */
4419 leaveok(stdscr, TRUE);
4420
4421 if (has_colors())
4422 init_colors();
4423
4424 getmaxyx(stdscr, y, x);
4425 status_win = newwin(1, 0, y - 1, 0);
4426 if (!status_win)
4427 die("Failed to create status window");
4428
4429 /* Enable keyboard mapping */
4430 keypad(status_win, TRUE);
4431 wbkgdset(status_win, get_line_attr(LINE_STATUS));
4432}
4433
4434static char *
4435read_prompt(const char *prompt)
4436{
4437 enum { READING, STOP, CANCEL } status = READING;
4438 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4439 int pos = 0;
4440
4441 while (status == READING) {
4442 struct view *view;
4443 int i, key;
4444
4445 input_mode = TRUE;
4446
4447 foreach_view (view, i)
4448 update_view(view);
4449
4450 input_mode = FALSE;
4451
4452 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4453 wclrtoeol(status_win);
4454
4455 /* Refresh, accept single keystroke of input */
4456 key = wgetch(status_win);
4457 switch (key) {
4458 case KEY_RETURN:
4459 case KEY_ENTER:
4460 case '\n':
4461 status = pos ? STOP : CANCEL;
4462 break;
4463
4464 case KEY_BACKSPACE:
4465 if (pos > 0)
4466 pos--;
4467 else
4468 status = CANCEL;
4469 break;
4470
4471 case KEY_ESC:
4472 status = CANCEL;
4473 break;
4474
4475 case ERR:
4476 break;
4477
4478 default:
4479 if (pos >= sizeof(buf)) {
4480 report("Input string too long");
4481 return NULL;
4482 }
4483
4484 if (isprint(key))
4485 buf[pos++] = (char) key;
4486 }
4487 }
4488
4489 /* Clear the status window */
4490 status_empty = FALSE;
4491 report("");
4492
4493 if (status == CANCEL)
4494 return NULL;
4495
4496 buf[pos++] = 0;
4497
4498 return buf;
4499}
4500
4501/*
4502 * Repository references
4503 */
4504
4505static struct ref *refs;
4506static size_t refs_size;
4507
4508/* Id <-> ref store */
4509static struct ref ***id_refs;
4510static size_t id_refs_size;
4511
4512static struct ref **
4513get_refs(char *id)
4514{
4515 struct ref ***tmp_id_refs;
4516 struct ref **ref_list = NULL;
4517 size_t ref_list_size = 0;
4518 size_t i;
4519
4520 for (i = 0; i < id_refs_size; i++)
4521 if (!strcmp(id, id_refs[i][0]->id))
4522 return id_refs[i];
4523
4524 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4525 if (!tmp_id_refs)
4526 return NULL;
4527
4528 id_refs = tmp_id_refs;
4529
4530 for (i = 0; i < refs_size; i++) {
4531 struct ref **tmp;
4532
4533 if (strcmp(id, refs[i].id))
4534 continue;
4535
4536 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4537 if (!tmp) {
4538 if (ref_list)
4539 free(ref_list);
4540 return NULL;
4541 }
4542
4543 ref_list = tmp;
4544 if (ref_list_size > 0)
4545 ref_list[ref_list_size - 1]->next = 1;
4546 ref_list[ref_list_size] = &refs[i];
4547
4548 /* XXX: The properties of the commit chains ensures that we can
4549 * safely modify the shared ref. The repo references will
4550 * always be similar for the same id. */
4551 ref_list[ref_list_size]->next = 0;
4552 ref_list_size++;
4553 }
4554
4555 if (ref_list)
4556 id_refs[id_refs_size++] = ref_list;
4557
4558 return ref_list;
4559}
4560
4561static int
4562read_ref(char *id, size_t idlen, char *name, size_t namelen)
4563{
4564 struct ref *ref;
4565 bool tag = FALSE;
4566 bool remote = FALSE;
4567
4568 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4569 /* Commits referenced by tags has "^{}" appended. */
4570 if (name[namelen - 1] != '}')
4571 return OK;
4572
4573 while (namelen > 0 && name[namelen] != '^')
4574 namelen--;
4575
4576 tag = TRUE;
4577 namelen -= STRING_SIZE("refs/tags/");
4578 name += STRING_SIZE("refs/tags/");
4579
4580 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4581 remote = TRUE;
4582 namelen -= STRING_SIZE("refs/remotes/");
4583 name += STRING_SIZE("refs/remotes/");
4584
4585 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4586 namelen -= STRING_SIZE("refs/heads/");
4587 name += STRING_SIZE("refs/heads/");
4588
4589 } else if (!strcmp(name, "HEAD")) {
4590 return OK;
4591 }
4592
4593 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4594 if (!refs)
4595 return ERR;
4596
4597 ref = &refs[refs_size++];
4598 ref->name = malloc(namelen + 1);
4599 if (!ref->name)
4600 return ERR;
4601
4602 strncpy(ref->name, name, namelen);
4603 ref->name[namelen] = 0;
4604 ref->tag = tag;
4605 ref->remote = remote;
4606 string_copy_rev(ref->id, id);
4607
4608 return OK;
4609}
4610
4611static int
4612load_refs(void)
4613{
4614 const char *cmd_env = getenv("TIG_LS_REMOTE");
4615 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4616
4617 return read_properties(popen(cmd, "r"), "\t", read_ref);
4618}
4619
4620static int
4621read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4622{
4623 if (!strcmp(name, "i18n.commitencoding"))
4624 string_ncopy(opt_encoding, value, valuelen);
4625
4626 if (!strcmp(name, "core.editor"))
4627 string_ncopy(opt_editor, value, valuelen);
4628
4629 return OK;
4630}
4631
4632static int
4633load_repo_config(void)
4634{
4635 return read_properties(popen(GIT_CONFIG " --list", "r"),
4636 "=", read_repo_config_option);
4637}
4638
4639static int
4640read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4641{
4642 if (!opt_git_dir[0]) {
4643 string_ncopy(opt_git_dir, name, namelen);
4644
4645 } else if (opt_is_inside_work_tree == -1) {
4646 /* This can be 3 different values depending on the
4647 * version of git being used. If git-rev-parse does not
4648 * understand --is-inside-work-tree it will simply echo
4649 * the option else either "true" or "false" is printed.
4650 * Default to true for the unknown case. */
4651 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4652
4653 } else {
4654 string_ncopy(opt_cdup, name, namelen);
4655 }
4656
4657 return OK;
4658}
4659
4660/* XXX: The line outputted by "--show-cdup" can be empty so the option
4661 * must be the last one! */
4662static int
4663load_repo_info(void)
4664{
4665 return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4666 "=", read_repo_info);
4667}
4668
4669static int
4670read_properties(FILE *pipe, const char *separators,
4671 int (*read_property)(char *, size_t, char *, size_t))
4672{
4673 char buffer[BUFSIZ];
4674 char *name;
4675 int state = OK;
4676
4677 if (!pipe)
4678 return ERR;
4679
4680 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4681 char *value;
4682 size_t namelen;
4683 size_t valuelen;
4684
4685 name = chomp_string(name);
4686 namelen = strcspn(name, separators);
4687
4688 if (name[namelen]) {
4689 name[namelen] = 0;
4690 value = chomp_string(name + namelen + 1);
4691 valuelen = strlen(value);
4692
4693 } else {
4694 value = "";
4695 valuelen = 0;
4696 }
4697
4698 state = read_property(name, namelen, value, valuelen);
4699 }
4700
4701 if (state != ERR && ferror(pipe))
4702 state = ERR;
4703
4704 pclose(pipe);
4705
4706 return state;
4707}
4708
4709
4710/*
4711 * Main
4712 */
4713
4714static void __NORETURN
4715quit(int sig)
4716{
4717 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4718 if (cursed)
4719 endwin();
4720 exit(0);
4721}
4722
4723static void __NORETURN
4724die(const char *err, ...)
4725{
4726 va_list args;
4727
4728 endwin();
4729
4730 va_start(args, err);
4731 fputs("tig: ", stderr);
4732 vfprintf(stderr, err, args);
4733 fputs("\n", stderr);
4734 va_end(args);
4735
4736 exit(1);
4737}
4738
4739int
4740main(int argc, char *argv[])
4741{
4742 struct view *view;
4743 enum request request;
4744 size_t i;
4745
4746 signal(SIGINT, quit);
4747
4748 if (setlocale(LC_ALL, "")) {
4749 char *codeset = nl_langinfo(CODESET);
4750
4751 string_ncopy(opt_codeset, codeset, strlen(codeset));
4752 }
4753
4754 if (load_repo_info() == ERR)
4755 die("Failed to load repo info.");
4756
4757 if (load_options() == ERR)
4758 die("Failed to load user config.");
4759
4760 /* Load the repo config file so options can be overwritten from
4761 * the command line. */
4762 if (load_repo_config() == ERR)
4763 die("Failed to load repo config.");
4764
4765 if (!parse_options(argc, argv))
4766 return 0;
4767
4768 /* Require a git repository unless when running in pager mode. */
4769 if (!opt_git_dir[0])
4770 die("Not a git repository");
4771
4772 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4773 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4774 if (opt_iconv == ICONV_NONE)
4775 die("Failed to initialize character set conversion");
4776 }
4777
4778 if (load_refs() == ERR)
4779 die("Failed to load refs.");
4780
4781 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4782 view->cmd_env = getenv(view->cmd_env);
4783
4784 request = opt_request;
4785
4786 init_display();
4787
4788 while (view_driver(display[current_view], request)) {
4789 int key;
4790 int i;
4791
4792 foreach_view (view, i)
4793 update_view(view);
4794
4795 /* Refresh, accept single keystroke of input */
4796 key = wgetch(status_win);
4797
4798 /* wgetch() with nodelay() enabled returns ERR when there's no
4799 * input. */
4800 if (key == ERR) {
4801 request = REQ_NONE;
4802 continue;
4803 }
4804
4805 request = get_keybinding(display[current_view]->keymap, key);
4806
4807 /* Some low-level request handling. This keeps access to
4808 * status_win restricted. */
4809 switch (request) {
4810 case REQ_PROMPT:
4811 {
4812 char *cmd = read_prompt(":");
4813
4814 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4815 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4816 opt_request = REQ_VIEW_DIFF;
4817 } else {
4818 opt_request = REQ_VIEW_PAGER;
4819 }
4820 break;
4821 }
4822
4823 request = REQ_NONE;
4824 break;
4825 }
4826 case REQ_SEARCH:
4827 case REQ_SEARCH_BACK:
4828 {
4829 const char *prompt = request == REQ_SEARCH
4830 ? "/" : "?";
4831 char *search = read_prompt(prompt);
4832
4833 if (search)
4834 string_ncopy(opt_search, search, strlen(search));
4835 else
4836 request = REQ_NONE;
4837 break;
4838 }
4839 case REQ_SCREEN_RESIZE:
4840 {
4841 int height, width;
4842
4843 getmaxyx(stdscr, height, width);
4844
4845 /* Resize the status view and let the view driver take
4846 * care of resizing the displayed views. */
4847 wresize(status_win, 1, width);
4848 mvwin(status_win, height - 1, 0);
4849 wrefresh(status_win);
4850 break;
4851 }
4852 default:
4853 break;
4854 }
4855 }
4856
4857 quit(0);
4858
4859 return 0;
4860}