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)]