7 * tig - text-mode interface for git
13 * tig log [git log options]
14 * tig diff [git diff options]
15 * tig < [git log or git diff output]
19 * Browse changes in a git repository.
44 static void die(const char *err, ...);
45 static void report(const char *msg, ...);
47 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
52 #define REQ_OFFSET (MAX_COMMAND + 1)
54 /* Requests for switching between the different views. */
55 #define REQ_DIFF (REQ_OFFSET + 0)
56 #define REQ_LOG (REQ_OFFSET + 1)
57 #define REQ_MAIN (REQ_OFFSET + 2)
59 #define REQ_QUIT (REQ_OFFSET + 11)
60 #define REQ_VERSION (REQ_OFFSET + 12)
61 #define REQ_STOP (REQ_OFFSET + 13)
62 #define REQ_UPDATE (REQ_OFFSET + 14)
63 #define REQ_REDRAW (REQ_OFFSET + 15)
79 * stop all background loading
91 #define HELP "(d)iff, (l)og, (m)ain, (q)uit, (v)ersion, (h)elp"
98 struct keymap keymap[] = {
99 { KEY_UP, REQ_PREV_LINE },
100 { 'k', REQ_PREV_LINE },
101 { KEY_DOWN, REQ_NEXT_LINE },
102 { 'j', REQ_NEXT_LINE },
103 { KEY_NPAGE, REQ_NEXT_PAGE },
104 { KEY_PPAGE, REQ_PREV_PAGE },
110 /* No input from wgetch() with nodelay() enabled. */
113 { KEY_ESC, REQ_QUIT },
116 { 'v', REQ_VERSION },
121 get_request(int request)
125 for (i = 0; i < ARRAY_SIZE(keymap); i++)
126 if (keymap[i].alias == request)
127 return keymap[i].request;
142 int (*render)(struct view *, int);
146 unsigned long offset; /* Offset of the window top */
147 unsigned long lineno; /* Current line number */
150 unsigned long lines; /* Total number of lines */
151 char **line; /* Line index */
157 static int default_renderer(struct view *view, int lineno);
160 "git log --stat -n1 %s ; echo; " \
161 "git diff --find-copies-harder -B -C %s^ %s"
164 "git log --stat -n100 %s"
166 /* The status window at the bottom. Used for polling keystrokes. */
167 static WINDOW *status_win;
169 static struct view views[] = {
170 { "diff", DIFF_CMD, default_renderer },
171 { "log", LOG_CMD, default_renderer },
175 static struct view *display[ARRAY_SIZE(views)];
176 static unsigned int current_view;
177 static unsigned int nloading;
179 #define foreach_view(view, i) \
180 for (i = 0; i < sizeof(display) && (view = display[i]); i++)
183 redraw_view(struct view *view)
189 wmove(view->win, 0, 0);
191 getmaxyx(view->win, lines, cols);
193 for (lineno = 0; lineno < lines; lineno++) {
194 view->render(view, lineno);
197 redrawwin(view->win);
201 /* FIXME: Fix percentage. */
203 report_position(struct view *view, int all)
205 report(all ? "line %d of %d (%d%%) viewing from %d"
209 view->lines ? view->offset * 100 / view->lines : 0,
214 scroll_view(struct view *view, int request)
217 enum { BACKWARD = -1, FORWARD = 1 } direction = FORWARD;
219 getmaxyx(view->win, y, x);
225 if (view->offset + lines > view->lines)
226 lines = view->lines - view->offset;
228 if (lines == 0 || view->offset + y >= view->lines) {
229 report("already at last line");
237 if (lines > view->offset)
238 lines = view->offset;
241 report("already at first line");
245 direction = BACKWARD;
252 report("off=%d lines=%d lineno=%d move=%d", view->offset, view->lines, view->lineno, lines * direction);
254 /* The rendering expects the new offset. */
255 view->offset += lines * direction;
257 /* Move current line into the view. */
258 if (view->lineno < view->offset)
259 view->lineno = view->offset;
260 if (view->lineno > view->offset + y)
261 view->lineno = view->offset + y;
263 assert(0 <= view->offset && view->offset < view->lines);
264 //assert(0 <= view->offset + lines && view->offset + lines < view->lines);
265 assert(view->offset <= view->lineno && view->lineno <= view->lines);
268 int from = direction == FORWARD ? y - lines : 0;
269 int to = from + lines;
271 wscrl(view->win, lines * direction);
273 for (; from < to; from++) {
274 if (!view->render(view, from))
279 redrawwin(view->win);
282 report_position(view, lines);
286 resize_view(struct view *view)
290 getmaxyx(stdscr, lines, cols);
293 mvwin(view->win, 0, 0);
294 wresize(view->win, lines - 1, cols);
297 view->win = newwin(lines - 1, 0, 0, 0);
299 report("Failed to create %s view", view->name);
302 scrollok(view->win, TRUE);
308 begin_update(struct view *view)
313 if (snprintf(buf, sizeof(buf), view->cmd, "HEAD", "HEAD", "HEAD") < sizeof(buf))
314 view->pipe = popen(buf, "r");
320 nodelay(status_win, TRUE);
323 display[current_view] = view;
333 end_update(struct view *view)
335 wattrset(view->win, A_NORMAL);
340 nodelay(status_win, FALSE);
344 update_view(struct view *view)
355 getmaxyx(view->win, lines, cols);
357 redraw = !view->line;
359 tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
365 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
371 linelen = strlen(line);
373 line[linelen - 1] = 0;
375 view->line[view->lines] = strdup(line);
376 if (!view->line[view->lines])
384 if (ferror(view->pipe)) {
385 report("Failed to read %s", view->cmd);
388 } else if (feof(view->pipe)) {
389 report_position(view, 0);
396 report("Allocation failure");
405 switch_view(struct view *prev, int request)
407 struct view *view = &views[request - REQ_OFFSET];
408 struct view *displayed;
412 foreach_view (displayed, i) ;
415 report("Already in %s view", view->name);
417 report("FIXME: Maximize");
422 foreach_view (displayed, i) {
423 if (view == displayed) {
425 report("New current view");
437 for (i = 0; i < view->lines; i++)
445 if (prev && prev->pipe)
448 if (begin_update(view)) {
452 report("loading...");
459 /* Process a keystroke */
461 view_driver(struct view *view, int key)
463 int request = get_request(key);
472 scroll_view(view, request);
478 view = switch_view(view, request);
486 foreach_view (view, i) {
489 scroll_view(view, 0);
495 report("version %s", VERSION);
518 #define ATTR(line, attr) { (line), sizeof(line) - 1, (attr) }
526 static struct attr attrs[] = {
527 ATTR("commit ", COLOR_PAIR(COLOR_GREEN)),
528 ATTR("Author: ", COLOR_PAIR(COLOR_CYAN)),
529 ATTR("Date: ", COLOR_PAIR(COLOR_YELLOW)),
530 ATTR("diff --git ", COLOR_PAIR(COLOR_YELLOW)),
531 ATTR("diff-tree ", COLOR_PAIR(COLOR_BLUE)),
532 ATTR("index ", COLOR_PAIR(COLOR_BLUE)),
533 ATTR("-", COLOR_PAIR(COLOR_RED)),
534 ATTR("+", COLOR_PAIR(COLOR_GREEN)),
535 ATTR("@", COLOR_PAIR(COLOR_MAGENTA)),
539 default_renderer(struct view *view, int lineno)
546 line = view->line[view->offset + lineno];
547 if (!line) return FALSE;
549 linelen = strlen(line);
551 for (i = 0; i < ARRAY_SIZE(attrs); i++) {
552 if (linelen < attrs[i].linelen
553 || strncmp(attrs[i].line, line, attrs[i].linelen))
556 attr = attrs[i].attr;
560 wattrset(view->win, attr);
561 mvwprintw(view->win, lineno, 0, "%4d: %s", view->offset + lineno, line);
577 /* FIXME: Shutdown gracefully. */
582 static void die(const char *err, ...)
589 fputs("tig: ", stderr);
590 vfprintf(stderr, err, args);
598 report(const char *msg, ...)
605 wmove(status_win, 0, 0);
607 if (display[current_view])
608 wprintw(status_win, "%4s: ", display[current_view]->name);
610 vwprintw(status_win, msg, args);
611 wrefresh(status_win);
619 int bg = COLOR_BLACK;
623 if (use_default_colors() != ERR)
626 init_pair(COLOR_BLACK, COLOR_BLACK, bg);
627 init_pair(COLOR_GREEN, COLOR_GREEN, bg);
628 init_pair(COLOR_RED, COLOR_RED, bg);
629 init_pair(COLOR_CYAN, COLOR_CYAN, bg);
630 init_pair(COLOR_WHITE, COLOR_WHITE, bg);
631 init_pair(COLOR_MAGENTA, COLOR_MAGENTA, bg);
632 init_pair(COLOR_BLUE, COLOR_BLUE, bg);
633 init_pair(COLOR_YELLOW, COLOR_YELLOW, bg);
637 main(int argc, char *argv[])
639 int request = REQ_MAIN;
642 signal(SIGINT, quit);
644 initscr(); /* initialize the curses library */
645 nonl(); /* tell curses not to do NL->CR/NL on output */
646 cbreak(); /* take input chars one at a time, no wait for \n */
647 noecho(); /* don't echo input */
648 leaveok(stdscr, TRUE);
654 getmaxyx(stdscr, y, x);
655 status_win = newwin(1, 0, y - 1, 0);
657 die("Failed to create status window");
659 /* Enable keyboard mapping */
660 keypad(status_win, TRUE);
661 wattrset(status_win, COLOR_PAIR(COLOR_GREEN));
663 while (view_driver(display[current_view], request)) {
667 foreach_view (view, i) {
673 /* Refresh, accept single keystroke of input */
674 request = wgetch(status_win);
675 if (request == KEY_RESIZE) {
678 getmaxyx(stdscr, lines, cols);
679 mvwin(status_win, lines - 1, 0);
680 wresize(status_win, 1, cols - 1);
692 * Copyright (c) Jonas Fonseca, 2006
694 * This program is free software; you can redistribute it and/or modify
695 * it under the terms of the GNU General Public License as published by
696 * the Free Software Foundation; either version 2 of the License, or
697 * (at your option) any later version.
701 * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
702 * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]