chiark / gitweb /
The pager is begining to work. :)
authorJonas Fonseca <fonseca@diku.dk>
Wed, 26 Apr 2006 23:27:53 +0000 (01:27 +0200)
committerJonas Fonseca <fonseca@antimatter.localdomain>
Wed, 26 Apr 2006 23:27:53 +0000 (01:27 +0200)
Makefile
cgit.c [deleted file]
tig.c [new file with mode: 0644]

index efeca0d36e948b5f8bb57a95447204c66c046734..38728516b42deff51de25b9405b7c18c8056deac 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,8 @@
 LDFLAGS = -lcurses
-CFLAGS = -g
-PROGS  = cgit
-DOCS   = cgit.1.txt cgit.1 cgit.1.html
+CFLAGS = -g '-DVERSION="$(VERSION)"' -Wall
+PROGS  = tig
+DOCS   = tig.1.txt tig.1 tig.1.html
+VERSION        = $(shell git-describe)
 
 all: $(PROGS)
 docs: $(DOCS)
@@ -14,12 +15,14 @@ install: all
 clean:
        rm -f $(PROGS) $(DOCS)
 
-cgit: cgit.c
+.PHONY: all docs install clean
 
-cgit.1.txt: cgit.c
-       sed -n '/\*\*/,/\*\*/p' < $< | \
-       sed '/\*\*/d' | \
-       sed -n 's/^ \* *//p' > $@
+tig: tig.c
+
+tig.1.txt: tig.c
+       sed -n '/^\/\*\*/,/\*\*\//p' < $< | \
+       sed '/^[^*]\*\*/d' | \
+       sed 's/\*\///;s/^[^*]*\* *//' > $@
 
 %.1.html : %.1.txt
        asciidoc -b xhtml11 -d manpage -f asciidoc.conf $<
diff --git a/cgit.c b/cgit.c
deleted file mode 100644 (file)
index 20d58fa..0000000
--- a/cgit.c
+++ /dev/null
@@ -1,401 +0,0 @@
-/**
- * gitzilla(1)
- * ===========
- *
- * NAME
- * ----
- * gitzilla - cursed git browser
- *
- * SYNOPSIS
- * --------
- * gitzilla
- *
- * DESCRIPTION
- * -----------
- *
- * a
- *
- **/
-
-#include <stdarg.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
-#include <signal.h>
-
-#include <curses.h>
-
-/**
- * OPTIONS
- * -------
- *
- * None
- *
- **/
-
-/**
- * KEYS
- * ----
- *
- * q:: quit
- * s:: shell
- * j:: down
- * k:: up
- *
- **/
-
-#define MSG_HELP "(q)uit, (s)hell, (j) down, (k) up"
-
-#define KEY_ESC        27
-#define KEY_TAB        9
-
-struct view {
-       WINDOW *win;
-
-       char *cmd;
-       void (*reader)(char *, int);
-       FILE *pipe;
-
-       unsigned long lines;
-       unsigned long lineno;
-};
-
-static struct view main_view;
-static struct view diff_view;
-static struct view log_view;
-static struct view status_view;
-
-int do_resize = 1;
-
-static void
-put_status(char *msg, ...)
-{
-       va_list args;
-
-       va_start(args, msg);
-       werase(status_view.win);
-       wmove(status_view.win, 0, 0);
-       vwprintw(status_view.win, msg, args);
-       wrefresh(status_view.win);
-       va_end(args);
-}
-
-static void
-resize_views(void)
-{
-       int x, y;
-
-       getmaxyx(stdscr, y, x);
-
-       if (status_view.win)
-               delwin(status_view.win);
-       status_view.win = newwin(1, 0, y - 1, 0);
-
-       wattrset(status_view.win, COLOR_PAIR(COLOR_GREEN));
-       put_status(MSG_HELP);
-
-       if (main_view.win)
-               delwin(main_view.win);
-       main_view.win = newwin(y - 1, 0, 0, 0);
-
-       scrollok(main_view.win, TRUE);
-       keypad(main_view.win, TRUE);  /* enable keyboard mapping */
-       put_status("%d %d", y, x);
-}
-
-/*
- * Init and quit
- */
-
-static void
-quit(int sig)
-{
-       endwin();
-
-       /* do your non-curses wrapup here */
-
-       exit(0);
-}
-
-static void
-init_colors(void)
-{
-       int bg = COLOR_BLACK;
-
-       start_color();
-
-       if (use_default_colors() != ERR)
-               bg = -1;
-
-       init_pair(COLOR_BLACK,   COLOR_BLACK,   bg);
-       init_pair(COLOR_GREEN,   COLOR_GREEN,   bg);
-       init_pair(COLOR_RED,     COLOR_RED,     bg);
-       init_pair(COLOR_CYAN,    COLOR_CYAN,    bg);
-       init_pair(COLOR_WHITE,   COLOR_WHITE,   bg);
-       init_pair(COLOR_MAGENTA, COLOR_MAGENTA, bg);
-       init_pair(COLOR_BLUE,    COLOR_BLUE,    bg);
-       init_pair(COLOR_YELLOW,  COLOR_YELLOW,  bg);
-}
-
-static void
-init(void)
-{
-       signal(SIGINT, quit);
-
-       initscr();      /* initialize the curses library */
-       nonl();         /* tell curses not to do NL->CR/NL on output */
-       cbreak();       /* take input chars one at a time, no wait for \n */
-       noecho();       /* don't echo input */
-       leaveok(stdscr, TRUE);
-       /* curs_set(0); */
-
-       if (has_colors())
-               init_colors();
-}
-
-/*
- * Pipe readers
- */
-
-#define DIFF_CMD       \
-       "git log --stat -n1 HEAD ; echo; " \
-       "git diff --find-copies-harder -B -C HEAD^ HEAD"
-
-#define LOG_CMD        \
-       "git log --stat -n100"
-
-static void
-log_reader(char *line, int lineno)
-{
-       static int log_reader_skip;
-
-       if (!line) {
-               wattrset(main_view.win, A_NORMAL);
-               log_reader_skip = 0;
-               return;
-       }
-
-       if (!strncmp("commit ", line, 7)) {
-               wattrset(main_view.win, COLOR_PAIR(COLOR_GREEN));
-
-       } else if (!strncmp("Author: ", line, 8)) {
-               wattrset(main_view.win, COLOR_PAIR(COLOR_CYAN));
-
-       } else if (!strncmp("Date:   ", line, 8)) {
-               wattrset(main_view.win, COLOR_PAIR(COLOR_YELLOW));
-
-       } else if (!strncmp("diff --git ", line, 11)) {
-               wattrset(main_view.win, COLOR_PAIR(COLOR_YELLOW));
-
-       } else if (!strncmp("diff-tree ", line, 10)) {
-               wattrset(main_view.win, COLOR_PAIR(COLOR_BLUE));
-
-       } else if (!strncmp("index ", line, 6)) {
-               wattrset(main_view.win, COLOR_PAIR(COLOR_BLUE));
-
-       } else if (line[0] == '-') {
-               wattrset(main_view.win, COLOR_PAIR(COLOR_RED));
-
-       } else if (line[0] == '+') {
-               wattrset(main_view.win, COLOR_PAIR(COLOR_GREEN));
-
-       } else if (line[0] == '@') {
-               wattrset(main_view.win, COLOR_PAIR(COLOR_MAGENTA));
-
-       } else if (line[0] == ':') {
-               main_view.lines--;
-               log_reader_skip = 1;
-               return;
-
-       } else if (log_reader_skip) {
-               main_view.lines--;
-               log_reader_skip = 0;
-               return;
-
-       } else {
-               wattrset(main_view.win, A_NORMAL);
-       }
-
-       mvwaddstr(main_view.win, lineno, 0, line);
-}
-
-static struct view *
-update_view(struct view *view, char *cmd)
-{
-       view->cmd       = cmd;
-       view->pipe      = popen(cmd, "r");
-       view->lines     = 0;
-       view->lineno    = 0;
-       view->reader    = log_reader;
-
-       wclear(view->win);
-       wmove(view->win, 0, 0);
-
-       put_status("Loading...");
-
-       return view;
-}
-
-static struct view *
-read_pipe(struct view *view, int lines)
-{
-       char buffer[BUFSIZ];
-       char *line;
-       int x, y;
-
-       while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
-               int linelen;
-
-               if (!--lines)
-                       break;
-
-               linelen = strlen(line);
-               if (linelen)
-                       line[linelen - 1] = 0;
-
-               view->reader(line, view->lines++);
-       }
-
-       if (ferror(view->pipe)) {
-               put_status("Failed to read %s", view->cmd, view->lines - 1);
-
-       } else if (feof(view->pipe)) {
-               put_status("%s (lines %d)", MSG_HELP, view->lines - 1);
-
-       } else {
-               return view;
-       }
-
-       view->reader(NULL, view->lines - 1);
-       pclose(view->pipe);
-       view->pipe = NULL;
-       view->reader = NULL;
-}
-
-/*
- * Main
- */
-
-int
-main(int argc, char *argv[])
-{
-       static struct view *loading_view;
-
-       init();
-
-       //pipe = open_pipe(LOG_CMD, log_reader);
-
-       for (;;) {
-               int c;
-
-               if (do_resize) {
-                       resize_views();
-                       do_resize = 0;
-               }
-
-               if (loading_view && (loading_view = read_pipe(loading_view, 20)))
-                       nodelay(loading_view->win, TRUE);
-
-               c = wgetch(main_view.win);     /* refresh, accept single keystroke of input */
-
-               if (loading_view)
-                       nodelay(loading_view->win, FALSE);
-
-               /* No input from wgetch() with nodelay() enabled. */
-               if (c == ERR) {
-                       doupdate();
-                       continue;
-               }
-
-               /* Process the command keystroke */
-               switch (c) {
-               case KEY_RESIZE:
-                       fprintf(stderr, "resize");
-                       exit;
-                       break;
-
-               case KEY_ESC:
-               case 'q':
-                       quit(0);
-                       main_view.lineno--;
-                       return 0;
-
-               case KEY_DOWN:
-               case 'j':
-               {
-                       int x, y;
-
-                       getmaxyx(main_view.win, y, x);
-                       if (main_view.lineno + y < main_view.lines) {
-                               wscrl(main_view.win, 1);
-                               main_view.lineno++;
-                               put_status("line %d out of %d (%d%%)",
-                                          main_view.lineno,
-                                          main_view.lines,
-                                          100 * main_view.lineno / main_view.lines);
-                       } else {
-                               put_status("last line reached");
-                       }
-                       break;
-               }
-               case KEY_UP:
-               case 'k':
-                       if (main_view.lineno > 1) {
-                               wscrl(main_view.win, -1);
-                               main_view.lineno--;
-                               put_status("line %d out of %d (%d%%)",
-                                          main_view.lineno,
-                                          main_view.lines,
-                                          100 * main_view.lineno / main_view.lines);
-                       } else {
-                               put_status("first line reached");
-                       }
-                       break;
-
-               case 'c':
-                       wclear(main_view.win);
-                       break;
-
-               case 'd':
-                       loading_view = update_view(&main_view, DIFF_CMD);
-                       break;
-
-               case 'l':
-                       loading_view = update_view(&main_view, LOG_CMD);
-                       break;
-
-               case 's':
-                       mvwaddstr(status_view.win, 0, 0, "Shelling out...");
-                       def_prog_mode();           /* save current tty modes */
-                       endwin();                  /* restore original tty modes */
-                       system("sh");              /* run shell */
-
-                       werase(status_view.win);
-                       mvwaddstr(status_view.win, 0, 0, MSG_HELP);
-                       reset_prog_mode();
-                       break;
-               }
-
-               redrawwin(main_view.win);
-               wrefresh(main_view.win);
-       }
-
-       quit(0);
-}
-
-/**
- * COPYRIGHT
- * ---------
- * Copyright (c) Jonas Fonseca, 2006
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * SEE ALSO
- * --------
- * gitlink:cogito[7],
- * gitlink:git[7]
- **/
diff --git a/tig.c b/tig.c
new file mode 100644 (file)
index 0000000..5f0a9e1
--- /dev/null
+++ b/tig.c
@@ -0,0 +1,703 @@
+/**
+ * TIG(1)
+ * ======
+ *
+ * NAME
+ * ----
+ * tig - text-mode interface for git
+ *
+ * SYNOPSIS
+ * --------
+ * [verse]
+ * tig
+ * tig log  [git log options]
+ * tig diff [git diff options]
+ * tig < [git log or git diff output]
+ *
+ * DESCRIPTION
+ * -----------
+ * Browse changes in a git repository.
+ *
+ * OPTIONS
+ * -------
+ *
+ * None.
+ *
+ **/
+
+#define DEBUG
+
+#ifndef DEBUG
+#define NDEBUG
+#endif
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <assert.h>
+
+#include <curses.h>
+#include <form.h>
+
+static void die(const char *err, ...);
+static void report(const char *msg, ...);
+
+#define ARRAY_SIZE(x)  (sizeof(x) / sizeof(x[0]))
+
+#define KEY_ESC                27
+#define KEY_TAB                9
+
+#define REQ_OFFSET     (MAX_COMMAND + 1)
+
+/* Requests for switching between the different views. */
+#define REQ_DIFF       (REQ_OFFSET + 0)
+#define REQ_LOG                (REQ_OFFSET + 1)
+#define REQ_MAIN       (REQ_OFFSET + 2)
+
+#define REQ_QUIT       (REQ_OFFSET + 11)
+#define REQ_VERSION    (REQ_OFFSET + 12)
+#define REQ_STOP       (REQ_OFFSET + 13)
+#define REQ_UPDATE     (REQ_OFFSET + 14)
+#define REQ_REDRAW     (REQ_OFFSET + 15)
+
+
+/**
+ * KEYS
+ * ----
+ *
+ * d::
+ *     diff
+ * l::
+ *     log
+ * q::
+ *     quit
+ * r::
+ *     redraw screen
+ * s::
+ *     stop all background loading
+ * j::
+ *     down
+ * k::
+ *     up
+ * h, ?::
+ *     help
+ * v::
+ *     version
+ *
+ **/
+
+#define HELP "(d)iff, (l)og, (m)ain, (q)uit, (v)ersion, (h)elp"
+
+struct keymap {
+       int alias;
+       int request;
+};
+
+struct keymap keymap[] = {
+       { KEY_UP,       REQ_PREV_LINE },
+       { 'k',          REQ_PREV_LINE },
+       { KEY_DOWN,     REQ_NEXT_LINE },
+       { 'j',          REQ_NEXT_LINE },
+       { KEY_NPAGE,    REQ_NEXT_PAGE },
+       { KEY_PPAGE,    REQ_PREV_PAGE },
+
+       { 'd',          REQ_DIFF },
+       { 'l',          REQ_LOG },
+       { 'm',          REQ_MAIN },
+
+       /* No input from wgetch() with nodelay() enabled. */
+       { ERR,          REQ_UPDATE },
+
+       { KEY_ESC,      REQ_QUIT },
+       { 'q',          REQ_QUIT },
+       { 's',          REQ_STOP },
+       { 'v',          REQ_VERSION },
+       { 'r',          REQ_REDRAW },
+};
+
+static int
+get_request(int request)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(keymap); i++)
+               if (keymap[i].alias == request)
+                       return keymap[i].request;
+
+       return request;
+}
+
+
+/*
+ * Viewer
+ */
+
+struct view {
+       char *name;
+       char *cmd;
+
+       /* Rendering */
+       int (*render)(struct view *, int);
+       WINDOW *win;
+
+       /* Navigation */
+       unsigned long offset;   /* Offset of the window top */
+       unsigned long lineno;   /* Current line number */
+
+       /* Buffering */
+       unsigned long lines;    /* Total number of lines */
+       char **line;            /* Line index */
+
+       /* Loading */
+       FILE *pipe;
+};
+
+static int default_renderer(struct view *view, int lineno);
+
+#define DIFF_CMD       \
+       "git log --stat -n1 %s ; echo; " \
+       "git diff --find-copies-harder -B -C %s^ %s"
+
+#define LOG_CMD        \
+       "git log --stat -n100 %s"
+
+/* The status window at the bottom. Used for polling keystrokes. */
+static WINDOW *status_win;
+
+static struct view views[] = {
+       { "diff", DIFF_CMD, default_renderer },
+       { "log",  LOG_CMD,  default_renderer },
+       { "main", NULL },
+};
+
+static struct view *display[ARRAY_SIZE(views)];
+static unsigned int current_view;
+static unsigned int nloading;
+
+#define foreach_view(view, i) \
+       for (i = 0; i < sizeof(display) && (view = display[i]); i++)
+
+static void
+redraw_view(struct view *view)
+{
+       int lineno;
+       int lines, cols;
+
+       wclear(view->win);
+       wmove(view->win, 0, 0);
+
+       getmaxyx(view->win, lines, cols);
+
+       for (lineno = 0; lineno < lines; lineno++) {
+               view->render(view, lineno);
+       }
+
+       redrawwin(view->win);
+       wrefresh(view->win);
+}
+
+/* FIXME: Fix percentage. */
+static void
+report_position(struct view *view, int all)
+{
+       report(all ? "line %d of %d (%d%%) viewing from %d"
+                    : "line %d of %d",
+              view->lineno + 1,
+              view->lines,
+              view->lines ? view->offset * 100 / view->lines : 0,
+              view->offset);
+}
+
+static void
+scroll_view(struct view *view, int request)
+{
+       int x, y, lines = 1;
+       enum { BACKWARD = -1,  FORWARD = 1 } direction = FORWARD;
+
+       getmaxyx(view->win, y, x);
+
+       switch (request) {
+       case REQ_NEXT_PAGE:
+               lines = y;
+       case REQ_NEXT_LINE:
+               if (view->offset + lines > view->lines)
+                       lines = view->lines - view->offset - 1;
+
+               if (lines == 0 || view->offset + y >= view->lines) {
+                       report("already at last line");
+                       return;
+               }
+               break;
+
+       case REQ_PREV_PAGE:
+               lines = y;
+       case REQ_PREV_LINE:
+               if (lines > view->offset)
+                       lines = view->offset;
+
+               if (lines == 0) {
+                       report("already at first line");
+                       return;
+               }
+
+               direction = BACKWARD;
+               break;
+
+       default:
+               lines = 0;
+       }
+
+       report("off=%d lines=%d lineno=%d move=%d", view->offset, view->lines, view->lineno, lines * direction);
+
+       /* The rendering expects the new offset. */
+       view->offset += lines * direction;
+
+       /* Move current line into the view. */
+       if (view->lineno < view->offset)
+               view->lineno = view->offset;
+       if (view->lineno > view->offset + y)
+               view->lineno = view->offset + y;
+
+       assert(0 <= view->offset && view->offset < view->lines);
+       //assert(0 <= view->offset + lines && view->offset + lines < view->lines);
+       assert(view->offset <= view->lineno && view->lineno <= view->lines);
+
+       if (lines) {
+               int from = direction == FORWARD ? y - lines : 0;
+               int to   = from + lines;
+
+               wscrl(view->win, lines * direction);
+
+               for (; from < to; from++) {
+                       if (!view->render(view, from))
+                               break;
+               }
+       }
+
+       redrawwin(view->win);
+       wrefresh(view->win);
+
+       report_position(view, lines);
+}
+
+static void
+resize_view(struct view *view)
+{
+       int lines, cols;
+
+       getmaxyx(stdscr, lines, cols);
+
+       if (view->win) {
+               mvwin(view->win, 0, 0);
+               wresize(view->win, lines - 1, cols);
+
+       } else {
+               view->win = newwin(lines - 1, 0, 0, 0);
+               if (!view->win) {
+                       report("Failed to create %s view", view->name);
+                       return;
+               }
+               scrollok(view->win, TRUE);
+       }
+}
+
+
+static bool
+begin_update(struct view *view)
+{
+       char buf[1024];
+
+       if (view->cmd) {
+               if (snprintf(buf, sizeof(buf), view->cmd, "HEAD", "HEAD", "HEAD") < sizeof(buf))
+                       view->pipe = popen(buf, "r");
+
+               if (!view->pipe)
+                       return FALSE;
+
+               if (nloading++ == 0)
+                       nodelay(status_win, TRUE);
+       }
+
+       display[current_view] = view;
+
+       view->offset = 0;
+       view->lines  = 0;
+       view->lineno = 0;
+
+       return TRUE;
+}
+
+static void
+end_update(struct view *view)
+{
+       wattrset(view->win, A_NORMAL);
+       pclose(view->pipe);
+       view->pipe = NULL;
+
+       if (nloading-- == 1)
+               nodelay(status_win, FALSE);
+}
+
+static int
+update_view(struct view *view)
+{
+       char buffer[BUFSIZ];
+       char *line;
+       int lines, cols;
+       char **tmp;
+       int redraw;
+
+       if (!view->pipe)
+               return TRUE;
+
+       getmaxyx(view->win, lines, cols);
+
+       redraw = !view->line;
+
+       tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
+       if (!tmp)
+               goto alloc_error;
+
+       view->line = tmp;
+
+       while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
+               int linelen;
+
+               if (!lines--)
+                       break;
+
+               linelen = strlen(line);
+               if (linelen)
+                       line[linelen - 1] = 0;
+
+               view->line[view->lines] = strdup(line);
+               if (!view->line[view->lines])
+                       goto alloc_error;
+               view->lines++;
+       }
+
+       if (redraw)
+               redraw_view(view);
+
+       if (ferror(view->pipe)) {
+               report("Failed to read %s", view->cmd);
+               goto end;
+
+       } else if (feof(view->pipe)) {
+               report_position(view, 0);
+               goto end;
+       }
+
+       return TRUE;
+
+alloc_error:
+       report("Allocation failure");
+
+end:
+       end_update(view);
+       return FALSE;
+}
+
+
+static struct view *
+switch_view(struct view *prev, int request)
+{
+       struct view *view = &views[request - REQ_OFFSET];
+       struct view *displayed;
+       int i;
+
+       if (view == prev) {
+               foreach_view (displayed, i) ;
+
+               if (i == 1)
+                       report("Already in %s view", view->name);
+               else
+                       report("FIXME: Maximize");
+
+               return view;
+
+       } else {
+               foreach_view (displayed, i) {
+                       if (view == displayed) {
+                               current_view = i;
+                               report("New current view");
+                               return view;
+                       }
+               }
+       }
+
+       if (!view->win)
+               resize_view(view);
+
+       /* Reload */
+
+       if (view->line) {
+               for (i = 0; i < view->lines; i++)
+                       if (view->line[i])
+                               free(view->line[i]);
+
+               free(view->line);
+               view->line = NULL;
+       }
+
+       if (prev && prev->pipe)
+               end_update(prev);
+
+       if (begin_update(view)) {
+               if (!view->cmd)
+                       report("%s", HELP);
+               else
+                       report("loading...");
+       }
+
+       return view;
+}
+
+
+/* Process a keystroke */
+static int
+view_driver(struct view *view, int key)
+{
+       int request = get_request(key);
+       int i;
+
+       switch (request) {
+       case REQ_NEXT_LINE:
+       case REQ_NEXT_PAGE:
+       case REQ_PREV_LINE:
+       case REQ_PREV_PAGE:
+               if (view)
+                       scroll_view(view, request);
+               break;
+
+       case REQ_MAIN:
+       case REQ_LOG:
+       case REQ_DIFF:
+               view = switch_view(view, request);
+               break;
+
+       case REQ_REDRAW:
+               redraw_view(view);
+               break;
+
+       case REQ_STOP:
+               foreach_view (view, i) {
+                       if (view->pipe) {
+                               end_update(view);
+                               scroll_view(view, 0);
+                       }
+               }
+               break;
+
+       case REQ_VERSION:
+               report("version %s", VERSION);
+               return TRUE;
+
+       case REQ_UPDATE:
+               doupdate();
+               return TRUE;
+
+       case REQ_QUIT:
+               return FALSE;
+
+       default:
+               report(HELP);
+               return TRUE;
+       }
+
+       return TRUE;
+}
+
+
+/*
+ * Rendering
+ */
+
+#define ATTR(line, attr) { (line), sizeof(line) - 1, (attr) }
+
+struct attr {
+       char *line;
+       int linelen;
+       int attr;
+};
+
+static struct attr attrs[] = {
+       ATTR("commit ",         COLOR_PAIR(COLOR_GREEN)),
+       ATTR("Author: ",        COLOR_PAIR(COLOR_CYAN)),
+       ATTR("Date:   ",        COLOR_PAIR(COLOR_YELLOW)),
+       ATTR("diff --git ",     COLOR_PAIR(COLOR_YELLOW)),
+       ATTR("diff-tree ",      COLOR_PAIR(COLOR_BLUE)),
+       ATTR("index ",          COLOR_PAIR(COLOR_BLUE)),
+       ATTR("-",               COLOR_PAIR(COLOR_RED)),
+       ATTR("+",               COLOR_PAIR(COLOR_GREEN)),
+       ATTR("@",               COLOR_PAIR(COLOR_MAGENTA)),
+};
+
+static int
+default_renderer(struct view *view, int lineno)
+{
+       char *line;
+       int linelen;
+       int attr = A_NORMAL;
+       int i;
+
+       line = view->line[view->offset + lineno];
+       if (!line) return FALSE;
+
+       linelen = strlen(line);
+
+       for (i = 0; i < ARRAY_SIZE(attrs); i++) {
+               if (linelen < attrs[i].linelen
+                   || strncmp(attrs[i].line, line, attrs[i].linelen))
+                       continue;
+
+               attr = attrs[i].attr;
+               break;
+       }
+
+       wattrset(view->win, attr);
+       mvwprintw(view->win, lineno, 0, "%4d: %s", view->offset + lineno, line);
+
+       return TRUE;
+}
+
+/*
+ * Main
+ */
+
+static void
+quit(int sig)
+{
+       if (status_win)
+               delwin(status_win);
+       endwin();
+
+       /* FIXME: Shutdown gracefully. */
+
+       exit(0);
+}
+
+static void die(const char *err, ...)
+{
+       va_list args;
+
+       endwin();
+
+       va_start(args, err);
+       fputs("tig: ", stderr);
+       vfprintf(stderr, err, args);
+       fputs("\n", stderr);
+       va_end(args);
+
+       exit(1);
+}
+
+static void
+report(const char *msg, ...)
+{
+       va_list args;
+
+       va_start(args, msg);
+
+       werase(status_win);
+       wmove(status_win, 0, 0);
+
+       if (display[current_view])
+               wprintw(status_win, "%4s: ", display[current_view]->name);
+
+       vwprintw(status_win, msg, args);
+       wrefresh(status_win);
+
+       va_end(args);
+}
+
+static void
+init_colors(void)
+{
+       int bg = COLOR_BLACK;
+
+       start_color();
+
+       if (use_default_colors() != ERR)
+               bg = -1;
+
+       init_pair(COLOR_BLACK,   COLOR_BLACK,   bg);
+       init_pair(COLOR_GREEN,   COLOR_GREEN,   bg);
+       init_pair(COLOR_RED,     COLOR_RED,     bg);
+       init_pair(COLOR_CYAN,    COLOR_CYAN,    bg);
+       init_pair(COLOR_WHITE,   COLOR_WHITE,   bg);
+       init_pair(COLOR_MAGENTA, COLOR_MAGENTA, bg);
+       init_pair(COLOR_BLUE,    COLOR_BLUE,    bg);
+       init_pair(COLOR_YELLOW,  COLOR_YELLOW,  bg);
+}
+
+int
+main(int argc, char *argv[])
+{
+       int request = REQ_MAIN;
+       int x, y;
+
+       signal(SIGINT, quit);
+
+       initscr();      /* initialize the curses library */
+       nonl();         /* tell curses not to do NL->CR/NL on output */
+       cbreak();       /* take input chars one at a time, no wait for \n */
+       noecho();       /* don't echo input */
+       leaveok(stdscr, TRUE);
+       /* curs_set(0); */
+
+       if (has_colors())
+               init_colors();
+
+       getmaxyx(stdscr, y, x);
+       status_win = newwin(1, 0, y - 1, 0);
+       if (!status_win)
+               die("Failed to create status window");
+
+       /* Enable keyboard mapping */
+       keypad(status_win, TRUE);
+       wattrset(status_win, COLOR_PAIR(COLOR_GREEN));
+
+       while (view_driver(display[current_view], request)) {
+               struct view *view;
+               int i;
+
+               foreach_view (view, i) {
+                       if (view->pipe) {
+                               update_view(view);
+                       }
+               }
+
+               /* Refresh, accept single keystroke of input */
+               request = wgetch(status_win);
+               if (request == KEY_RESIZE) {
+                       int lines, cols;
+
+                       getmaxyx(stdscr, lines, cols);
+                       mvwin(status_win, lines - 1, 0);
+                       wresize(status_win, 1, cols - 1);
+               }
+       }
+
+       quit(0);
+
+       return 0;
+}
+
+/**
+ * COPYRIGHT
+ * ---------
+ * Copyright (c) Jonas Fonseca, 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * SEE ALSO
+ * --------
+ * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
+ * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
+ **/