+ char *home = getenv("HOME");
+ char buf[SIZEOF_STR];
+ FILE *file;
+
+ config_lineno = 0;
+ config_errors = FALSE;
+
+ if (!home || !string_format(buf, "%s/.tigrc", home))
+ return ERR;
+
+ /* It's ok that the file doesn't exist. */
+ file = fopen(buf, "r");
+ if (!file)
+ return OK;
+
+ if (read_properties(file, " \t", read_option) == ERR ||
+ config_errors == TRUE)
+ fprintf(stderr, "Errors while loading %s.\n", buf);
+
+ return OK;
+}
+
+
+/*
+ * The viewer
+ */
+
+struct view;
+struct view_ops;
+
+/* The display array of active views and the index of the current view. */
+static struct view *display[2];
+static unsigned int current_view;
+
+/* Reading from the prompt? */
+static bool input_mode = FALSE;
+
+#define foreach_displayed_view(view, i) \
+ for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
+
+#define displayed_views() (display[1] != NULL ? 2 : 1)
+
+/* Current head and commit ID */
+static char ref_blob[SIZEOF_REF] = "";
+static char ref_commit[SIZEOF_REF] = "HEAD";
+static char ref_head[SIZEOF_REF] = "HEAD";
+
+struct view {
+ const char *name; /* View name */
+ const char *cmd_fmt; /* Default command line format */
+ const char *cmd_env; /* Command line set via environment */
+ const char *id; /* Points to either of ref_{head,commit,blob} */
+
+ struct view_ops *ops; /* View operations */
+
+ enum keymap keymap; /* What keymap does this view have */
+
+ char cmd[SIZEOF_STR]; /* Command buffer */
+ char ref[SIZEOF_REF]; /* Hovered commit reference */
+ char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
+
+ int height, width; /* The width and height of the main window */
+ WINDOW *win; /* The main window */
+ WINDOW *title; /* The title window living below the main window */
+
+ /* Navigation */
+ unsigned long offset; /* Offset of the window top */
+ unsigned long lineno; /* Current line number */
+
+ /* Searching */
+ char grep[SIZEOF_STR]; /* Search string */
+ regex_t *regex; /* Pre-compiled regex */
+
+ /* If non-NULL, points to the view that opened this view. If this view
+ * is closed tig will switch back to the parent view. */
+ struct view *parent;
+
+ /* Buffering */
+ unsigned long lines; /* Total number of lines */
+ struct line *line; /* Line index */
+ unsigned long line_size;/* Total number of allocated lines */
+ unsigned int digits; /* Number of digits in the lines member. */
+
+ /* Loading */
+ FILE *pipe;
+ time_t start_time;
+};
+
+struct view_ops {
+ /* What type of content being displayed. Used in the title bar. */
+ const char *type;
+ /* Open and reads in all view content. */
+ bool (*open)(struct view *view);
+ /* Read one line; updates view->line. */
+ bool (*read)(struct view *view, char *data);
+ /* Draw one line; @lineno must be < view->height. */
+ bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
+ /* Depending on view handle a special requests. */
+ enum request (*request)(struct view *view, enum request request, struct line *line);
+ /* Search for regex in a line. */
+ bool (*grep)(struct view *view, struct line *line);
+ /* Select line */
+ void (*select)(struct view *view, struct line *line);
+};
+
+static struct view_ops pager_ops;
+static struct view_ops main_ops;
+static struct view_ops tree_ops;
+static struct view_ops blob_ops;
+static struct view_ops help_ops;
+static struct view_ops status_ops;
+static struct view_ops stage_ops;
+
+#define VIEW_STR(name, cmd, env, ref, ops, map) \
+ { name, cmd, #env, ref, ops, map}
+
+#define VIEW_(id, name, ops, ref) \
+ VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
+
+
+static struct view views[] = {
+ VIEW_(MAIN, "main", &main_ops, ref_head),
+ VIEW_(DIFF, "diff", &pager_ops, ref_commit),
+ VIEW_(LOG, "log", &pager_ops, ref_head),
+ VIEW_(TREE, "tree", &tree_ops, ref_commit),
+ VIEW_(BLOB, "blob", &blob_ops, ref_blob),
+ VIEW_(HELP, "help", &help_ops, ""),
+ VIEW_(PAGER, "pager", &pager_ops, "stdin"),
+ VIEW_(STATUS, "status", &status_ops, ""),
+ VIEW_(STAGE, "stage", &stage_ops, ""),
+};
+
+#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
+
+#define foreach_view(view, i) \
+ for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
+
+#define view_is_displayed(view) \
+ (view == display[0] || view == display[1])
+
+static bool
+draw_view_line(struct view *view, unsigned int lineno)
+{
+ struct line *line;
+ bool selected = (view->offset + lineno == view->lineno);
+ bool draw_ok;
+
+ assert(view_is_displayed(view));
+
+ if (view->offset + lineno >= view->lines)
+ return FALSE;
+
+ line = &view->line[view->offset + lineno];
+
+ if (selected) {
+ line->selected = TRUE;
+ view->ops->select(view, line);
+ } else if (line->selected) {
+ line->selected = FALSE;
+ wmove(view->win, lineno, 0);
+ wclrtoeol(view->win);
+ }
+
+ scrollok(view->win, FALSE);
+ draw_ok = view->ops->draw(view, line, lineno, selected);
+ scrollok(view->win, TRUE);
+
+ return draw_ok;
+}
+
+static void
+redraw_view_from(struct view *view, int lineno)
+{
+ assert(0 <= lineno && lineno < view->height);
+
+ for (; lineno < view->height; lineno++) {
+ if (!draw_view_line(view, lineno))
+ break;
+ }
+
+ redrawwin(view->win);
+ if (input_mode)
+ wnoutrefresh(view->win);
+ else
+ wrefresh(view->win);
+}
+
+static void
+redraw_view(struct view *view)
+{
+ wclear(view->win);
+ redraw_view_from(view, 0);
+}
+
+
+static void
+update_view_title(struct view *view)
+{
+ char buf[SIZEOF_STR];
+ char state[SIZEOF_STR];
+ size_t bufpos = 0, statelen = 0;
+
+ assert(view_is_displayed(view));
+
+ if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
+ unsigned int view_lines = view->offset + view->height;
+ unsigned int lines = view->lines
+ ? MIN(view_lines, view->lines) * 100 / view->lines
+ : 0;
+
+ string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
+ view->ops->type,
+ view->lineno + 1,
+ view->lines,
+ lines);
+
+ if (view->pipe) {
+ time_t secs = time(NULL) - view->start_time;
+
+ /* Three git seconds are a long time ... */
+ if (secs > 2)
+ string_format_from(state, &statelen, " %lds", secs);
+ }
+ }
+
+ string_format_from(buf, &bufpos, "[%s]", view->name);
+ if (*view->ref && bufpos < view->width) {
+ size_t refsize = strlen(view->ref);
+ size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
+
+ if (minsize < view->width)
+ refsize = view->width - minsize + 7;
+ string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
+ }
+
+ if (statelen && bufpos < view->width) {
+ string_format_from(buf, &bufpos, " %s", state);
+ }
+
+ if (view == display[current_view])
+ wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
+ else
+ wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
+
+ mvwaddnstr(view->title, 0, 0, buf, bufpos);
+ wclrtoeol(view->title);
+ wmove(view->title, 0, view->width - 1);
+
+ if (input_mode)
+ wnoutrefresh(view->title);
+ else
+ wrefresh(view->title);
+}
+
+static void
+resize_display(void)
+{
+ int offset, i;
+ struct view *base = display[0];
+ struct view *view = display[1] ? display[1] : display[0];
+
+ /* Setup window dimensions */
+
+ getmaxyx(stdscr, base->height, base->width);
+
+ /* Make room for the status window. */
+ base->height -= 1;
+
+ if (view != base) {
+ /* Horizontal split. */
+ view->width = base->width;
+ view->height = SCALE_SPLIT_VIEW(base->height);
+ base->height -= view->height;
+
+ /* Make room for the title bar. */
+ view->height -= 1;
+ }
+
+ /* Make room for the title bar. */
+ base->height -= 1;
+
+ offset = 0;
+
+ foreach_displayed_view (view, i) {
+ if (!view->win) {
+ view->win = newwin(view->height, 0, offset, 0);
+ if (!view->win)
+ die("Failed to create %s view", view->name);
+
+ scrollok(view->win, TRUE);
+
+ view->title = newwin(1, 0, offset + view->height, 0);
+ if (!view->title)
+ die("Failed to create title window");
+
+ } else {
+ wresize(view->win, view->height, view->width);
+ mvwin(view->win, offset, 0);
+ mvwin(view->title, offset + view->height, 0);
+ }
+
+ offset += view->height + 1;
+ }
+}
+
+static void
+redraw_display(void)
+{
+ struct view *view;
+ int i;
+
+ foreach_displayed_view (view, i) {
+ redraw_view(view);
+ update_view_title(view);
+ }
+}
+
+static void
+update_display_cursor(struct view *view)
+{
+ /* Move the cursor to the right-most column of the cursor line.
+ *
+ * XXX: This could turn out to be a bit expensive, but it ensures that
+ * the cursor does not jump around. */
+ if (view->lines) {
+ wmove(view->win, view->lineno - view->offset, view->width - 1);
+ wrefresh(view->win);
+ }
+}
+
+/*
+ * Navigation
+ */
+
+/* Scrolling backend */
+static void
+do_scroll_view(struct view *view, int lines)
+{
+ bool redraw_current_line = FALSE;
+
+ /* The rendering expects the new offset. */
+ view->offset += lines;
+
+ assert(0 <= view->offset && view->offset < view->lines);
+ assert(lines);
+
+ /* Move current line into the view. */
+ if (view->lineno < view->offset) {
+ view->lineno = view->offset;
+ redraw_current_line = TRUE;
+ } else if (view->lineno >= view->offset + view->height) {
+ view->lineno = view->offset + view->height - 1;
+ redraw_current_line = TRUE;
+ }
+
+ assert(view->offset <= view->lineno && view->lineno < view->lines);
+
+ /* Redraw the whole screen if scrolling is pointless. */
+ if (view->height < ABS(lines)) {
+ redraw_view(view);
+
+ } else {
+ int line = lines > 0 ? view->height - lines : 0;
+ int end = line + ABS(lines);
+
+ wscrl(view->win, lines);
+
+ for (; line < end; line++) {
+ if (!draw_view_line(view, line))
+ break;
+ }
+
+ if (redraw_current_line)
+ draw_view_line(view, view->lineno - view->offset);
+ }
+
+ redrawwin(view->win);
+ wrefresh(view->win);
+ report("");
+}
+
+/* Scroll frontend */
+static void
+scroll_view(struct view *view, enum request request)
+{
+ int lines = 1;
+
+ assert(view_is_displayed(view));
+
+ switch (request) {
+ case REQ_SCROLL_PAGE_DOWN:
+ lines = view->height;
+ case REQ_SCROLL_LINE_DOWN:
+ if (view->offset + lines > view->lines)
+ lines = view->lines - view->offset;
+
+ if (lines == 0 || view->offset + view->height >= view->lines) {
+ report("Cannot scroll beyond the last line");
+ return;
+ }
+ break;
+
+ case REQ_SCROLL_PAGE_UP:
+ lines = view->height;
+ case REQ_SCROLL_LINE_UP:
+ if (lines > view->offset)
+ lines = view->offset;
+
+ if (lines == 0) {
+ report("Cannot scroll beyond the first line");
+ return;
+ }
+
+ lines = -lines;
+ break;
+
+ default:
+ die("request %d not handled in switch", request);
+ }
+
+ do_scroll_view(view, lines);
+}
+
+/* Cursor moving */
+static void
+move_view(struct view *view, enum request request)
+{
+ int scroll_steps = 0;
+ int steps;
+
+ switch (request) {
+ case REQ_MOVE_FIRST_LINE:
+ steps = -view->lineno;
+ break;
+
+ case REQ_MOVE_LAST_LINE:
+ steps = view->lines - view->lineno - 1;
+ break;
+
+ case REQ_MOVE_PAGE_UP:
+ steps = view->height > view->lineno
+ ? -view->lineno : -view->height;
+ break;
+
+ case REQ_MOVE_PAGE_DOWN:
+ steps = view->lineno + view->height >= view->lines
+ ? view->lines - view->lineno - 1 : view->height;
+ break;
+
+ case REQ_MOVE_UP:
+ steps = -1;
+ break;
+
+ case REQ_MOVE_DOWN:
+ steps = 1;
+ break;
+
+ default:
+ die("request %d not handled in switch", request);
+ }
+
+ if (steps <= 0 && view->lineno == 0) {
+ report("Cannot move beyond the first line");
+ return;
+
+ } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
+ report("Cannot move beyond the last line");
+ return;
+ }
+
+ /* Move the current line */
+ view->lineno += steps;
+ assert(0 <= view->lineno && view->lineno < view->lines);
+
+ /* Check whether the view needs to be scrolled */
+ if (view->lineno < view->offset ||
+ view->lineno >= view->offset + view->height) {
+ scroll_steps = steps;
+ if (steps < 0 && -steps > view->offset) {
+ scroll_steps = -view->offset;
+
+ } else if (steps > 0) {
+ if (view->lineno == view->lines - 1 &&
+ view->lines > view->height) {
+ scroll_steps = view->lines - view->offset - 1;
+ if (scroll_steps >= view->height)
+ scroll_steps -= view->height - 1;
+ }
+ }
+ }
+
+ if (!view_is_displayed(view)) {
+ view->offset += scroll_steps;
+ assert(0 <= view->offset && view->offset < view->lines);
+ view->ops->select(view, &view->line[view->lineno]);
+ return;
+ }
+
+ /* Repaint the old "current" line if we be scrolling */
+ if (ABS(steps) < view->height)
+ draw_view_line(view, view->lineno - steps - view->offset);
+
+ if (scroll_steps) {
+ do_scroll_view(view, scroll_steps);
+ return;
+ }
+
+ /* Draw the current line */
+ draw_view_line(view, view->lineno - view->offset);
+
+ redrawwin(view->win);
+ wrefresh(view->win);
+ report("");
+}
+
+
+/*
+ * Searching
+ */
+
+static void search_view(struct view *view, enum request request);
+
+static bool
+find_next_line(struct view *view, unsigned long lineno, struct line *line)
+{
+ assert(view_is_displayed(view));
+
+ if (!view->ops->grep(view, line))
+ return FALSE;
+
+ if (lineno - view->offset >= view->height) {
+ view->offset = lineno;
+ view->lineno = lineno;
+ redraw_view(view);
+
+ } else {
+ unsigned long old_lineno = view->lineno - view->offset;
+
+ view->lineno = lineno;
+ draw_view_line(view, old_lineno);
+
+ draw_view_line(view, view->lineno - view->offset);
+ redrawwin(view->win);
+ wrefresh(view->win);
+ }
+
+ report("Line %ld matches '%s'", lineno + 1, view->grep);
+ return TRUE;
+}
+
+static void
+find_next(struct view *view, enum request request)
+{
+ unsigned long lineno = view->lineno;
+ int direction;
+
+ if (!*view->grep) {
+ if (!*opt_search)
+ report("No previous search");
+ else
+ search_view(view, request);
+ return;
+ }
+
+ switch (request) {
+ case REQ_SEARCH:
+ case REQ_FIND_NEXT:
+ direction = 1;
+ break;
+
+ case REQ_SEARCH_BACK:
+ case REQ_FIND_PREV:
+ direction = -1;
+ break;
+
+ default:
+ return;
+ }
+
+ if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
+ lineno += direction;
+
+ /* Note, lineno is unsigned long so will wrap around in which case it
+ * will become bigger than view->lines. */
+ for (; lineno < view->lines; lineno += direction) {
+ struct line *line = &view->line[lineno];
+
+ if (find_next_line(view, lineno, line))
+ return;
+ }
+
+ report("No match found for '%s'", view->grep);
+}
+
+static void
+search_view(struct view *view, enum request request)
+{
+ int regex_err;
+
+ if (view->regex) {
+ regfree(view->regex);
+ *view->grep = 0;
+ } else {
+ view->regex = calloc(1, sizeof(*view->regex));
+ if (!view->regex)
+ return;
+ }
+
+ regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
+ if (regex_err != 0) {
+ char buf[SIZEOF_STR] = "unknown error";
+
+ regerror(regex_err, view->regex, buf, sizeof(buf));
+ report("Search failed: %s", buf);
+ return;
+ }
+
+ string_copy(view->grep, opt_search);
+
+ find_next(view, request);
+}
+
+/*
+ * Incremental updating
+ */
+
+static void
+end_update(struct view *view)
+{
+ if (!view->pipe)
+ return;
+ set_nonblocking_input(FALSE);
+ if (view->pipe == stdin)
+ fclose(view->pipe);
+ else
+ pclose(view->pipe);
+ view->pipe = NULL;
+}
+
+static bool
+begin_update(struct view *view)
+{
+ if (view->pipe)
+ end_update(view);