Exploration
[tig] / cgit.c
CommitLineData
a7bc4b14
JF
1/**
2 * gitzilla(1)
3 * ===========
800a900c 4 *
a7bc4b14
JF
5 * NAME
6 * ----
7 * gitzilla - cursed git browser
800a900c 8 *
a7bc4b14
JF
9 * SYNOPSIS
10 * --------
11 * gitzilla
12 *
13 * DESCRIPTION
14 * -----------
15 *
16 * a
17 *
18 **/
800a900c 19
e3e7b9fc 20#include <stdarg.h>
800a900c
JF
21#include <stdlib.h>
22#include <stdio.h>
e3e7b9fc 23#include <string.h>
800a900c
JF
24#include <ctype.h>
25#include <signal.h>
26
27#include <curses.h>
28
a7bc4b14
JF
29/**
30 * OPTIONS
31 * -------
32 *
33 * None
34 *
35 **/
36
37/**
38 * KEYS
39 * ----
40 *
41 * q:: quit
42 * s:: shell
43 * j:: down
44 * k:: up
45 *
46 **/
e3e7b9fc 47
a74fa190
JF
48#define MSG_HELP "(q)uit, (s)hell, (j) down, (k) up"
49
05f1685b
JF
50#define KEY_ESC 27
51#define KEY_TAB 9
52
a7bc4b14
JF
53struct view {
54 WINDOW *win;
05f1685b 55
a7bc4b14
JF
56 char *cmd;
57 void (*reader)(char *, int);
58 FILE *pipe;
05f1685b 59
a7bc4b14
JF
60 unsigned long lines;
61 unsigned long lineno;
62};
05f1685b 63
a7bc4b14
JF
64static struct view main_view;
65static struct view diff_view;
66static struct view log_view;
67static struct view status_view;
68
69int do_resize = 1;
e3e7b9fc
JF
70
71static void
72put_status(char *msg, ...)
73{
74 va_list args;
75
76 va_start(args, msg);
a7bc4b14
JF
77 werase(status_view.win);
78 wmove(status_view.win, 0, 0);
79 vwprintw(status_view.win, msg, args);
80 wrefresh(status_view.win);
e3e7b9fc
JF
81 va_end(args);
82}
83
a7bc4b14
JF
84static void
85resize_views(void)
86{
87 int x, y;
88
89 getmaxyx(stdscr, y, x);
90
91 if (status_view.win)
92 delwin(status_view.win);
93 status_view.win = newwin(1, 0, y - 1, 0);
94
95 wattrset(status_view.win, COLOR_PAIR(COLOR_GREEN));
96 put_status(MSG_HELP);
97
98 if (main_view.win)
99 delwin(main_view.win);
100 main_view.win = newwin(y - 1, 0, 0, 0);
101
102 scrollok(main_view.win, TRUE);
103 keypad(main_view.win, TRUE); /* enable keyboard mapping */
104 put_status("%d %d", y, x);
105}
106
800a900c
JF
107/*
108 * Init and quit
109 */
110
05f1685b
JF
111static void
112quit(int sig)
800a900c
JF
113{
114 endwin();
115
116 /* do your non-curses wrapup here */
117
118 exit(0);
119}
120
121static void
122init_colors(void)
123{
05f1685b
JF
124 int bg = COLOR_BLACK;
125
800a900c
JF
126 start_color();
127
e3e7b9fc 128 if (use_default_colors() != ERR)
05f1685b
JF
129 bg = -1;
130
131 init_pair(COLOR_BLACK, COLOR_BLACK, bg);
132 init_pair(COLOR_GREEN, COLOR_GREEN, bg);
133 init_pair(COLOR_RED, COLOR_RED, bg);
134 init_pair(COLOR_CYAN, COLOR_CYAN, bg);
135 init_pair(COLOR_WHITE, COLOR_WHITE, bg);
136 init_pair(COLOR_MAGENTA, COLOR_MAGENTA, bg);
137 init_pair(COLOR_BLUE, COLOR_BLUE, bg);
138 init_pair(COLOR_YELLOW, COLOR_YELLOW, bg);
800a900c
JF
139}
140
141static void
142init(void)
143{
05f1685b
JF
144 signal(SIGINT, quit);
145
146 initscr(); /* initialize the curses library */
147 nonl(); /* tell curses not to do NL->CR/NL on output */
148 cbreak(); /* take input chars one at a time, no wait for \n */
149 noecho(); /* don't echo input */
e3e7b9fc 150 leaveok(stdscr, TRUE);
a7bc4b14 151 /* curs_set(0); */
800a900c
JF
152
153 if (has_colors())
154 init_colors();
05f1685b
JF
155}
156
157/*
a74fa190 158 * Pipe readers
05f1685b
JF
159 */
160
161#define DIFF_CMD \
a7bc4b14
JF
162 "git log --stat -n1 HEAD ; echo; " \
163 "git diff --find-copies-harder -B -C HEAD^ HEAD"
05f1685b 164
a74fa190 165#define LOG_CMD \
a7bc4b14 166 "git log --stat -n100"
a74fa190 167
05f1685b 168static void
a74fa190 169log_reader(char *line, int lineno)
05f1685b 170{
a74fa190 171 static int log_reader_skip;
05f1685b
JF
172
173 if (!line) {
a7bc4b14 174 wattrset(main_view.win, A_NORMAL);
a74fa190 175 log_reader_skip = 0;
05f1685b
JF
176 return;
177 }
178
179 if (!strncmp("commit ", line, 7)) {
a7bc4b14 180 wattrset(main_view.win, COLOR_PAIR(COLOR_GREEN));
05f1685b
JF
181
182 } else if (!strncmp("Author: ", line, 8)) {
a7bc4b14 183 wattrset(main_view.win, COLOR_PAIR(COLOR_CYAN));
05f1685b 184
a7bc4b14
JF
185 } else if (!strncmp("Date: ", line, 8)) {
186 wattrset(main_view.win, COLOR_PAIR(COLOR_YELLOW));
05f1685b
JF
187
188 } else if (!strncmp("diff --git ", line, 11)) {
a7bc4b14 189 wattrset(main_view.win, COLOR_PAIR(COLOR_YELLOW));
05f1685b
JF
190
191 } else if (!strncmp("diff-tree ", line, 10)) {
a7bc4b14 192 wattrset(main_view.win, COLOR_PAIR(COLOR_BLUE));
05f1685b
JF
193
194 } else if (!strncmp("index ", line, 6)) {
a7bc4b14 195 wattrset(main_view.win, COLOR_PAIR(COLOR_BLUE));
05f1685b
JF
196
197 } else if (line[0] == '-') {
a7bc4b14 198 wattrset(main_view.win, COLOR_PAIR(COLOR_RED));
05f1685b
JF
199
200 } else if (line[0] == '+') {
a7bc4b14 201 wattrset(main_view.win, COLOR_PAIR(COLOR_GREEN));
05f1685b
JF
202
203 } else if (line[0] == '@') {
a7bc4b14 204 wattrset(main_view.win, COLOR_PAIR(COLOR_MAGENTA));
05f1685b
JF
205
206 } else if (line[0] == ':') {
a7bc4b14 207 main_view.lines--;
a74fa190 208 log_reader_skip = 1;
05f1685b
JF
209 return;
210
a74fa190 211 } else if (log_reader_skip) {
a7bc4b14 212 main_view.lines--;
a74fa190 213 log_reader_skip = 0;
05f1685b
JF
214 return;
215
216 } else {
a7bc4b14 217 wattrset(main_view.win, A_NORMAL);
05f1685b
JF
218 }
219
a7bc4b14 220 mvwaddstr(main_view.win, lineno, 0, line);
05f1685b
JF
221}
222
a7bc4b14
JF
223static struct view *
224update_view(struct view *view, char *cmd)
05f1685b 225{
a7bc4b14
JF
226 view->cmd = cmd;
227 view->pipe = popen(cmd, "r");
228 view->lines = 0;
229 view->lineno = 0;
230 view->reader = log_reader;
231
232 wclear(view->win);
233 wmove(view->win, 0, 0);
234
e3e7b9fc 235 put_status("Loading...");
a7bc4b14
JF
236
237 return view;
05f1685b
JF
238}
239
a7bc4b14
JF
240static struct view *
241read_pipe(struct view *view, int lines)
05f1685b
JF
242{
243 char buffer[BUFSIZ];
244 char *line;
e3e7b9fc 245 int x, y;
05f1685b 246
a7bc4b14 247 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
e3e7b9fc
JF
248 int linelen;
249
05f1685b
JF
250 if (!--lines)
251 break;
e3e7b9fc
JF
252
253 linelen = strlen(line);
254 if (linelen)
255 line[linelen - 1] = 0;
256
a7bc4b14 257 view->reader(line, view->lines++);
05f1685b
JF
258 }
259
a7bc4b14
JF
260 if (ferror(view->pipe)) {
261 put_status("Failed to read %s", view->cmd, view->lines - 1);
262
263 } else if (feof(view->pipe)) {
264 put_status("%s (lines %d)", MSG_HELP, view->lines - 1);
265
266 } else {
267 return view;
05f1685b 268 }
a7bc4b14
JF
269
270 view->reader(NULL, view->lines - 1);
271 pclose(view->pipe);
272 view->pipe = NULL;
273 view->reader = NULL;
800a900c
JF
274}
275
276/*
277 * Main
278 */
279
280int
281main(int argc, char *argv[])
282{
a7bc4b14
JF
283 static struct view *loading_view;
284
800a900c
JF
285 init();
286
a74fa190 287 //pipe = open_pipe(LOG_CMD, log_reader);
800a900c
JF
288
289 for (;;) {
05f1685b
JF
290 int c;
291
a7bc4b14
JF
292 if (do_resize) {
293 resize_views();
294 do_resize = 0;
295 }
296
297 if (loading_view && (loading_view = read_pipe(loading_view, 20)))
298 nodelay(loading_view->win, TRUE);
05f1685b 299
a7bc4b14 300 c = wgetch(main_view.win); /* refresh, accept single keystroke of input */
05f1685b 301
a7bc4b14
JF
302 if (loading_view)
303 nodelay(loading_view->win, FALSE);
05f1685b 304
e3e7b9fc 305 /* No input from wgetch() with nodelay() enabled. */
a7bc4b14
JF
306 if (c == ERR) {
307 doupdate();
05f1685b 308 continue;
a7bc4b14 309 }
800a900c
JF
310
311 /* Process the command keystroke */
312 switch (c) {
a7bc4b14
JF
313 case KEY_RESIZE:
314 fprintf(stderr, "resize");
315 exit;
316 break;
317
05f1685b 318 case KEY_ESC:
800a900c
JF
319 case 'q':
320 quit(0);
a7bc4b14 321 main_view.lineno--;
800a900c
JF
322 return 0;
323
05f1685b
JF
324 case KEY_DOWN:
325 case 'j':
a7bc4b14
JF
326 {
327 int x, y;
328
329 getmaxyx(main_view.win, y, x);
330 if (main_view.lineno + y < main_view.lines) {
331 wscrl(main_view.win, 1);
332 main_view.lineno++;
333 put_status("line %d out of %d (%d%%)",
334 main_view.lineno,
335 main_view.lines,
336 100 * main_view.lineno / main_view.lines);
337 } else {
338 put_status("last line reached");
339 }
05f1685b 340 break;
a7bc4b14 341 }
05f1685b
JF
342 case KEY_UP:
343 case 'k':
a7bc4b14
JF
344 if (main_view.lineno > 1) {
345 wscrl(main_view.win, -1);
346 main_view.lineno--;
347 put_status("line %d out of %d (%d%%)",
348 main_view.lineno,
349 main_view.lines,
350 100 * main_view.lineno / main_view.lines);
351 } else {
352 put_status("first line reached");
353 }
05f1685b
JF
354 break;
355
e3e7b9fc 356 case 'c':
a7bc4b14 357 wclear(main_view.win);
e3e7b9fc
JF
358 break;
359
05f1685b 360 case 'd':
a7bc4b14 361 loading_view = update_view(&main_view, DIFF_CMD);
05f1685b
JF
362 break;
363
364 case 'l':
a7bc4b14 365 loading_view = update_view(&main_view, LOG_CMD);
05f1685b
JF
366 break;
367
800a900c 368 case 's':
a7bc4b14 369 mvwaddstr(status_view.win, 0, 0, "Shelling out...");
800a900c
JF
370 def_prog_mode(); /* save current tty modes */
371 endwin(); /* restore original tty modes */
372 system("sh"); /* run shell */
05f1685b 373
a7bc4b14
JF
374 werase(status_view.win);
375 mvwaddstr(status_view.win, 0, 0, MSG_HELP);
800a900c 376 reset_prog_mode();
800a900c 377 break;
800a900c
JF
378 }
379
a7bc4b14
JF
380 redrawwin(main_view.win);
381 wrefresh(main_view.win);
800a900c
JF
382 }
383
384 quit(0);
385}
a7bc4b14
JF
386
387/**
388 * COPYRIGHT
389 * ---------
390 * Copyright (c) Jonas Fonseca, 2006
391 *
392 * This program is free software; you can redistribute it and/or modify
393 * it under the terms of the GNU General Public License as published by
394 * the Free Software Foundation; either version 2 of the License, or
395 * (at your option) any later version.
396 *
397 * SEE ALSO
398 * --------
399 * gitlink:cogito[7],
400 * gitlink:git[7]
401 **/