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]))
53 #define REQ_OFFSET (MAX_COMMAND + 1)
55 /* Requests for switching between the different views. */
56 #define REQ_DIFF (REQ_OFFSET + 0)
57 #define REQ_LOG (REQ_OFFSET + 1)
58 #define REQ_MAIN (REQ_OFFSET + 2)
60 #define REQ_QUIT (REQ_OFFSET + 11)
61 #define REQ_VERSION (REQ_OFFSET + 12)
62 #define REQ_STOP (REQ_OFFSET + 13)
63 #define REQ_UPDATE (REQ_OFFSET + 14)
64 #define REQ_REDRAW (REQ_OFFSET + 15)
65 #define REQ_FIRST_LINE (REQ_OFFSET + 16)
66 #define REQ_LAST_LINE (REQ_OFFSET + 17)
68 #define COLOR_CURSOR 42
83 * stop all background loading
95 #define HELP "(d)iff, (l)og, (m)ain, (q)uit, (v)ersion, (h)elp"
102 struct keymap keymap[] = {
103 /* Cursor navigation */
104 { KEY_UP, REQ_PREV_LINE },
105 { 'k', REQ_PREV_LINE },
106 { KEY_DOWN, REQ_NEXT_LINE },
107 { 'j', REQ_NEXT_LINE },
108 { KEY_HOME, REQ_FIRST_LINE },
109 { KEY_END, REQ_LAST_LINE },
112 { KEY_IC, REQ_SCR_BLINE }, /* scroll field backward a line */
113 { KEY_DC, REQ_SCR_FLINE }, /* scroll field forward a line */
114 { KEY_NPAGE, REQ_SCR_FPAGE }, /* scroll field forward a page */
115 { KEY_PPAGE, REQ_SCR_BPAGE }, /* scroll field backward a page */
116 { 'w', REQ_SCR_FHPAGE }, /* scroll field forward half page */
117 { 's', REQ_SCR_BHPAGE }, /* scroll field backward half page */
123 /* No input from wgetch() with nodelay() enabled. */
126 { KEY_ESC, REQ_QUIT },
129 { 'v', REQ_VERSION },
134 get_request(int request)
138 for (i = 0; i < ARRAY_SIZE(keymap); i++)
139 if (keymap[i].alias == request)
140 return keymap[i].request;
156 int (*render)(struct view *, unsigned int);
161 unsigned long offset; /* Offset of the window top */
162 unsigned long lineno; /* Current line number */
165 unsigned long lines; /* Total number of lines */
166 char **line; /* Line index */
172 static int default_renderer(struct view *view, unsigned int lineno);
175 "git log --stat -n1 %s ; echo; " \
176 "git diff --find-copies-harder -B -C %s^ %s"
179 "git log --stat -n100 %s"
182 "git log --stat --pretty=raw %s"
184 /* The status window at the bottom. Used for polling keystrokes. */
185 static WINDOW *status_win;
187 #define SIZEOF_ID 1024
189 char head_id[SIZEOF_ID] = "HEAD";
190 char commit_id[SIZEOF_ID] = "HEAD";
192 static unsigned int current_view;
193 static unsigned int nloading;
195 static struct view views[] = {
196 { "diff", DIFF_CMD, commit_id, default_renderer },
197 { "log", LOG_CMD, head_id, default_renderer },
198 { "main", MAIN_CMD, head_id, default_renderer },
201 static struct view *display[ARRAY_SIZE(views)];
203 #define foreach_view(view, i) \
204 for (i = 0; i < sizeof(display) && (view = display[i]); i++)
207 redraw_view(struct view *view)
212 wmove(view->win, 0, 0);
214 for (lineno = 0; lineno < view->height; lineno++) {
215 if (!view->render(view, lineno))
219 redrawwin(view->win);
223 /* FIXME: Fix percentage. */
225 report_position(struct view *view, int all)
227 report(all ? "line %d of %d (%d%%) viewing from %d"
231 view->lines ? view->offset * 100 / view->lines : 0,
236 move_view(struct view *view, int lines)
238 /* The rendering expects the new offset. */
239 view->offset += lines;
241 assert(0 <= view->offset && view->offset < view->lines);
245 int from = lines > 0 ? view->height - lines : 0;
246 int to = from + (lines > 0 ? lines : -lines);
248 wscrl(view->win, lines);
250 for (; from < to; from++) {
251 if (!view->render(view, from))
256 /* Move current line into the view. */
257 if (view->lineno < view->offset) {
258 view->lineno = view->offset;
259 view->render(view, 0);
261 } else if (view->lineno >= view->offset + view->height) {
262 view->lineno = view->offset + view->height - 1;
263 view->render(view, view->lineno - view->offset);
266 assert(view->offset <= view->lineno && view->lineno <= view->lines);
268 redrawwin(view->win);
271 report_position(view, lines);
274 scroll_view(struct view *view, int request)
280 lines = view->height;
282 if (view->offset + lines > view->lines)
283 lines = view->lines - view->offset;
285 if (lines == 0 || view->offset + view->height >= view->lines) {
286 report("already at last line");
292 lines = view->height;
294 if (lines > view->offset)
295 lines = view->offset;
298 report("already at first line");
306 move_view(view, lines);
311 navigate_view(struct view *view, int request)
317 if (view->lineno == 0) {
318 report("already at first line");
325 if (view->lineno + 1 >= view->lines) {
326 report("already at last line");
333 steps = -view->lineno;
337 steps = view->lines - view->lineno - 1;
340 view->lineno += steps;
341 view->render(view, view->lineno - steps - view->offset);
343 if (view->lineno < view->offset ||
344 view->lineno >= view->offset + view->height) {
345 if (steps < 0 && -steps > view->offset) {
346 steps = -view->offset;
348 move_view(view, steps);
352 view->render(view, view->lineno - view->offset);
354 redrawwin(view->win);
357 report_position(view, view->height);
361 resize_view(struct view *view)
365 getmaxyx(stdscr, lines, cols);
368 mvwin(view->win, 0, 0);
369 wresize(view->win, lines - 1, cols);
372 view->win = newwin(lines - 1, 0, 0, 0);
374 report("failed to create %s view", view->name);
377 scrollok(view->win, TRUE);
380 getmaxyx(view->win, view->height, view->width);
385 begin_update(struct view *view)
392 if (snprintf(buf, sizeof(buf), view->cmd, id, id, id) < sizeof(buf))
393 view->pipe = popen(buf, "r");
399 nodelay(status_win, TRUE);
402 display[current_view] = view;
412 end_update(struct view *view)
414 wattrset(view->win, A_NORMAL);
419 nodelay(status_win, FALSE);
423 update_view(struct view *view)
429 int lines = view->height;
434 /* Only redraw after the first reading session. */
435 redraw = !view->line;
437 tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
443 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
446 linelen = strlen(line);
448 line[linelen - 1] = 0;
450 view->line[view->lines] = strdup(line);
451 if (!view->line[view->lines])
462 if (ferror(view->pipe)) {
463 report("failed to read %s", view->cmd);
466 } else if (feof(view->pipe)) {
467 report_position(view, 0);
474 report("allocation failure");
483 switch_view(struct view *prev, int request)
485 struct view *view = &views[request - REQ_OFFSET];
486 struct view *displayed;
490 foreach_view (displayed, i) ;
493 report("already in %s view", view->name);
495 report("FIXME: Maximize");
500 foreach_view (displayed, i) {
501 if (view == displayed) {
503 report("new current view");
515 for (i = 0; i < view->lines; i++)
523 if (prev && prev->pipe)
526 if (begin_update(view)) {
530 report("loading...");
537 /* Process a keystroke */
539 view_driver(struct view *view, int key)
541 int request = get_request(key);
550 navigate_view(view, request);
558 scroll_view(view, request);
564 view = switch_view(view, request);
572 foreach_view (view, i) {
575 scroll_view(view, 0);
581 report("version %s", VERSION);
604 #define ATTR(line, attr) { (line), sizeof(line) - 1, (attr) }
612 static struct attr attrs[] = {
613 ATTR("commit ", COLOR_PAIR(COLOR_GREEN)),
614 ATTR("Author: ", COLOR_PAIR(COLOR_CYAN)),
615 ATTR("Date: ", COLOR_PAIR(COLOR_YELLOW)),
616 ATTR("diff --git ", COLOR_PAIR(COLOR_YELLOW)),
617 ATTR("diff-tree ", COLOR_PAIR(COLOR_BLUE)),
618 ATTR("index ", COLOR_PAIR(COLOR_BLUE)),
619 ATTR("-", COLOR_PAIR(COLOR_RED)),
620 ATTR("+", COLOR_PAIR(COLOR_GREEN)),
621 ATTR("@", COLOR_PAIR(COLOR_MAGENTA)),
625 default_renderer(struct view *view, unsigned int lineno)
632 if (view->offset + lineno >= view->lines)
635 line = view->line[view->offset + lineno];
636 if (!line) return FALSE;
638 linelen = strlen(line);
640 for (i = 0; i < ARRAY_SIZE(attrs); i++) {
641 if (linelen < attrs[i].linelen
642 || strncmp(attrs[i].line, line, attrs[i].linelen))
645 attr = attrs[i].attr;
649 if (view->offset + lineno == view->lineno) {
651 strncpy(commit_id, line + 7, SIZEOF_ID);
652 attr = COLOR_PAIR(COLOR_CURSOR) | A_BOLD;
655 wattrset(view->win, attr);
656 //mvwprintw(view->win, lineno, 0, "%4d: %s", view->offset + lineno, line);
657 mvwaddstr(view->win, lineno, 0, line);
673 /* FIXME: Shutdown gracefully. */
678 static void die(const char *err, ...)
685 fputs("tig: ", stderr);
686 vfprintf(stderr, err, args);
694 report(const char *msg, ...)
701 wmove(status_win, 0, 0);
704 if (display[current_view])
705 wprintw(status_win, "%s %4s: ", commit_id, display[current_view]->name);
707 vwprintw(status_win, msg, args);
708 wrefresh(status_win);
716 int bg = COLOR_BLACK;
720 if (use_default_colors() != ERR)
723 init_pair(COLOR_BLACK, COLOR_BLACK, bg);
724 init_pair(COLOR_GREEN, COLOR_GREEN, bg);
725 init_pair(COLOR_RED, COLOR_RED, bg);
726 init_pair(COLOR_CYAN, COLOR_CYAN, bg);
727 init_pair(COLOR_WHITE, COLOR_WHITE, bg);
728 init_pair(COLOR_MAGENTA, COLOR_MAGENTA, bg);
729 init_pair(COLOR_BLUE, COLOR_BLUE, bg);
730 init_pair(COLOR_YELLOW, COLOR_YELLOW, bg);
731 init_pair(COLOR_CURSOR, COLOR_WHITE, COLOR_GREEN);
735 main(int argc, char *argv[])
737 int request = REQ_MAIN;
740 signal(SIGINT, quit);
742 initscr(); /* initialize the curses library */
743 nonl(); /* tell curses not to do NL->CR/NL on output */
744 cbreak(); /* take input chars one at a time, no wait for \n */
745 noecho(); /* don't echo input */
746 leaveok(stdscr, TRUE);
752 getmaxyx(stdscr, y, x);
753 status_win = newwin(1, 0, y - 1, 0);
755 die("Failed to create status window");
757 /* Enable keyboard mapping */
758 keypad(status_win, TRUE);
759 wattrset(status_win, COLOR_PAIR(COLOR_GREEN));
761 while (view_driver(display[current_view], request)) {
765 foreach_view (view, i) {
771 /* Refresh, accept single keystroke of input */
772 request = wgetch(status_win);
773 if (request == KEY_RESIZE) {
776 getmaxyx(stdscr, lines, cols);
777 mvwin(status_win, lines - 1, 0);
778 wresize(status_win, 1, cols - 1);
790 * Copyright (c) Jonas Fonseca, 2006
792 * This program is free software; you can redistribute it and/or modify
793 * it under the terms of the GNU General Public License as published by
794 * the Free Software Foundation; either version 2 of the License, or
795 * (at your option) any later version.
799 * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
800 * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]