+ 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 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);
+
+ /* Repaint the old "current" line if we be scrolling */
+ if (ABS(steps) < view->height) {
+ int prev_lineno = view->lineno - steps - view->offset;
+
+ wmove(view->win, prev_lineno, 0);
+ wclrtoeol(view->win);
+ view->ops->draw(view, prev_lineno);
+ }
+
+ /* Check whether the view needs to be scrolled */
+ if (view->lineno < view->offset ||
+ view->lineno >= view->offset + view->height) {
+ if (steps < 0 && -steps > view->offset) {
+ steps = -view->offset;
+
+ } 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;
+ }
+ }
+
+ do_scroll_view(view, steps);
+ return;
+ }
+
+ /* Draw the current line */
+ view->ops->draw(view, view->lineno - view->offset);
+
+ redrawwin(view->win);
+ wrefresh(view->win);
+ report("");
+}
+
+
+/*
+ * Incremental updating
+ */
+
+static bool
+begin_update(struct view *view)
+{
+ char *id = view->id;
+
+ if (opt_cmd[0]) {
+ string_copy(view->cmd, opt_cmd);
+ opt_cmd[0] = 0;
+ /* When running random commands, the view ref could have become
+ * invalid so clear it. */
+ view->ref[0] = 0;
+ } else {
+ char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
+
+ if (snprintf(view->cmd, sizeof(view->cmd), format,
+ id, id, id, id, id) >= sizeof(view->cmd))
+ return FALSE;
+ }
+
+ /* Special case for the pager view. */
+ if (opt_pipe) {
+ view->pipe = opt_pipe;
+ opt_pipe = NULL;
+ } else {
+ view->pipe = popen(view->cmd, "r");
+ }
+
+ if (!view->pipe)
+ return FALSE;
+
+ set_nonblocking_input(TRUE);
+
+ view->offset = 0;
+ view->lines = 0;
+ view->lineno = 0;
+ string_copy(view->vid, id);
+
+ if (view->line) {
+ int i;
+
+ for (i = 0; i < view->lines; i++)
+ if (view->line[i])
+ free(view->line[i]);
+
+ free(view->line);
+ view->line = NULL;
+ }
+
+ view->start_time = time(NULL);
+
+ return TRUE;
+}
+
+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
+update_view(struct view *view)
+{
+ char buffer[BUFSIZ];
+ char *line;
+ void **tmp;
+ /* The number of lines to read. If too low it will cause too much
+ * redrawing (and possible flickering), if too high responsiveness
+ * will suffer. */
+ unsigned long lines = view->height;
+ int redraw_from = -1;
+
+ if (!view->pipe)
+ return TRUE;
+
+ /* Only redraw if lines are visible. */
+ if (view->offset + view->height >= view->lines)
+ redraw_from = view->lines - view->offset;
+
+ 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 = strlen(line);
+
+ if (linelen)
+ line[linelen - 1] = 0;
+
+ if (!view->ops->read(view, line))
+ goto alloc_error;
+
+ if (lines-- == 1)
+ break;
+ }
+
+ {
+ int digits;
+
+ lines = view->lines;
+ for (digits = 0; lines; digits++)
+ lines /= 10;
+
+ /* Keep the displayed view in sync with line number scaling. */
+ if (digits != view->digits) {
+ view->digits = digits;
+ redraw_from = 0;
+ }
+ }
+
+ if (redraw_from >= 0) {
+ /* If this is an incremental update, redraw the previous line
+ * since for commits some members could have changed when
+ * loading the main view. */
+ if (redraw_from > 0)
+ redraw_from--;
+
+ /* Incrementally draw avoids flickering. */
+ redraw_view_from(view, redraw_from);
+ }
+
+ /* Update the title _after_ the redraw so that if the redraw picks up a
+ * commit reference in view->ref it'll be available here. */
+ update_view_title(view);
+
+ if (ferror(view->pipe)) {
+ report("Failed to read: %s", strerror(errno));
+ goto end;
+
+ } else if (feof(view->pipe)) {
+ time_t secs = time(NULL) - view->start_time;
+
+ if (view == VIEW(REQ_VIEW_HELP)) {
+ report("%s", HELP);
+ goto end;
+ }
+
+ report("Loaded %d lines in %ld second%s", view->lines, secs,
+ secs == 1 ? "" : "s");
+ goto end;
+ }
+
+ return TRUE;
+
+alloc_error:
+ report("Allocation failure");
+
+end:
+ end_update(view);
+ return FALSE;
+}
+
+enum open_flags {
+ OPEN_DEFAULT = 0, /* Use default view switching. */
+ OPEN_SPLIT = 1, /* Split current view. */
+ OPEN_BACKGROUNDED = 2, /* Backgrounded. */
+ OPEN_RELOAD = 4, /* Reload view even if it is the current. */
+};
+
+static void
+open_view(struct view *prev, enum request request, enum open_flags flags)
+{
+ bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
+ bool split = !!(flags & OPEN_SPLIT);
+ bool reload = !!(flags & OPEN_RELOAD);
+ struct view *view = VIEW(request);
+ struct view *displayed;
+ int nviews;
+
+ /* Cycle between displayed views and count the views. */
+ foreach_view (displayed, nviews) {
+ if (prev != view &&
+ view == displayed &&
+ !strcmp(view->vid, prev->vid)) {
+ current_view = nviews;
+ /* Blur out the title of the previous view. */
+ update_view_title(prev);
+ report("Switching to %s view", view->name);
+ return;
+ }
+ }
+
+ if (view == prev && nviews == 1 && !reload) {
+ report("Already in %s view", view->name);
+ return;
+ }
+
+ if ((reload || strcmp(view->vid, view->id)) &&
+ !begin_update(view)) {
+ report("Failed to load %s view", view->name);
+ return;
+ }
+
+ if (split) {
+ display[current_view + 1] = view;
+ if (!backgrounded)
+ current_view++;
+ } else {
+ /* Maximize the current view. */
+ memset(display, 0, sizeof(display));
+ current_view = 0;
+ display[current_view] = view;
+ }
+
+ resize_display();
+
+ if (split && prev->lineno - prev->offset >= prev->height) {
+ /* Take the title line into account. */
+ int lines = prev->lineno - prev->offset - prev->height + 1;
+
+ /* Scroll the view that was split if the current line is
+ * outside the new limited view. */
+ do_scroll_view(prev, lines);
+ }
+
+ if (prev && view != prev) {
+ /* "Blur" the previous view. */
+ update_view_title(prev);
+
+ /* Continue loading split views in the background. */
+ if (!split)
+ end_update(prev);
+ }
+
+ if (view->pipe) {
+ /* Clear the old view and let the incremental updating refill
+ * the screen. */
+ wclear(view->win);
+ report("Loading...");
+ } else {
+ redraw_view(view);
+ report("");
+ }
+}
+
+
+/*
+ * User request switch noodle
+ */
+
+static int
+view_driver(struct view *view, enum request request)
+{
+ int i;
+
+ switch (request) {
+ case REQ_MOVE_UP:
+ case REQ_MOVE_DOWN:
+ case REQ_MOVE_PAGE_UP:
+ case REQ_MOVE_PAGE_DOWN:
+ case REQ_MOVE_FIRST_LINE:
+ case REQ_MOVE_LAST_LINE:
+ move_view(view, request);
+ break;
+
+ case REQ_SCROLL_LINE_DOWN:
+ case REQ_SCROLL_LINE_UP:
+ case REQ_SCROLL_PAGE_DOWN:
+ case REQ_SCROLL_PAGE_UP:
+ scroll_view(view, request);
+ break;
+
+ case REQ_VIEW_MAIN:
+ case REQ_VIEW_DIFF:
+ case REQ_VIEW_LOG:
+ case REQ_VIEW_HELP:
+ case REQ_VIEW_PAGER:
+ open_view(view, request, OPEN_DEFAULT);
+ break;
+
+ case REQ_ENTER:
+ if (!view->lines) {
+ report("Nothing to enter");
+ break;
+ }
+ return view->ops->enter(view);
+
+ case REQ_VIEW_NEXT:
+ {
+ int nviews = display[1] ? 2 : 1;
+ int next_view = (current_view + 1) % nviews;
+
+ if (next_view == current_view) {
+ report("Only one view is displayed");
+ break;
+ }
+
+ current_view = next_view;
+ /* Blur out the title of the previous view. */
+ update_view_title(view);
+ report("Switching to %s view", display[current_view]->name);
+ break;
+ }
+ case REQ_TOGGLE_LINE_NUMBERS:
+ opt_line_number = !opt_line_number;
+ redraw_view(view);
+ break;
+
+ case REQ_PROMPT:
+ /* Always reload^Wrerun commands from the prompt. */
+ open_view(view, opt_request, OPEN_RELOAD);
+ break;
+
+ case REQ_STOP_LOADING:
+ foreach_view (view, i) {
+ if (view->pipe)
+ report("Stopped loaded of %s view", view->name),
+ end_update(view);
+ }
+ break;
+
+ case REQ_SHOW_VERSION:
+ report("Version: %s", VERSION);
+ return TRUE;
+
+ case REQ_SCREEN_RESIZE:
+ resize_display();
+ /* Fall-through */
+ case REQ_SCREEN_REDRAW:
+ foreach_view (view, i) {
+ redraw_view(view);
+ update_view_title(view);
+ }
+ break;
+
+ case REQ_SCREEN_UPDATE:
+ doupdate();
+ return TRUE;
+
+ case REQ_QUIT:
+ return FALSE;
+
+ default:
+ /* An unknown key will show most commonly used commands. */
+ report("%s", HELP);
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * View backend handlers
+ */
+
+static bool
+pager_draw(struct view *view, unsigned int lineno)
+{
+ enum line_type type;
+ char *line;
+ int linelen;
+ int attr;
+
+ 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) {
+ string_copy(view->ref, line + 7);
+ string_copy(ref_commit, view->ref);