Exploration
[tig] / cgit.c
1 /**
2 * gitzilla(1)
3 * ===========
4 *
5 * NAME
6 * ----
7 * gitzilla - cursed git browser
8 *
9 * SYNOPSIS
10 * --------
11 * gitzilla
12 *
13 * DESCRIPTION
14 * -----------
15 *
16 * a
17 *
18 **/
19
20 #include <stdarg.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <signal.h>
26
27 #include <curses.h>
28
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 **/
47
48 #define MSG_HELP "(q)uit, (s)hell, (j) down, (k) up"
49
50 #define KEY_ESC 27
51 #define KEY_TAB 9
52
53 struct view {
54 WINDOW *win;
55
56 char *cmd;
57 void (*reader)(char *, int);
58 FILE *pipe;
59
60 unsigned long lines;
61 unsigned long lineno;
62 };
63
64 static struct view main_view;
65 static struct view diff_view;
66 static struct view log_view;
67 static struct view status_view;
68
69 int do_resize = 1;
70
71 static void
72 put_status(char *msg, ...)
73 {
74 va_list args;
75
76 va_start(args, msg);
77 werase(status_view.win);
78 wmove(status_view.win, 0, 0);
79 vwprintw(status_view.win, msg, args);
80 wrefresh(status_view.win);
81 va_end(args);
82 }
83
84 static void
85 resize_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
107 /*
108 * Init and quit
109 */
110
111 static void
112 quit(int sig)
113 {
114 endwin();
115
116 /* do your non-curses wrapup here */
117
118 exit(0);
119 }
120
121 static void
122 init_colors(void)
123 {
124 int bg = COLOR_BLACK;
125
126 start_color();
127
128 if (use_default_colors() != ERR)
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);
139 }
140
141 static void
142 init(void)
143 {
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 */
150 leaveok(stdscr, TRUE);
151 /* curs_set(0); */
152
153 if (has_colors())
154 init_colors();
155 }
156
157 /*
158 * Pipe readers
159 */
160
161 #define DIFF_CMD \
162 "git log --stat -n1 HEAD ; echo; " \
163 "git diff --find-copies-harder -B -C HEAD^ HEAD"
164
165 #define LOG_CMD \
166 "git log --stat -n100"
167
168 static void
169 log_reader(char *line, int lineno)
170 {
171 static int log_reader_skip;
172
173 if (!line) {
174 wattrset(main_view.win, A_NORMAL);
175 log_reader_skip = 0;
176 return;
177 }
178
179 if (!strncmp("commit ", line, 7)) {
180 wattrset(main_view.win, COLOR_PAIR(COLOR_GREEN));
181
182 } else if (!strncmp("Author: ", line, 8)) {
183 wattrset(main_view.win, COLOR_PAIR(COLOR_CYAN));
184
185 } else if (!strncmp("Date: ", line, 8)) {
186 wattrset(main_view.win, COLOR_PAIR(COLOR_YELLOW));
187
188 } else if (!strncmp("diff --git ", line, 11)) {
189 wattrset(main_view.win, COLOR_PAIR(COLOR_YELLOW));
190
191 } else if (!strncmp("diff-tree ", line, 10)) {
192 wattrset(main_view.win, COLOR_PAIR(COLOR_BLUE));
193
194 } else if (!strncmp("index ", line, 6)) {
195 wattrset(main_view.win, COLOR_PAIR(COLOR_BLUE));
196
197 } else if (line[0] == '-') {
198 wattrset(main_view.win, COLOR_PAIR(COLOR_RED));
199
200 } else if (line[0] == '+') {
201 wattrset(main_view.win, COLOR_PAIR(COLOR_GREEN));
202
203 } else if (line[0] == '@') {
204 wattrset(main_view.win, COLOR_PAIR(COLOR_MAGENTA));
205
206 } else if (line[0] == ':') {
207 main_view.lines--;
208 log_reader_skip = 1;
209 return;
210
211 } else if (log_reader_skip) {
212 main_view.lines--;
213 log_reader_skip = 0;
214 return;
215
216 } else {
217 wattrset(main_view.win, A_NORMAL);
218 }
219
220 mvwaddstr(main_view.win, lineno, 0, line);
221 }
222
223 static struct view *
224 update_view(struct view *view, char *cmd)
225 {
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
235 put_status("Loading...");
236
237 return view;
238 }
239
240 static struct view *
241 read_pipe(struct view *view, int lines)
242 {
243 char buffer[BUFSIZ];
244 char *line;
245 int x, y;
246
247 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
248 int linelen;
249
250 if (!--lines)
251 break;
252
253 linelen = strlen(line);
254 if (linelen)
255 line[linelen - 1] = 0;
256
257 view->reader(line, view->lines++);
258 }
259
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;
268 }
269
270 view->reader(NULL, view->lines - 1);
271 pclose(view->pipe);
272 view->pipe = NULL;
273 view->reader = NULL;
274 }
275
276 /*
277 * Main
278 */
279
280 int
281 main(int argc, char *argv[])
282 {
283 static struct view *loading_view;
284
285 init();
286
287 //pipe = open_pipe(LOG_CMD, log_reader);
288
289 for (;;) {
290 int c;
291
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);
299
300 c = wgetch(main_view.win); /* refresh, accept single keystroke of input */
301
302 if (loading_view)
303 nodelay(loading_view->win, FALSE);
304
305 /* No input from wgetch() with nodelay() enabled. */
306 if (c == ERR) {
307 doupdate();
308 continue;
309 }
310
311 /* Process the command keystroke */
312 switch (c) {
313 case KEY_RESIZE:
314 fprintf(stderr, "resize");
315 exit;
316 break;
317
318 case KEY_ESC:
319 case 'q':
320 quit(0);
321 main_view.lineno--;
322 return 0;
323
324 case KEY_DOWN:
325 case 'j':
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 }
340 break;
341 }
342 case KEY_UP:
343 case 'k':
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 }
354 break;
355
356 case 'c':
357 wclear(main_view.win);
358 break;
359
360 case 'd':
361 loading_view = update_view(&main_view, DIFF_CMD);
362 break;
363
364 case 'l':
365 loading_view = update_view(&main_view, LOG_CMD);
366 break;
367
368 case 's':
369 mvwaddstr(status_view.win, 0, 0, "Shelling out...");
370 def_prog_mode(); /* save current tty modes */
371 endwin(); /* restore original tty modes */
372 system("sh"); /* run shell */
373
374 werase(status_view.win);
375 mvwaddstr(status_view.win, 0, 0, MSG_HELP);
376 reset_prog_mode();
377 break;
378 }
379
380 redrawwin(main_view.win);
381 wrefresh(main_view.win);
382 }
383
384 quit(0);
385 }
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 **/