chiark / gitweb /
20d58fa5801b16f35d4f5aeec2295001947f7a5f
[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  **/