* SYNOPSIS
* --------
* [verse]
- * tig
- * tig log [git log options]
- * tig diff [git diff options]
- * tig < [git log or git diff output]
+ * tig [options]
+ * tig [options] log [git log options]
+ * tig [options] diff [git diff options]
+ * tig [options] < [git log or git diff output]
*
* DESCRIPTION
* -----------
* Browse changes in a git repository.
- *
- * OPTIONS
- * -------
- *
- * None.
- *
**/
#define DEBUG
-
#ifndef DEBUG
#define NDEBUG
#endif
+#ifndef VERSION
+#define VERSION "tig-0.1"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <signal.h>
#include <stdarg.h>
-#include <stdlib.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
-#include <signal.h>
-#include <assert.h>
+#include <time.h>
#include <curses.h>
#include <form.h>
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
-#define KEY_ESC 27
#define KEY_TAB 9
+#define KEY_ESC 27
+#define KEY_DEL 127
#define REQ_OFFSET (MAX_COMMAND + 1)
#define REQ_DIFF (REQ_OFFSET + 0)
#define REQ_LOG (REQ_OFFSET + 1)
#define REQ_MAIN (REQ_OFFSET + 2)
+#define REQ_VIEWS (REQ_OFFSET + 3)
#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)
+#define REQ_FIRST_LINE (REQ_OFFSET + 16)
+#define REQ_LAST_LINE (REQ_OFFSET + 17)
+#define REQ_LINE_NUMBER (REQ_OFFSET + 18)
+
+#define SIZEOF_VIEWS (REQ_VIEWS - REQ_OFFSET)
+#define SIZEOF_ID 1024
+
+#define COLOR_TRANSP (-1)
+
+
+/**
+ * OPTIONS
+ * -------
+ **/
+
+static int opt_line_number;
+static int opt_request = REQ_MAIN;
+
+char head_id[SIZEOF_ID] = "HEAD";
+char commit_id[SIZEOF_ID] = "HEAD";
+
+
+/* Returns the index of log or diff command or -1 to exit. */
+static int
+parse_options(int argc, char *argv[])
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ char *opt = argv[i];
+
+ /**
+ * log [options]::
+ * git log options.
+ **/
+ if (!strcmp(opt, "log")) {
+ opt_request = REQ_LOG;
+ return i;
+
+ /**
+ * diff [options]::
+ * git diff options.
+ **/
+ } else if (!strcmp(opt, "diff")) {
+ opt_request = REQ_DIFF;
+ return i;
+
+ /**
+ * -l::
+ * Start up in log view.
+ **/
+ } else if (!strcmp(opt, "-l")) {
+ opt_request = REQ_LOG;
+
+ /**
+ * -d::
+ * Start up in diff view.
+ **/
+ } else if (!strcmp(opt, "-d")) {
+ opt_request = REQ_DIFF;
+
+ /**
+ * -n, --line-number::
+ * Prefix line numbers in log and diff view.
+ **/
+ } else if (!strcmp(opt, "-n") ||
+ !strcmp(opt, "--line-number")) {
+ opt_line_number = 1;
+
+ /**
+ * -v, --version::
+ * Show version and exit.
+ **/
+ } else if (!strcmp(opt, "-v") ||
+ !strcmp(opt, "--version")) {
+ printf("tig version %s\n", VERSION);
+ return -1;
+
+ /**
+ * ref::
+ * Commit reference, symbolic or raw SHA1 ID.
+ **/
+ } else if (opt[0] && opt[0] != '-') {
+ strncpy(head_id, opt, SIZEOF_ID);
+ strncpy(commit_id, opt, SIZEOF_ID);
+
+ } else {
+ die("Unknown command: '%s'", opt);
+ }
+ }
+
+ return i;
+}
+
+
+/*
+ * Line-oriented content detection.
+ */
+
+enum line_type {
+ LINE_DEFAULT,
+ LINE_AUTHOR,
+ LINE_AUTHOR_IDENT,
+ LINE_COMMIT,
+ LINE_COMMITTER,
+ LINE_CURSOR,
+ LINE_DATE,
+ LINE_DIFF,
+ LINE_DIFF_ADD,
+ LINE_DIFF_CHUNK,
+ LINE_DIFF_COPY,
+ LINE_DIFF_DEL,
+ LINE_DIFF_DISSIM,
+ LINE_DIFF_NEWMODE,
+ LINE_DIFF_OLDMODE,
+ LINE_DIFF_RENAME,
+ LINE_DIFF_SIM,
+ LINE_DIFF_TREE,
+ LINE_INDEX,
+ LINE_MAIN_AUTHOR,
+ LINE_MAIN_COMMIT,
+ LINE_MAIN_DATE,
+ LINE_MAIN_DELIM,
+ LINE_MERGE,
+ LINE_PARENT,
+ LINE_STATUS,
+ LINE_TITLE,
+ LINE_TREE,
+};
+
+struct line_info {
+ enum line_type type;
+ char *line;
+ int linelen;
+
+ int fg;
+ int bg;
+ int attr;
+};
+
+#define LINE(type, line, fg, bg, attr) \
+ { LINE_##type, (line), sizeof(line) - 1, (fg), (bg), (attr) }
+
+static struct line_info line_info[] = {
+ /* Diff markup */
+ LINE(DIFF, "diff --git ", COLOR_YELLOW, COLOR_TRANSP, 0),
+ LINE(INDEX, "index ", COLOR_BLUE, COLOR_TRANSP, 0),
+ LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_TRANSP, 0),
+ LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_TRANSP, 0),
+ LINE(DIFF_DEL, "-", COLOR_RED, COLOR_TRANSP, 0),
+ LINE(DIFF_OLDMODE, "old mode ", COLOR_YELLOW, COLOR_TRANSP, 0),
+ LINE(DIFF_NEWMODE, "new mode ", COLOR_YELLOW, COLOR_TRANSP, 0),
+ LINE(DIFF_COPY, "copy ", COLOR_YELLOW, COLOR_TRANSP, 0),
+ LINE(DIFF_RENAME, "rename ", COLOR_YELLOW, COLOR_TRANSP, 0),
+ LINE(DIFF_SIM, "similarity ", COLOR_YELLOW, COLOR_TRANSP, 0),
+ LINE(DIFF_DISSIM, "dissimilarity ", COLOR_YELLOW, COLOR_TRANSP, 0),
+
+ /* Pretty print commit header */
+ LINE(AUTHOR, "Author: ", COLOR_CYAN, COLOR_TRANSP, 0),
+ LINE(MERGE, "Merge: ", COLOR_BLUE, COLOR_TRANSP, 0),
+ LINE(DATE, "Date: ", COLOR_YELLOW, COLOR_TRANSP, 0),
+
+ /* Raw commit header */
+ LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_TRANSP, 0),
+ LINE(PARENT, "parent ", COLOR_BLUE, COLOR_TRANSP, 0),
+ LINE(TREE, "tree ", COLOR_BLUE, COLOR_TRANSP, 0),
+ LINE(AUTHOR_IDENT, "author ", COLOR_CYAN, COLOR_TRANSP, 0),
+ LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_TRANSP, 0),
+
+ LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_TRANSP, 0),
+
+ /* UI colors */
+ LINE(DEFAULT, "", COLOR_TRANSP, COLOR_TRANSP, A_NORMAL),
+ LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD),
+ LINE(STATUS, "", COLOR_GREEN, COLOR_TRANSP, 0),
+ LINE(TITLE, "", COLOR_YELLOW, COLOR_BLUE, A_BOLD),
+ LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_TRANSP, 0),
+ LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_TRANSP, 0),
+ LINE(MAIN_COMMIT, "", COLOR_TRANSP, COLOR_TRANSP, 0),
+ LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_TRANSP, 0),
+};
+
+static struct line_info *
+get_line_info(char *line)
+{
+ int linelen = strlen(line);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(line_info); i++) {
+ if (linelen < line_info[i].linelen
+ || strncmp(line_info[i].line, line, line_info[i].linelen))
+ continue;
+
+ return &line_info[i];
+ }
+
+ return NULL;
+}
+
+static enum line_type
+get_line_type(char *line)
+{
+ struct line_info *info = get_line_info(line);
+
+ return info ? info->type : LINE_DEFAULT;
+}
+
+static int
+get_line_attr(enum line_type type)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(line_info); i++)
+ if (line_info[i].type == type)
+ return COLOR_PAIR(line_info[i].type) | line_info[i].attr;
+
+ return A_NORMAL;
+}
+
+static void
+init_colors(void)
+{
+ int transparent_bg = COLOR_BLACK;
+ int transparent_fg = COLOR_WHITE;
+ int i;
+
+ start_color();
+
+ if (use_default_colors() != ERR) {
+ transparent_bg = -1;
+ transparent_fg = -1;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(line_info); i++) {
+ struct line_info *info = &line_info[i];
+ int bg = info->bg == COLOR_TRANSP ? transparent_bg : info->bg;
+ int fg = info->fg == COLOR_TRANSP ? transparent_fg : info->fg;
+
+ init_pair(info->type, fg, bg);
+ }
+}
/**
};
struct keymap keymap[] = {
+ /* Cursor navigation */
{ KEY_UP, REQ_PREV_LINE },
{ 'k', REQ_PREV_LINE },
{ KEY_DOWN, REQ_NEXT_LINE },
{ 'j', REQ_NEXT_LINE },
+ { KEY_HOME, REQ_FIRST_LINE },
+ { KEY_END, REQ_LAST_LINE },
{ KEY_NPAGE, REQ_NEXT_PAGE },
{ KEY_PPAGE, REQ_PREV_PAGE },
+ /* Scrolling */
+ { KEY_IC, REQ_SCR_BLINE }, /* scroll field backward a line */
+ { KEY_DC, REQ_SCR_FLINE }, /* scroll field forward a line */
+ { 's', REQ_SCR_FPAGE }, /* scroll field forward a page */
+ { 'w', REQ_SCR_BPAGE }, /* scroll field backward a page */
+
{ 'd', REQ_DIFF },
{ 'l', REQ_LOG },
{ 'm', REQ_MAIN },
+ { 'n', REQ_LINE_NUMBER },
+
/* No input from wgetch() with nodelay() enabled. */
{ ERR, REQ_UPDATE },
struct view {
char *name;
char *cmd;
+ char *id;
+
/* Rendering */
- int (*render)(struct view *, int);
+ int (*read)(struct view *, char *);
+ int (*draw)(struct view *, unsigned int);
+ size_t objsize; /* Size of objects in the line index */
+
WINDOW *win;
+ int height, width;
/* Navigation */
unsigned long offset; /* Offset of the window top */
/* Buffering */
unsigned long lines; /* Total number of lines */
- char **line; /* Line index */
+ void **line; /* Line index */
/* Loading */
FILE *pipe;
};
-static int default_renderer(struct view *view, int lineno);
+struct commit {
+ char id[41];
+ char title[75];
+ char author[75];
+ struct tm time;
+};
+
+static int pager_draw(struct view *view, unsigned int lineno);
+static int pager_read(struct view *view, char *line);
+
+static int main_draw(struct view *view, unsigned int lineno);
+static int main_read(struct view *view, char *line);
-#define DIFF_CMD \
+#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"
+#define MAIN_CMD \
+ "git log --stat --pretty=raw %s"
+
/* The status window at the bottom. Used for polling keystrokes. */
static WINDOW *status_win;
+static WINDOW *title_win;
+
+static unsigned int current_view;
+static unsigned int nloading;
+
+static struct view views[];
+static struct view *display[];
+
static struct view views[] = {
- { "diff", DIFF_CMD, default_renderer },
- { "log", LOG_CMD, default_renderer },
- { "main", NULL },
+ { "diff", DIFF_CMD, commit_id, pager_read, pager_draw, sizeof(char) },
+ { "log", LOG_CMD, head_id, pager_read, pager_draw, sizeof(char) },
+ { "main", MAIN_CMD, head_id, main_read, main_draw, sizeof(struct commit) },
};
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++)
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);
+ for (lineno = 0; lineno < view->height; lineno++) {
+ if (!view->draw(view, lineno))
+ break;
}
redrawwin(view->win);
wrefresh(view->win);
}
+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 - 2, cols);
+
+ } else {
+ view->win = newwin(lines - 2, 0, 0, 0);
+ if (!view->win) {
+ report("failed to create %s view", view->name);
+ return;
+ }
+ scrollok(view->win, TRUE);
+ }
+
+ getmaxyx(view->win, view->height, view->width);
+}
+
/* FIXME: Fix percentage. */
static void
report_position(struct view *view, int all)
view->offset);
}
+
static void
-scroll_view(struct view *view, int request)
+move_view(struct view *view, int lines)
{
- int x, y, lines = 1;
- enum { BACKWARD = -1, FORWARD = 1 } direction = FORWARD;
+ /* The rendering expects the new offset. */
+ view->offset += lines;
+
+ assert(0 <= view->offset && view->offset < view->lines);
+ assert(lines);
+
+ if (view->height < (lines > 0 ? lines : -lines)) {
+ redraw_view(view);
+
+ } else {
+ int line = lines > 0 ? view->height - lines : 0;
+ int end = line + (lines > 0 ? lines : -lines);
+
+ wscrl(view->win, lines);
+
+ for (; line < end; line++) {
+ if (!view->draw(view, line))
+ break;
+ }
+ }
+
+ /* Move current line into the view. */
+ if (view->lineno < view->offset) {
+ view->lineno = view->offset;
+ view->draw(view, 0);
+
+ } else if (view->lineno >= view->offset + view->height) {
+ view->lineno = view->offset + view->height - 1;
+ view->draw(view, view->lineno - view->offset);
+ }
+
+ assert(view->offset <= view->lineno && view->lineno <= view->lines);
- getmaxyx(view->win, y, x);
+ redrawwin(view->win);
+ wrefresh(view->win);
+
+ report_position(view, lines);
+}
+
+static void
+scroll_view(struct view *view, int request)
+{
+ int lines = 1;
switch (request) {
- case REQ_NEXT_PAGE:
- lines = y;
- case REQ_NEXT_LINE:
+ case REQ_SCR_FPAGE:
+ lines = view->height;
+ case REQ_SCR_FLINE:
if (view->offset + lines > view->lines)
- lines = view->lines - view->offset - 1;
+ lines = view->lines - view->offset;
- if (lines == 0 || view->offset + y >= view->lines) {
+ if (lines == 0 || view->offset + view->height >= view->lines) {
report("already at last line");
return;
}
break;
- case REQ_PREV_PAGE:
- lines = y;
- case REQ_PREV_LINE:
+ case REQ_SCR_BPAGE:
+ lines = view->height;
+ case REQ_SCR_BLINE:
if (lines > view->offset)
lines = view->offset;
return;
}
- direction = BACKWARD;
+ lines = -lines;
break;
-
- default:
- lines = 0;
}
- report("off=%d lines=%d lineno=%d move=%d", view->offset, view->lines, view->lineno, lines * direction);
+ move_view(view, lines);
+}
- /* The rendering expects the new offset. */
- view->offset += lines * direction;
+static void
+navigate_view(struct view *view, int request)
+{
+ int steps;
- /* 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;
+ switch (request) {
+ case REQ_FIRST_LINE:
+ steps = -view->lineno;
+ break;
- 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);
+ case REQ_LAST_LINE:
+ steps = view->lines - view->lineno - 1;
+ break;
- if (lines) {
- int from = direction == FORWARD ? y - lines : 0;
- int to = from + lines;
+ case REQ_PREV_PAGE:
+ steps = view->height > view->lineno
+ ? -view->lineno : -view->height;
+ break;
- wscrl(view->win, lines * direction);
+ case REQ_NEXT_PAGE:
+ steps = view->lineno + view->height >= view->lines
+ ? view->lines - view->lineno - 1 : view->height;
+ break;
- for (; from < to; from++) {
- if (!view->render(view, from))
- break;
- }
- }
+ case REQ_PREV_LINE:
+ steps = -1;
+ break;
- redrawwin(view->win);
- wrefresh(view->win);
+ case REQ_NEXT_LINE:
+ steps = 1;
+ break;
+ }
- report_position(view, lines);
-}
+ if (steps < 0 && view->lineno == 0) {
+ report("already at first line");
+ return;
-static void
-resize_view(struct view *view)
-{
- int lines, cols;
+ } else if (steps > 0 && view->lineno + 1 == view->lines) {
+ report("already at last line");
+ return;
+ }
- getmaxyx(stdscr, lines, cols);
+ view->lineno += steps;
+ view->draw(view, view->lineno - steps - view->offset);
- if (view->win) {
- mvwin(view->win, 0, 0);
- wresize(view->win, lines - 1, cols);
+ if (view->lineno < view->offset ||
+ view->lineno >= view->offset + view->height) {
+ if (steps < 0 && -steps > view->offset) {
+ steps = -view->offset;
- } else {
- view->win = newwin(lines - 1, 0, 0, 0);
- if (!view->win) {
- report("Failed to create %s view", view->name);
- return;
+ } else if (steps > 0) {
+ if (view->lineno == view->lines - 1 &&
+ view->lines > view->height) {
+ steps = view->lines - view->offset - 1;
+ if (steps >= view->height)
+ steps -= view->height - 1;
+ }
}
- scrollok(view->win, TRUE);
+
+ move_view(view, steps);
+ return;
}
+
+ /* Draw the cursor line */
+ view->draw(view, view->lineno - view->offset);
+
+ redrawwin(view->win);
+ wrefresh(view->win);
+
+ report_position(view, view->height);
}
+
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))
+ char *id = view->id;
+
+ if (snprintf(buf, sizeof(buf), view->cmd, id, id, id) < sizeof(buf))
view->pipe = popen(buf, "r");
if (!view->pipe)
{
char buffer[BUFSIZ];
char *line;
- int lines, cols;
- char **tmp;
+ void **tmp;
int redraw;
+ int lines = view->height;
if (!view->pipe)
return TRUE;
- getmaxyx(view->win, lines, cols);
-
- redraw = !view->line;
+ /* Only redraw after the first reading session. */
+ /* FIXME: ... and possibly more. */
+ redraw = view->height > view->lines;
tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
if (!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])
+ if (!view->read(view, line))
goto alloc_error;
- view->lines++;
+
+ if (lines-- == 1)
+ break;
}
if (redraw)
redraw_view(view);
if (ferror(view->pipe)) {
- report("Failed to read %s", view->cmd);
+ report("failed to read %s", view->cmd);
goto end;
} else if (feof(view->pipe)) {
return TRUE;
alloc_error:
- report("Allocation failure");
+ report("allocation failure");
end:
end_update(view);
foreach_view (displayed, i) ;
if (i == 1)
- report("Already in %s view", view->name);
+ report("already in %s view", view->name);
else
report("FIXME: Maximize");
foreach_view (displayed, i) {
if (view == displayed) {
current_view = i;
- report("New current view");
+ report("new current view");
return view;
}
}
switch (request) {
case REQ_NEXT_LINE:
- case REQ_NEXT_PAGE:
case REQ_PREV_LINE:
+ case REQ_FIRST_LINE:
+ case REQ_LAST_LINE:
+ case REQ_NEXT_PAGE:
case REQ_PREV_PAGE:
if (view)
+ navigate_view(view, request);
+ break;
+
+ case REQ_SCR_FLINE:
+ case REQ_SCR_BLINE:
+ case REQ_SCR_FPAGE:
+ case REQ_SCR_BPAGE:
+ if (view)
scroll_view(view, request);
break;
view = switch_view(view, request);
break;
+ case REQ_LINE_NUMBER:
+ opt_line_number = !opt_line_number;
+ redraw_view(view);
+ break;
+
case REQ_REDRAW:
redraw_view(view);
break;
* Rendering
*/
-#define ATTR(line, attr) { (line), sizeof(line) - 1, (attr) }
-
-struct attr {
+static int
+pager_draw(struct view *view, unsigned int lineno)
+{
+ enum line_type type;
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)),
-};
+ if (view->offset + lineno >= view->lines)
+ return FALSE;
+
+ line = view->line[view->offset + lineno];
+ type = get_line_type(line);
+
+ if (view->offset + lineno == view->lineno) {
+ if (type == LINE_COMMIT)
+ strncpy(commit_id, line + 7, SIZEOF_ID);
+ type = LINE_CURSOR;
+ }
+
+ attr = get_line_attr(type);
+ wattrset(view->win, attr);
+
+ if (opt_line_number) {
+ mvwprintw(view->win, lineno, 0, "%4d: ", view->offset + lineno + 1);
+ while (line) {
+ if (*line == '\t') {
+ waddstr(view->win, " ");
+ line++;
+ } else {
+ char *tab = strchr(line, '\t');
+
+ if (tab)
+ waddnstr(view->win, line, tab - line);
+ else
+ waddstr(view->win, line);
+ line = tab;
+ }
+ }
+ waddstr(view->win, line);
+
+ } else {
+ /* No empty lines makes cursor drawing and clearing implicit. */
+ if (!*line)
+ line = " ";
+ mvwaddstr(view->win, lineno, 0, line);
+ }
+
+ return TRUE;
+}
static int
-default_renderer(struct view *view, int lineno)
+pager_read(struct view *view, char *line)
{
- char *line;
- int linelen;
- int attr = A_NORMAL;
- int i;
+ view->line[view->lines] = strdup(line);
+ if (!view->line[view->lines])
+ return FALSE;
- line = view->line[view->offset + lineno];
- if (!line) return FALSE;
+ view->lines++;
+ return TRUE;
+}
- linelen = strlen(line);
+static int
+main_draw(struct view *view, unsigned int lineno)
+{
+ char buf[21];
+ struct commit *commit;
+ enum line_type type;
+ int cols = 0;
+ size_t timelen;
- for (i = 0; i < ARRAY_SIZE(attrs); i++) {
- if (linelen < attrs[i].linelen
- || strncmp(attrs[i].line, line, attrs[i].linelen))
- continue;
+ if (view->offset + lineno >= view->lines)
+ return FALSE;
- attr = attrs[i].attr;
- break;
+ commit = view->line[view->offset + lineno];
+ if (!commit) return FALSE;
+
+ if (view->offset + lineno == view->lineno) {
+ strncpy(commit_id, commit->id, SIZEOF_ID);
+ type = LINE_CURSOR;
+ } else {
+ type = LINE_MAIN_COMMIT;
}
- wattrset(view->win, attr);
- mvwprintw(view->win, lineno, 0, "%4d: %s", view->offset + lineno, line);
+ wmove(view->win, lineno, cols);
+ wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
+
+ timelen = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S ", &commit->time);
+ waddnstr(view->win, buf, timelen);
+
+ cols += 20;
+ wmove(view->win, lineno, cols);
+ wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
+
+ if (strlen(commit->author) > 19) {
+ waddnstr(view->win, commit->author, 18);
+ wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
+ waddch(view->win, '~');
+ } else {
+ waddstr(view->win, commit->author);
+ }
+
+ cols += 20;
+ wattrset(view->win, A_NORMAL);
+ mvwaddch(view->win, lineno, cols, ACS_LTEE);
+ wattrset(view->win, get_line_attr(type));
+ mvwaddstr(view->win, lineno, cols + 2, commit->title);
+ wattrset(view->win, A_NORMAL);
return TRUE;
}
+static int
+main_read(struct view *view, char *line)
+{
+ enum line_type type = get_line_type(line);
+ struct commit *commit;
+
+ switch (type) {
+ case LINE_COMMIT:
+ commit = calloc(1, sizeof(struct commit));
+ if (!commit)
+ return FALSE;
+
+ line += sizeof("commit ") - 1;
+
+ view->line[view->lines++] = commit;
+ strncpy(commit->id, line, sizeof(commit->id));
+ break;
+
+ case LINE_AUTHOR_IDENT:
+ {
+ char *ident = line + sizeof("author ") - 1;
+ char *end = strchr(ident, '<');
+
+ if (end) {
+ for (; end > ident && isspace(end[-1]); end--) ;
+ *end = 0;
+ }
+
+ commit = view->line[view->lines - 1];
+ strncpy(commit->author, ident, sizeof(commit->author));
+
+ if (end) {
+ char *secs = strchr(end + 1, '>');
+ char *zone;
+ time_t time;
+
+ if (!secs || secs[1] != ' ')
+ break;
+
+ secs += 2;
+ time = (time_t) atol(secs);
+ zone = strchr(secs, ' ');
+ if (zone && strlen(zone) == sizeof(" +0700") - 1) {
+ long tz;
+
+ zone++;
+ tz = ('0' - zone[1]) * 60 * 60 * 10;
+ tz += ('0' - zone[2]) * 60 * 60;
+ tz += ('0' - zone[3]) * 60;
+ tz += ('0' - zone[4]) * 60;
+
+ if (zone[0] == '-')
+ tz = -tz;
+
+ time -= tz;
+ }
+ gmtime_r(&time, &commit->time);
+ }
+ break;
+ }
+ default:
+ commit = view->line[view->lines - 1];
+ if (!commit->title[0] &&
+ !strncmp(line, " ", 4) &&
+ !isspace(line[5]))
+ strncpy(commit->title, line + 4, sizeof(commit->title));
+ }
+
+ return TRUE;
+}
+
+
/*
* Main
*/
{
if (status_win)
delwin(status_win);
+ if (title_win)
+ delwin(title_win);
endwin();
/* FIXME: Shutdown gracefully. */
{
va_list args;
+ werase(title_win);
+ wmove(title_win, 0, 0);
+ wprintw(title_win, "commit %s", commit_id);
+ wrefresh(title_win);
+
va_start(args, msg);
werase(status_win);
wmove(status_win, 0, 0);
+#if 0
if (display[current_view])
- wprintw(status_win, "%4s: ", display[current_view]->name);
-
+ wprintw(status_win, "%s %4s: ", commit_id, display[current_view]->name);
+#endif
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;
+ int request;
+ int git_cmd;
signal(SIGINT, quit);
+ git_cmd = parse_options(argc, argv);
+ if (git_cmd < 0)
+ return 0;
+
+ request = opt_request;
+
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 */
if (!status_win)
die("Failed to create status window");
+ title_win = newwin(1, 0, y - 2, 0);
+ if (!title_win)
+ die("Failed to create title window");
+
/* Enable keyboard mapping */
keypad(status_win, TRUE);
- wattrset(status_win, COLOR_PAIR(COLOR_GREEN));
+ wbkgdset(status_win, get_line_attr(LINE_STATUS));
+ wbkgdset(title_win, get_line_attr(LINE_TITLE));
while (view_driver(display[current_view], request)) {
struct view *view;
int lines, cols;
getmaxyx(stdscr, lines, cols);
+
mvwin(status_win, lines - 1, 0);
wresize(status_win, 1, cols - 1);
+
+ mvwin(title_win, lines - 2, 0);
+ wresize(title_win, 1, cols - 1);
}
}