Primitive option parsing; rendering generalizations
[tig] / tig.c
1 /**
2  * TIG(1)
3  * ======
4  *
5  * NAME
6  * ----
7  * tig - text-mode interface for git
8  *
9  * SYNOPSIS
10  * --------
11  * [verse]
12  * tig [options]
13  * tig [options] log  [git log options]
14  * tig [options] diff [git diff options]
15  * tig [options] < [git log or git diff output]
16  *
17  * DESCRIPTION
18  * -----------
19  * Browse changes in a git repository.
20  **/
21
22 #define DEBUG
23 #ifndef DEBUG
24 #define NDEBUG
25 #endif
26
27 #ifndef VERSION
28 #define VERSION "tig-0.1"
29 #endif
30
31 #include <assert.h>
32 #include <ctype.h>
33 #include <signal.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <time.h>
39
40 #include <curses.h>
41 #include <form.h>
42
43 static void die(const char *err, ...);
44 static void report(const char *msg, ...);
45
46 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
47
48 #define KEY_TAB         9
49 #define KEY_ESC         27
50 #define KEY_DEL         127
51
52 #define REQ_OFFSET      (MAX_COMMAND + 1)
53
54 /* Requests for switching between the different views. */
55 #define REQ_DIFF        (REQ_OFFSET + 0)
56 #define REQ_LOG         (REQ_OFFSET + 1)
57 #define REQ_MAIN        (REQ_OFFSET + 2)
58 #define REQ_VIEWS       (REQ_OFFSET + 3)
59
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)
67 #define REQ_LINE_NUMBER (REQ_OFFSET + 18)
68
69 #define SIZEOF_VIEWS    (REQ_VIEWS - REQ_OFFSET)
70 #define SIZEOF_ID       1024
71
72 #define COLOR_TRANSP    (-1)
73
74
75 /**
76  * OPTIONS
77  * -------
78  **/
79
80 static int opt_line_number;
81 static int opt_request = REQ_MAIN;
82
83 char head_id[SIZEOF_ID] = "HEAD";
84 char commit_id[SIZEOF_ID] = "HEAD";
85
86
87 /* Returns the index of log or diff command or -1 to exit. */
88 static int
89 parse_options(int argc, char *argv[])
90 {
91         int i;
92
93         for (i = 1; i < argc; i++) {
94                 char *opt = argv[i];
95
96                 /**
97                  * log [options]::
98                  *      git log options.
99                  **/
100                 if (!strcmp(opt, "log")) {
101                         opt_request = REQ_LOG;
102                         return i;
103
104                 /**
105                  * diff [options]::
106                  *      git diff options.
107                  **/
108                 } else if (!strcmp(opt, "diff")) {
109                         opt_request = REQ_DIFF;
110                         return i;
111
112                 /**
113                  * -l::
114                  *      Start up in log view.
115                  **/
116                 } else if (!strcmp(opt, "-l")) {
117                         opt_request = REQ_LOG;
118
119                 /**
120                  * -d::
121                  *      Start up in diff view.
122                  **/
123                 } else if (!strcmp(opt, "-d")) {
124                         opt_request = REQ_DIFF;
125
126                 /**
127                  * -n, --line-number::
128                  *      Prefix line numbers in log and diff view.
129                  **/
130                 } else if (!strcmp(opt, "-n") ||
131                            !strcmp(opt, "--line-number")) {
132                         opt_line_number = 1;
133
134                 /**
135                  * -v, --version::
136                  *      Show version and exit.
137                  **/
138                 } else if (!strcmp(opt, "-v") ||
139                            !strcmp(opt, "--version")) {
140                         printf("tig version %s\n", VERSION);
141                         return -1;
142
143                 /**
144                  * ref::
145                  *      Commit reference, symbolic or raw SHA1 ID.
146                  **/
147                 } else if (opt[0] && opt[0] != '-') {
148                         strncpy(head_id, opt, SIZEOF_ID);
149                         strncpy(commit_id, opt, SIZEOF_ID);
150
151                 } else {
152                         die("Unknown command: '%s'", opt);
153                 }
154         }
155
156         return i;
157 }
158
159
160 /*
161  * Line-oriented content detection.
162  */
163
164 enum line_type {
165         LINE_DEFAULT,
166         LINE_AUTHOR,
167         LINE_AUTHOR_IDENT,
168         LINE_COMMIT,
169         LINE_COMMITTER,
170         LINE_CURSOR,
171         LINE_DATE,
172         LINE_DIFF,
173         LINE_DIFF_ADD,
174         LINE_DIFF_CHUNK,
175         LINE_DIFF_COPY,
176         LINE_DIFF_DEL,
177         LINE_DIFF_DISSIM,
178         LINE_DIFF_NEWMODE,
179         LINE_DIFF_OLDMODE,
180         LINE_DIFF_RENAME,
181         LINE_DIFF_SIM,
182         LINE_DIFF_TREE,
183         LINE_INDEX,
184         LINE_MAIN_AUTHOR,
185         LINE_MAIN_COMMIT,
186         LINE_MAIN_DATE,
187         LINE_MAIN_DELIM,
188         LINE_MERGE,
189         LINE_PARENT,
190         LINE_STATUS,
191         LINE_TITLE,
192         LINE_TREE,
193 };
194
195 struct line_info {
196         enum line_type type;
197         char *line;
198         int linelen;
199
200         int fg;
201         int bg;
202         int attr;
203 };
204
205 #define LINE(type, line, fg, bg, attr) \
206         { LINE_##type, (line), sizeof(line) - 1, (fg), (bg), (attr) }
207
208 static struct line_info line_info[] = {
209         /* Diff markup */
210         LINE(DIFF,         "diff --git ",       COLOR_YELLOW,   COLOR_TRANSP,   0),
211         LINE(INDEX,        "index ",            COLOR_BLUE,     COLOR_TRANSP,   0),
212         LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_TRANSP,   0),
213         LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_TRANSP,   0),
214         LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_TRANSP,   0),
215         LINE(DIFF_OLDMODE, "old mode ",         COLOR_YELLOW,   COLOR_TRANSP,   0),
216         LINE(DIFF_NEWMODE, "new mode ",         COLOR_YELLOW,   COLOR_TRANSP,   0),
217         LINE(DIFF_COPY,    "copy ",             COLOR_YELLOW,   COLOR_TRANSP,   0),
218         LINE(DIFF_RENAME,  "rename ",           COLOR_YELLOW,   COLOR_TRANSP,   0),
219         LINE(DIFF_SIM,     "similarity ",       COLOR_YELLOW,   COLOR_TRANSP,   0),
220         LINE(DIFF_DISSIM,  "dissimilarity ",    COLOR_YELLOW,   COLOR_TRANSP,   0),
221
222         /* Pretty print commit header */
223         LINE(AUTHOR,       "Author: ",          COLOR_CYAN,     COLOR_TRANSP,   0),
224         LINE(MERGE,        "Merge: ",           COLOR_BLUE,     COLOR_TRANSP,   0),
225         LINE(DATE,         "Date:   ",          COLOR_YELLOW,   COLOR_TRANSP,   0),
226
227         /* Raw commit header */
228         LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_TRANSP,   0),
229         LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_TRANSP,   0),
230         LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_TRANSP,   0),
231         LINE(AUTHOR_IDENT, "author ",           COLOR_CYAN,     COLOR_TRANSP,   0),
232         LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_TRANSP,   0),
233
234         LINE(DIFF_TREE,    "diff-tree ",        COLOR_BLUE,     COLOR_TRANSP,   0),
235
236         /* UI colors */
237         LINE(DEFAULT,      "",  COLOR_TRANSP,   COLOR_TRANSP,   A_NORMAL),
238         LINE(CURSOR,       "",  COLOR_WHITE,    COLOR_GREEN,    A_BOLD),
239         LINE(STATUS,       "",  COLOR_GREEN,    COLOR_TRANSP,   0),
240         LINE(TITLE,        "",  COLOR_YELLOW,   COLOR_BLUE,     A_BOLD),
241         LINE(MAIN_DATE,    "",  COLOR_BLUE,     COLOR_TRANSP,   0),
242         LINE(MAIN_AUTHOR,  "",  COLOR_GREEN,    COLOR_TRANSP,   0),
243         LINE(MAIN_COMMIT,  "",  COLOR_TRANSP,   COLOR_TRANSP,   0),
244         LINE(MAIN_DELIM,   "",  COLOR_MAGENTA,  COLOR_TRANSP,   0),
245 };
246
247 static struct line_info *
248 get_line_info(char *line)
249 {
250         int linelen = strlen(line);
251         int i;
252
253         for (i = 0; i < ARRAY_SIZE(line_info); i++) {
254                 if (linelen < line_info[i].linelen
255                     || strncmp(line_info[i].line, line, line_info[i].linelen))
256                         continue;
257
258                 return &line_info[i];
259         }
260
261         return NULL;
262 }
263
264 static enum line_type
265 get_line_type(char *line)
266 {
267         struct line_info *info = get_line_info(line);
268
269         return info ? info->type : LINE_DEFAULT;
270 }
271
272 static int
273 get_line_attr(enum line_type type)
274 {
275         int i;
276
277         for (i = 0; i < ARRAY_SIZE(line_info); i++)
278                 if (line_info[i].type == type)
279                         return COLOR_PAIR(line_info[i].type) | line_info[i].attr;
280
281         return A_NORMAL;
282 }
283
284 static void
285 init_colors(void)
286 {
287         int transparent_bg = COLOR_BLACK;
288         int transparent_fg = COLOR_WHITE;
289         int i;
290
291         start_color();
292
293         if (use_default_colors() != ERR) {
294                 transparent_bg = -1;
295                 transparent_fg = -1;
296         }
297
298         for (i = 0; i < ARRAY_SIZE(line_info); i++) {
299                 struct line_info *info = &line_info[i];
300                 int bg = info->bg == COLOR_TRANSP ? transparent_bg : info->bg;
301                 int fg = info->fg == COLOR_TRANSP ? transparent_fg : info->fg;
302
303                 init_pair(info->type, fg, bg);
304         }
305 }
306
307
308 /**
309  * KEYS
310  * ----
311  *
312  * d::
313  *      diff
314  * l::
315  *      log
316  * q::
317  *      quit
318  * r::
319  *      redraw screen
320  * s::
321  *      stop all background loading
322  * j::
323  *      down
324  * k::
325  *      up
326  * h, ?::
327  *      help
328  * v::
329  *      version
330  *
331  **/
332
333 #define HELP "(d)iff, (l)og, (m)ain, (q)uit, (v)ersion, (h)elp"
334
335 struct keymap {
336         int alias;
337         int request;
338 };
339
340 struct keymap keymap[] = {
341         /* Cursor navigation */
342         { KEY_UP,       REQ_PREV_LINE },
343         { 'k',          REQ_PREV_LINE },
344         { KEY_DOWN,     REQ_NEXT_LINE },
345         { 'j',          REQ_NEXT_LINE },
346         { KEY_HOME,     REQ_FIRST_LINE },
347         { KEY_END,      REQ_LAST_LINE },
348         { KEY_NPAGE,    REQ_NEXT_PAGE },
349         { KEY_PPAGE,    REQ_PREV_PAGE },
350
351         /* Scrolling */
352         { KEY_IC,       REQ_SCR_BLINE }, /* scroll field backward a line */
353         { KEY_DC,       REQ_SCR_FLINE }, /* scroll field forward a line */
354         { 's',          REQ_SCR_FPAGE }, /* scroll field forward a page */
355         { 'w',          REQ_SCR_BPAGE }, /* scroll field backward a page */
356
357         { 'd',          REQ_DIFF },
358         { 'l',          REQ_LOG },
359         { 'm',          REQ_MAIN },
360
361         { 'n',          REQ_LINE_NUMBER },
362
363         /* No input from wgetch() with nodelay() enabled. */
364         { ERR,          REQ_UPDATE },
365
366         { KEY_ESC,      REQ_QUIT },
367         { 'q',          REQ_QUIT },
368         { 's',          REQ_STOP },
369         { 'v',          REQ_VERSION },
370         { 'r',          REQ_REDRAW },
371 };
372
373 static int
374 get_request(int request)
375 {
376         int i;
377
378         for (i = 0; i < ARRAY_SIZE(keymap); i++)
379                 if (keymap[i].alias == request)
380                         return keymap[i].request;
381
382         return request;
383 }
384
385
386 /*
387  * Viewer
388  */
389
390 struct view {
391         char *name;
392         char *cmd;
393         char *id;
394
395
396         /* Rendering */
397         int (*read)(struct view *, char *);
398         int (*draw)(struct view *, unsigned int);
399         size_t objsize;         /* Size of objects in the line index */
400
401         WINDOW *win;
402         int height, width;
403
404         /* Navigation */
405         unsigned long offset;   /* Offset of the window top */
406         unsigned long lineno;   /* Current line number */
407
408         /* Buffering */
409         unsigned long lines;    /* Total number of lines */
410         void **line;            /* Line index */
411
412         /* Loading */
413         FILE *pipe;
414 };
415
416 struct commit {
417         char id[41];
418         char title[75];
419         char author[75];
420         struct tm time;
421 };
422
423 static int pager_draw(struct view *view, unsigned int lineno);
424 static int pager_read(struct view *view, char *line);
425
426 static int main_draw(struct view *view, unsigned int lineno);
427 static int main_read(struct view *view, char *line);
428
429 #define DIFF_CMD \
430         "git log --stat -n1 %s ; echo; " \
431         "git diff --find-copies-harder -B -C %s^ %s"
432
433 #define LOG_CMD \
434         "git log --stat -n100 %s"
435
436 #define MAIN_CMD \
437         "git log --stat --pretty=raw %s"
438
439 /* The status window at the bottom. Used for polling keystrokes. */
440 static WINDOW *status_win;
441
442 static WINDOW *title_win;
443
444 static unsigned int current_view;
445 static unsigned int nloading;
446
447 static struct view views[];
448 static struct view *display[];
449
450 static struct view views[] = {
451         { "diff",  DIFF_CMD,   commit_id,  pager_read,  pager_draw, sizeof(char) },
452         { "log",   LOG_CMD,    head_id,    pager_read,  pager_draw, sizeof(char) },
453         { "main",  MAIN_CMD,   head_id,    main_read,   main_draw,  sizeof(struct commit) },
454 };
455
456 static struct view *display[ARRAY_SIZE(views)];
457
458
459 #define foreach_view(view, i) \
460         for (i = 0; i < sizeof(display) && (view = display[i]); i++)
461
462 static void
463 redraw_view(struct view *view)
464 {
465         int lineno;
466
467         wclear(view->win);
468         wmove(view->win, 0, 0);
469
470         for (lineno = 0; lineno < view->height; lineno++) {
471                 if (!view->draw(view, lineno))
472                         break;
473         }
474
475         redrawwin(view->win);
476         wrefresh(view->win);
477 }
478
479 static void
480 resize_view(struct view *view)
481 {
482         int lines, cols;
483
484         getmaxyx(stdscr, lines, cols);
485
486         if (view->win) {
487                 mvwin(view->win, 0, 0);
488                 wresize(view->win, lines - 2, cols);
489
490         } else {
491                 view->win = newwin(lines - 2, 0, 0, 0);
492                 if (!view->win) {
493                         report("failed to create %s view", view->name);
494                         return;
495                 }
496                 scrollok(view->win, TRUE);
497         }
498
499         getmaxyx(view->win, view->height, view->width);
500 }
501
502 /* FIXME: Fix percentage. */
503 static void
504 report_position(struct view *view, int all)
505 {
506         report(all ? "line %d of %d (%d%%) viewing from %d"
507                      : "line %d of %d",
508                view->lineno + 1,
509                view->lines,
510                view->lines ? view->offset * 100 / view->lines : 0,
511                view->offset);
512 }
513
514
515 static void
516 move_view(struct view *view, int lines)
517 {
518         /* The rendering expects the new offset. */
519         view->offset += lines;
520
521         assert(0 <= view->offset && view->offset < view->lines);
522         assert(lines);
523
524         if (view->height < (lines > 0 ? lines : -lines)) {
525                 redraw_view(view);
526
527         } else {
528                 int line = lines > 0 ? view->height - lines : 0;
529                 int end = line + (lines > 0 ? lines : -lines);
530
531                 wscrl(view->win, lines);
532
533                 for (; line < end; line++) {
534                         if (!view->draw(view, line))
535                                 break;
536                 }
537         }
538
539         /* Move current line into the view. */
540         if (view->lineno < view->offset) {
541                 view->lineno = view->offset;
542                 view->draw(view, 0);
543
544         } else if (view->lineno >= view->offset + view->height) {
545                 view->lineno = view->offset + view->height - 1;
546                 view->draw(view, view->lineno - view->offset);
547         }
548
549         assert(view->offset <= view->lineno && view->lineno <= view->lines);
550
551         redrawwin(view->win);
552         wrefresh(view->win);
553
554         report_position(view, lines);
555 }
556
557 static void
558 scroll_view(struct view *view, int request)
559 {
560         int lines = 1;
561
562         switch (request) {
563         case REQ_SCR_FPAGE:
564                 lines = view->height;
565         case REQ_SCR_FLINE:
566                 if (view->offset + lines > view->lines)
567                         lines = view->lines - view->offset;
568
569                 if (lines == 0 || view->offset + view->height >= view->lines) {
570                         report("already at last line");
571                         return;
572                 }
573                 break;
574
575         case REQ_SCR_BPAGE:
576                 lines = view->height;
577         case REQ_SCR_BLINE:
578                 if (lines > view->offset)
579                         lines = view->offset;
580
581                 if (lines == 0) {
582                         report("already at first line");
583                         return;
584                 }
585
586                 lines = -lines;
587                 break;
588         }
589
590         move_view(view, lines);
591 }
592
593 static void
594 navigate_view(struct view *view, int request)
595 {
596         int steps;
597
598         switch (request) {
599         case REQ_FIRST_LINE:
600                 steps = -view->lineno;
601                 break;
602
603         case REQ_LAST_LINE:
604                 steps = view->lines - view->lineno - 1;
605                 break;
606
607         case REQ_PREV_PAGE:
608                 steps = view->height > view->lineno
609                       ? -view->lineno : -view->height;
610                 break;
611
612         case REQ_NEXT_PAGE:
613                 steps = view->lineno + view->height >= view->lines
614                       ? view->lines - view->lineno - 1 : view->height;
615                 break;
616
617         case REQ_PREV_LINE:
618                 steps = -1;
619                 break;
620
621         case REQ_NEXT_LINE:
622                 steps = 1;
623                 break;
624         }
625
626         if (steps < 0 && view->lineno == 0) {
627                 report("already at first line");
628                 return;
629
630         } else if (steps > 0 && view->lineno + 1 == view->lines) {
631                 report("already at last line");
632                 return;
633         }
634
635         view->lineno += steps;
636         view->draw(view, view->lineno - steps - view->offset);
637
638         if (view->lineno < view->offset ||
639             view->lineno >= view->offset + view->height) {
640                 if (steps < 0 && -steps > view->offset) {
641                         steps = -view->offset;
642
643                 } else if (steps > 0) {
644                         if (view->lineno == view->lines - 1 &&
645                             view->lines > view->height) {
646                                 steps = view->lines - view->offset - 1;
647                                 if (steps >= view->height)
648                                         steps -= view->height - 1;
649                         }
650                 }
651
652                 move_view(view, steps);
653                 return;
654         }
655
656         /* Draw the cursor line */
657         view->draw(view, view->lineno - view->offset);
658
659         redrawwin(view->win);
660         wrefresh(view->win);
661
662         report_position(view, view->height);
663 }
664
665
666
667 static bool
668 begin_update(struct view *view)
669 {
670         char buf[1024];
671
672         if (view->cmd) {
673                 char *id = view->id;
674
675                 if (snprintf(buf, sizeof(buf), view->cmd, id, id, id) < sizeof(buf))
676                         view->pipe = popen(buf, "r");
677
678                 if (!view->pipe)
679                         return FALSE;
680
681                 if (nloading++ == 0)
682                         nodelay(status_win, TRUE);
683         }
684
685         display[current_view] = view;
686
687         view->offset = 0;
688         view->lines  = 0;
689         view->lineno = 0;
690
691         return TRUE;
692 }
693
694 static void
695 end_update(struct view *view)
696 {
697         wattrset(view->win, A_NORMAL);
698         pclose(view->pipe);
699         view->pipe = NULL;
700
701         if (nloading-- == 1)
702                 nodelay(status_win, FALSE);
703 }
704
705 static int
706 update_view(struct view *view)
707 {
708         char buffer[BUFSIZ];
709         char *line;
710         void **tmp;
711         int redraw;
712         int lines = view->height;
713
714         if (!view->pipe)
715                 return TRUE;
716
717         /* Only redraw after the first reading session. */
718         /* FIXME: ... and possibly more. */
719         redraw = view->height > view->lines;
720
721         tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
722         if (!tmp)
723                 goto alloc_error;
724
725         view->line = tmp;
726
727         while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
728                 int linelen;
729
730                 linelen = strlen(line);
731                 if (linelen)
732                         line[linelen - 1] = 0;
733
734                 if (!view->read(view, line))
735                         goto alloc_error;
736
737                 if (lines-- == 1)
738                         break;
739         }
740
741         if (redraw)
742                 redraw_view(view);
743
744         if (ferror(view->pipe)) {
745                 report("failed to read %s", view->cmd);
746                 goto end;
747
748         } else if (feof(view->pipe)) {
749                 report_position(view, 0);
750                 goto end;
751         }
752
753         return TRUE;
754
755 alloc_error:
756         report("allocation failure");
757
758 end:
759         end_update(view);
760         return FALSE;
761 }
762
763
764 static struct view *
765 switch_view(struct view *prev, int request)
766 {
767         struct view *view = &views[request - REQ_OFFSET];
768         struct view *displayed;
769         int i;
770
771         if (view == prev) {
772                 foreach_view (displayed, i) ;
773
774                 if (i == 1)
775                         report("already in %s view", view->name);
776                 else
777                         report("FIXME: Maximize");
778
779                 return view;
780
781         } else {
782                 foreach_view (displayed, i) {
783                         if (view == displayed) {
784                                 current_view = i;
785                                 report("new current view");
786                                 return view;
787                         }
788                 }
789         }
790
791         if (!view->win)
792                 resize_view(view);
793
794         /* Reload */
795
796         if (view->line) {
797                 for (i = 0; i < view->lines; i++)
798                         if (view->line[i])
799                                 free(view->line[i]);
800
801                 free(view->line);
802                 view->line = NULL;
803         }
804
805         if (prev && prev->pipe)
806                 end_update(prev);
807
808         if (begin_update(view)) {
809                 if (!view->cmd)
810                         report("%s", HELP);
811                 else
812                         report("loading...");
813         }
814
815         return view;
816 }
817
818
819 /* Process a keystroke */
820 static int
821 view_driver(struct view *view, int key)
822 {
823         int request = get_request(key);
824         int i;
825
826         switch (request) {
827         case REQ_NEXT_LINE:
828         case REQ_PREV_LINE:
829         case REQ_FIRST_LINE:
830         case REQ_LAST_LINE:
831         case REQ_NEXT_PAGE:
832         case REQ_PREV_PAGE:
833                 if (view)
834                         navigate_view(view, request);
835                 break;
836
837         case REQ_SCR_FLINE:
838         case REQ_SCR_BLINE:
839         case REQ_SCR_FPAGE:
840         case REQ_SCR_BPAGE:
841                 if (view)
842                         scroll_view(view, request);
843                 break;
844
845         case REQ_MAIN:
846         case REQ_LOG:
847         case REQ_DIFF:
848                 view = switch_view(view, request);
849                 break;
850
851         case REQ_LINE_NUMBER:
852                 opt_line_number = !opt_line_number;
853                 redraw_view(view);
854                 break;
855
856         case REQ_REDRAW:
857                 redraw_view(view);
858                 break;
859
860         case REQ_STOP:
861                 foreach_view (view, i) {
862                         if (view->pipe) {
863                                 end_update(view);
864                                 scroll_view(view, 0);
865                         }
866                 }
867                 break;
868
869         case REQ_VERSION:
870                 report("version %s", VERSION);
871                 return TRUE;
872
873         case REQ_UPDATE:
874                 doupdate();
875                 return TRUE;
876
877         case REQ_QUIT:
878                 return FALSE;
879
880         default:
881                 report(HELP);
882                 return TRUE;
883         }
884
885         return TRUE;
886 }
887
888
889 /*
890  * Rendering
891  */
892
893 static int
894 pager_draw(struct view *view, unsigned int lineno)
895 {
896         enum line_type type;
897         char *line;
898         int attr;
899
900         if (view->offset + lineno >= view->lines)
901                 return FALSE;
902
903         line = view->line[view->offset + lineno];
904         type = get_line_type(line);
905
906         if (view->offset + lineno == view->lineno) {
907                 if (type == LINE_COMMIT)
908                         strncpy(commit_id, line + 7, SIZEOF_ID);
909                 type = LINE_CURSOR;
910         }
911
912         attr = get_line_attr(type);
913         wattrset(view->win, attr);
914
915         if (opt_line_number) {
916                 mvwprintw(view->win, lineno, 0, "%4d: ", view->offset + lineno + 1);
917                 while (line) {
918                         if (*line == '\t') {
919                                 waddstr(view->win, "        ");
920                                 line++;
921                         } else {
922                                 char *tab = strchr(line, '\t');
923
924                                 if (tab)
925                                         waddnstr(view->win, line, tab - line);
926                                 else
927                                         waddstr(view->win, line);
928                                 line = tab;
929                         }
930                 }
931                 waddstr(view->win, line);
932
933         } else {
934                 /* No empty lines makes cursor drawing and clearing implicit. */
935                 if (!*line)
936                         line = " ";
937                 mvwaddstr(view->win, lineno, 0, line);
938         }
939
940         return TRUE;
941 }
942
943 static int
944 pager_read(struct view *view, char *line)
945 {
946         view->line[view->lines] = strdup(line);
947         if (!view->line[view->lines])
948                 return FALSE;
949
950         view->lines++;
951         return TRUE;
952 }
953
954 static int
955 main_draw(struct view *view, unsigned int lineno)
956 {
957         char buf[21];
958         struct commit *commit;
959         enum line_type type;
960         int cols = 0;
961         size_t timelen;
962
963         if (view->offset + lineno >= view->lines)
964                 return FALSE;
965
966         commit = view->line[view->offset + lineno];
967         if (!commit) return FALSE;
968
969         if (view->offset + lineno == view->lineno) {
970                 strncpy(commit_id, commit->id, SIZEOF_ID);
971                 type = LINE_CURSOR;
972         } else {
973                 type = LINE_MAIN_COMMIT;
974         }
975
976         wmove(view->win, lineno, cols);
977         wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
978
979         timelen = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S ", &commit->time);
980         waddnstr(view->win, buf, timelen);
981
982         cols += 20;
983         wmove(view->win, lineno, cols);
984         wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
985
986         if (strlen(commit->author) > 19) {
987                 waddnstr(view->win, commit->author, 18);
988                 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
989                 waddch(view->win, '~');
990         } else {
991                 waddstr(view->win, commit->author);
992         }
993
994         cols += 20;
995         wattrset(view->win, A_NORMAL);
996         mvwaddch(view->win, lineno, cols, ACS_LTEE);
997         wattrset(view->win, get_line_attr(type));
998         mvwaddstr(view->win, lineno, cols + 2, commit->title);
999         wattrset(view->win, A_NORMAL);
1000
1001         return TRUE;
1002 }
1003
1004 static int
1005 main_read(struct view *view, char *line)
1006 {
1007         enum line_type type = get_line_type(line);
1008         struct commit *commit;
1009
1010         switch (type) {
1011         case LINE_COMMIT:
1012                 commit = calloc(1, sizeof(struct commit));
1013                 if (!commit)
1014                         return FALSE;
1015
1016                 line += sizeof("commit ") - 1;
1017
1018                 view->line[view->lines++] = commit;
1019                 strncpy(commit->id, line, sizeof(commit->id));
1020                 break;
1021
1022         case LINE_AUTHOR_IDENT:
1023         {
1024                 char *ident = line + sizeof("author ") - 1;
1025                 char *end = strchr(ident, '<');
1026
1027                 if (end) {
1028                         for (; end > ident && isspace(end[-1]); end--) ;
1029                         *end = 0;
1030                 }
1031
1032                 commit = view->line[view->lines - 1];
1033                 strncpy(commit->author, ident, sizeof(commit->author));
1034
1035                 if (end) {
1036                         char *secs = strchr(end + 1, '>');
1037                         char *zone;
1038                         time_t time;
1039
1040                         if (!secs || secs[1] != ' ')
1041                                 break;
1042
1043                         secs += 2;
1044                         time = (time_t) atol(secs);
1045                         zone = strchr(secs, ' ');
1046                         if (zone && strlen(zone) == sizeof(" +0700") - 1) {
1047                                 long tz;
1048
1049                                 zone++;
1050                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
1051                                 tz += ('0' - zone[2]) * 60 * 60;
1052                                 tz += ('0' - zone[3]) * 60;
1053                                 tz += ('0' - zone[4]) * 60;
1054
1055                                 if (zone[0] == '-')
1056                                         tz = -tz;
1057
1058                                 time -= tz;
1059                         }
1060                         gmtime_r(&time, &commit->time);
1061                 }
1062                 break;
1063         }
1064         default:
1065                 commit = view->line[view->lines - 1];
1066                 if (!commit->title[0] &&
1067                     !strncmp(line, "    ", 4) &&
1068                     !isspace(line[5]))
1069                         strncpy(commit->title, line + 4, sizeof(commit->title));
1070         }
1071
1072         return TRUE;
1073 }
1074
1075
1076 /*
1077  * Main
1078  */
1079
1080 static void
1081 quit(int sig)
1082 {
1083         if (status_win)
1084                 delwin(status_win);
1085         if (title_win)
1086                 delwin(title_win);
1087         endwin();
1088
1089         /* FIXME: Shutdown gracefully. */
1090
1091         exit(0);
1092 }
1093
1094 static void die(const char *err, ...)
1095 {
1096         va_list args;
1097
1098         endwin();
1099
1100         va_start(args, err);
1101         fputs("tig: ", stderr);
1102         vfprintf(stderr, err, args);
1103         fputs("\n", stderr);
1104         va_end(args);
1105
1106         exit(1);
1107 }
1108
1109 static void
1110 report(const char *msg, ...)
1111 {
1112         va_list args;
1113
1114         werase(title_win);
1115         wmove(title_win, 0, 0);
1116         wprintw(title_win, "commit %s", commit_id);
1117         wrefresh(title_win);
1118
1119         va_start(args, msg);
1120
1121         werase(status_win);
1122         wmove(status_win, 0, 0);
1123
1124 #if 0
1125         if (display[current_view])
1126                 wprintw(status_win, "%s %4s: ", commit_id, display[current_view]->name);
1127 #endif
1128         vwprintw(status_win, msg, args);
1129         wrefresh(status_win);
1130
1131         va_end(args);
1132 }
1133
1134 int
1135 main(int argc, char *argv[])
1136 {
1137         int x, y;
1138         int request;
1139         int git_cmd;
1140
1141         signal(SIGINT, quit);
1142
1143         git_cmd = parse_options(argc, argv);
1144         if (git_cmd < 0)
1145                 return 0;
1146
1147         request = opt_request;
1148
1149         initscr();      /* initialize the curses library */
1150         nonl();         /* tell curses not to do NL->CR/NL on output */
1151         cbreak();       /* take input chars one at a time, no wait for \n */
1152         noecho();       /* don't echo input */
1153         leaveok(stdscr, TRUE);
1154         /* curs_set(0); */
1155
1156         if (has_colors())
1157                 init_colors();
1158
1159         getmaxyx(stdscr, y, x);
1160         status_win = newwin(1, 0, y - 1, 0);
1161         if (!status_win)
1162                 die("Failed to create status window");
1163
1164         title_win = newwin(1, 0, y - 2, 0);
1165         if (!title_win)
1166                 die("Failed to create title window");
1167
1168         /* Enable keyboard mapping */
1169         keypad(status_win, TRUE);
1170         wbkgdset(status_win, get_line_attr(LINE_STATUS));
1171         wbkgdset(title_win, get_line_attr(LINE_TITLE));
1172
1173         while (view_driver(display[current_view], request)) {
1174                 struct view *view;
1175                 int i;
1176
1177                 foreach_view (view, i) {
1178                         if (view->pipe) {
1179                                 update_view(view);
1180                         }
1181                 }
1182
1183                 /* Refresh, accept single keystroke of input */
1184                 request = wgetch(status_win);
1185                 if (request == KEY_RESIZE) {
1186                         int lines, cols;
1187
1188                         getmaxyx(stdscr, lines, cols);
1189
1190                         mvwin(status_win, lines - 1, 0);
1191                         wresize(status_win, 1, cols - 1);
1192
1193                         mvwin(title_win, lines - 2, 0);
1194                         wresize(title_win, 1, cols - 1);
1195                 }
1196         }
1197
1198         quit(0);
1199
1200         return 0;
1201 }
1202
1203 /**
1204  * COPYRIGHT
1205  * ---------
1206  * Copyright (c) Jonas Fonseca, 2006
1207  *
1208  * This program is free software; you can redistribute it and/or modify
1209  * it under the terms of the GNU General Public License as published by
1210  * the Free Software Foundation; either version 2 of the License, or
1211  * (at your option) any later version.
1212  *
1213  * SEE ALSO
1214  * --------
1215  * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
1216  * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
1217  **/