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