+ /* 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)) {
+ char *msg = TIG_HELP;
+
+ if (view->lines == 0) {
+ /* Slightly ugly, but abusing view->ref keeps
+ * the error message. */
+ string_copy(view->ref, "No help available");
+ msg = "The tig(1) manpage is not installed";
+ }
+
+ report("%s", msg);
+ 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("");
+ 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. */
+ if (!backgrounded)
+ 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);
+ if (view == VIEW(REQ_VIEW_HELP))
+ report("%s", TIG_HELP);
+ else
+ report("");
+ }
+
+ /* If the view is backgrounded the above calls to report()
+ * won't redraw the view title. */
+ if (backgrounded)
+ update_view_title(view);
+}
+
+
+/*
+ * 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_MOVE_UP_ENTER:
+ case REQ_MOVE_DOWN_ENTER:
+ move_view(view, request);
+ /* Fall-through */
+
+ 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("");
+ break;
+ }
+ case REQ_TOGGLE_LINE_NUMBERS:
+ opt_line_number = !opt_line_number;
+ redraw_view(view);
+ update_view_title(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 the %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("Unknown key, press 'h' for 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);
+
+ wmove(view->win, lineno, 0);
+
+ if (view->offset + lineno == view->lineno) {
+ if (type == LINE_COMMIT) {
+ string_copy(view->ref, line + 7);
+ string_copy(ref_commit, view->ref);
+ }
+
+ type = LINE_CURSOR;
+ wchgat(view->win, -1, 0, type, NULL);
+ }
+
+ attr = get_line_attr(type);
+ wattrset(view->win, attr);
+
+ linelen = strlen(line);
+
+ if (opt_line_number || opt_tab_size < TABSIZE) {
+ static char spaces[] = " ";
+ int col_offset = 0, col = 0;
+
+ if (opt_line_number) {
+ unsigned long real_lineno = view->offset + lineno + 1;
+
+ if (real_lineno == 1 ||
+ (real_lineno % opt_num_interval) == 0) {
+ wprintw(view->win, "%.*d", view->digits, real_lineno);
+
+ } else {
+ waddnstr(view->win, spaces,
+ MIN(view->digits, STRING_SIZE(spaces)));
+ }
+ waddstr(view->win, ": ");
+ col_offset = view->digits + 2;
+ }
+
+ while (line && col_offset + col < view->width) {
+ int cols_max = view->width - col_offset - col;
+ char *text = line;
+ int cols;
+
+ if (*line == '\t') {
+ assert(sizeof(spaces) > TABSIZE);
+ line++;
+ text = spaces;
+ cols = opt_tab_size - (col % opt_tab_size);
+
+ } else {
+ line = strchr(line, '\t');
+ cols = line ? line - text : strlen(text);
+ }
+
+ waddnstr(view->win, text, MIN(cols, cols_max));
+ col += cols;
+ }
+
+ } else {
+ int col = 0, pos = 0;
+
+ for (; pos < linelen && col < view->width; pos++, col++)
+ if (line[pos] == '\t')
+ col += TABSIZE - (col % TABSIZE) - 1;
+
+ waddnstr(view->win, line, pos);
+ }
+
+ return TRUE;
+}
+
+static bool
+pager_read(struct view *view, char *line)
+{
+ /* Compress empty lines in the help view. */
+ if (view == VIEW(REQ_VIEW_HELP) &&
+ !*line &&
+ view->lines &&
+ !*((char *) view->line[view->lines - 1]))
+ return TRUE;
+
+ view->line[view->lines] = strdup(line);
+ if (!view->line[view->lines])
+ return FALSE;
+
+ view->lines++;
+ return TRUE;
+}
+
+static bool
+pager_enter(struct view *view)
+{
+ char *line = view->line[view->lineno];
+
+ if (get_line_type(line) == LINE_COMMIT) {
+ if (view == VIEW(REQ_VIEW_LOG))
+ open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT | OPEN_BACKGROUNDED);
+ else
+ open_view(view, REQ_VIEW_DIFF, OPEN_DEFAULT);
+ }
+
+ return TRUE;
+}
+
+static struct view_ops pager_ops = {
+ "line",
+ pager_draw,
+ pager_read,
+ pager_enter,
+};
+