Commit | Line | Data |
---|---|---|
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 |
53 | struct 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 |
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; | |
e3e7b9fc JF |
70 | |
71 | static void | |
72 | put_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 |
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 | ||
800a900c JF |
107 | /* |
108 | * Init and quit | |
109 | */ | |
110 | ||
05f1685b JF |
111 | static void |
112 | quit(int sig) | |
800a900c JF |
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 | { | |
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 | ||
141 | static void | |
142 | init(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 | 168 | static void |
a74fa190 | 169 | log_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 |
223 | static struct view * |
224 | update_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 |
240 | static struct view * |
241 | read_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 | ||
280 | int | |
281 | main(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 | **/ |