+ if (!strcmp(argv[0], "line-number-interval")) {
+ opt_num_interval = atoi(argv[2]);
+ return OK;
+ }
+
+ if (!strcmp(argv[0], "tab-size")) {
+ opt_tab_size = atoi(argv[2]);
+ return OK;
+ }
+
+ if (!strcmp(argv[0], "commit-encoding")) {
+ char *arg = argv[2];
+ int delimiter = *arg;
+ int i;
+
+ switch (delimiter) {
+ case '"':
+ case '\'':
+ for (arg++, i = 0; arg[i]; i++)
+ if (arg[i] == delimiter) {
+ arg[i] = 0;
+ break;
+ }
+ default:
+ string_ncopy(opt_encoding, arg, strlen(arg));
+ return OK;
+ }
+ }
+
+ config_msg = "Unknown variable name";
+ return ERR;
+}
+
+/* Wants: mode request key */
+static int
+option_bind_command(int argc, char *argv[])
+{
+ enum request request;
+ int keymap;
+ int key;
+
+ if (argc < 3) {
+ config_msg = "Wrong number of arguments given to bind command";
+ return ERR;
+ }
+
+ if (set_keymap(&keymap, argv[0]) == ERR) {
+ config_msg = "Unknown key map";
+ return ERR;
+ }
+
+ key = get_key_value(argv[1]);
+ if (key == ERR) {
+ config_msg = "Unknown key";
+ return ERR;
+ }
+
+ request = get_request(argv[2]);
+ if (request == REQ_NONE) {
+ const char *obsolete[] = { "cherry-pick" };
+ size_t namelen = strlen(argv[2]);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
+ if (namelen == strlen(obsolete[i]) &&
+ !string_enum_compare(obsolete[i], argv[2], namelen)) {
+ config_msg = "Obsolete request name";
+ return ERR;
+ }
+ }
+ }
+ if (request == REQ_NONE && *argv[2]++ == '!')
+ request = add_run_request(keymap, key, argc - 2, argv + 2);
+ if (request == REQ_NONE) {
+ config_msg = "Unknown request name";
+ return ERR;
+ }
+
+ add_keybinding(keymap, request, key);
+
+ return OK;
+}
+
+static int
+set_option(char *opt, char *value)
+{
+ char *argv[16];
+ int valuelen;
+ int argc = 0;
+
+ /* Tokenize */
+ while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
+ argv[argc++] = value;
+ value += valuelen;
+
+ /* Nothing more to tokenize or last available token. */
+ if (!*value || argc >= ARRAY_SIZE(argv))
+ break;
+
+ *value++ = 0;
+ while (isspace(*value))
+ value++;
+ }
+
+ if (!strcmp(opt, "color"))
+ return option_color_command(argc, argv);
+
+ if (!strcmp(opt, "set"))
+ return option_set_command(argc, argv);
+
+ if (!strcmp(opt, "bind"))
+ return option_bind_command(argc, argv);
+
+ config_msg = "Unknown option command";
+ return ERR;
+}
+
+static int
+read_option(char *opt, size_t optlen, char *value, size_t valuelen)
+{
+ int status = OK;
+
+ config_lineno++;
+ config_msg = "Internal error";
+
+ /* Check for comment markers, since read_properties() will
+ * only ensure opt and value are split at first " \t". */
+ optlen = strcspn(opt, "#");
+ if (optlen == 0)
+ return OK;
+
+ if (opt[optlen] != 0) {
+ config_msg = "No option value";
+ status = ERR;
+
+ } else {
+ /* Look for comment endings in the value. */
+ size_t len = strcspn(value, "#");
+
+ if (len < valuelen) {
+ valuelen = len;
+ value[valuelen] = 0;
+ }
+
+ status = set_option(opt, value);
+ }
+
+ if (status == ERR) {
+ fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
+ config_lineno, (int) optlen, opt, config_msg);
+ config_errors = TRUE;
+ }
+
+ /* Always keep going if errors are encountered. */
+ return OK;
+}
+
+static void
+load_option_file(const char *path)
+{
+ FILE *file;
+
+ /* It's ok that the file doesn't exist. */
+ file = fopen(path, "r");
+ if (!file)
+ return;
+
+ config_lineno = 0;
+ config_errors = FALSE;
+
+ if (read_properties(file, " \t", read_option) == ERR ||
+ config_errors == TRUE)
+ fprintf(stderr, "Errors while loading %s.\n", path);
+}
+
+static int
+load_options(void)
+{
+ char *home = getenv("HOME");
+ char *tigrc_user = getenv("TIGRC_USER");
+ char *tigrc_system = getenv("TIGRC_SYSTEM");
+ char buf[SIZEOF_STR];
+
+ add_builtin_run_requests();
+
+ if (!tigrc_system) {
+ if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
+ return ERR;
+ tigrc_system = buf;
+ }
+ load_option_file(tigrc_system);
+
+ if (!tigrc_user) {
+ if (!home || !string_format(buf, "%s/.tigrc", home))
+ return ERR;
+ tigrc_user = buf;
+ }
+ load_option_file(tigrc_user);
+
+ 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 */
+ size_t lines; /* Total number of lines */
+ struct line *line; /* Line index */
+ size_t line_alloc; /* Total number of allocated lines */
+ size_t line_size; /* Total number of used 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 int
+draw_text(struct view *view, const char *string, int max_len, int col,
+ bool use_tilde, int tilde_attr)
+{
+ int len = 0;
+ int trimmed = FALSE;
+
+ if (max_len <= 0)
+ return 0;
+
+ if (opt_utf8) {
+ len = utf8_length(string, max_len, &trimmed, use_tilde);
+ } else {
+ len = strlen(string);
+ if (len > max_len) {
+ if (use_tilde) {
+ max_len -= 1;
+ }
+ len = max_len;
+ trimmed = TRUE;
+ }
+ }
+
+ waddnstr(view->win, string, len);
+ if (trimmed && use_tilde) {
+ if (tilde_attr != -1)
+ wattrset(view->win, tilde_attr);
+ waddch(view->win, '~');
+ len++;
+ }
+
+ return len;
+}
+
+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);
+
+ if (opt_cmd[0]) {
+ string_copy(view->cmd, opt_cmd);
+ opt_cmd[0] = 0;
+ /* When running random commands, initially show the
+ * command in the title. However, it maybe later be
+ * overwritten if a commit line is selected. */
+ if (view == VIEW(REQ_VIEW_PAGER))
+ string_copy(view->ref, view->cmd);
+ else
+ view->ref[0] = 0;
+
+ } else if (view == VIEW(REQ_VIEW_TREE)) {
+ const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
+ char path[SIZEOF_STR];
+
+ if (strcmp(view->vid, view->id))
+ opt_path[0] = path[0] = 0;
+ else if (sq_quote(path, 0, opt_path) >= sizeof(path))
+ return FALSE;
+
+ if (!string_format(view->cmd, format, view->id, path))
+ return FALSE;
+
+ } else {
+ const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
+ const char *id = view->id;
+
+ if (!string_format(view->cmd, format, id, id, id, id, id))
+ return FALSE;
+
+ /* Put the current ref_* value to the view title ref
+ * member. This is needed by the blob view. Most other
+ * views sets it automatically after loading because the
+ * first line is a commit line. */
+ string_copy_rev(view->ref, view->id);
+ }
+
+ /* 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_rev(view->vid, view->id);
+
+ if (view->line) {
+ int i;
+
+ for (i = 0; i < view->lines; i++)
+ if (view->line[i].data)
+ free(view->line[i].data);
+
+ free(view->line);
+ view->line = NULL;
+ }
+
+ view->start_time = time(NULL);
+
+ return TRUE;
+}
+
+#define ITEM_CHUNK_SIZE 256
+static void *
+realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
+{
+ size_t num_chunks = *size / ITEM_CHUNK_SIZE;
+ size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
+
+ if (mem == NULL || num_chunks != num_chunks_new) {
+ *size = num_chunks_new * ITEM_CHUNK_SIZE;
+ mem = realloc(mem, *size * item_size);
+ }
+
+ return mem;
+}
+
+static struct line *
+realloc_lines(struct view *view, size_t line_size)
+{
+ size_t alloc = view->line_alloc;
+ struct line *tmp = realloc_items(view->line, &alloc, line_size,
+ sizeof(*view->line));
+
+ if (!tmp)
+ return NULL;
+
+ view->line = tmp;
+ view->line_alloc = alloc;
+ view->line_size = line_size;
+ return view->line;
+}
+
+static bool
+update_view(struct view *view)
+{
+ char in_buffer[BUFSIZ];
+ char out_buffer[BUFSIZ * 2];
+ char *line;
+ /* 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;
+
+ /* FIXME: This is probably not perfect for backgrounded views. */
+ if (!realloc_lines(view, view->lines + lines))
+ goto alloc_error;
+
+ while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
+ size_t linelen = strlen(line);
+
+ if (linelen)
+ line[linelen - 1] = 0;
+
+ if (opt_iconv != ICONV_NONE) {
+ ICONV_CONST char *inbuf = line;
+ size_t inlen = linelen;
+
+ char *outbuf = out_buffer;
+ size_t outlen = sizeof(out_buffer);
+
+ size_t ret;
+
+ ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
+ if (ret != (size_t) -1) {
+ line = out_buffer;
+ linelen = strlen(out_buffer);
+ }
+ }
+
+ 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 (!view_is_displayed(view))
+ goto check_pipe;
+
+ if (view == VIEW(REQ_VIEW_TREE)) {
+ /* Clear the view and redraw everything since the tree sorting
+ * might have rearranged things. */
+ redraw_view(view);
+
+ } else 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--;
+
+ /* Since revision graph visualization requires knowledge
+ * about the parent commit, it causes a further one-off
+ * needed to be redrawn for incremental updates. */
+ if (redraw_from > 0 && opt_rev_graph)
+ 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);
+
+check_pipe:
+ if (ferror(view->pipe)) {
+ report("Failed to read: %s", strerror(errno));
+ goto end;
+
+ } else if (feof(view->pipe)) {
+ report("");
+ goto end;
+ }
+
+ return TRUE;
+
+alloc_error:
+ report("Allocation failure");
+
+end:
+ view->ops->read(view, NULL);
+ end_update(view);
+ return FALSE;
+}
+
+static struct line *
+add_line_data(struct view *view, void *data, enum line_type type)
+{
+ struct line *line = &view->line[view->lines++];
+
+ memset(line, 0, sizeof(*line));
+ line->type = type;
+ line->data = data;
+
+ return line;
+}
+
+static struct line *
+add_line_text(struct view *view, char *data, enum line_type type)
+{
+ if (data)
+ data = strdup(data);
+
+ return data ? add_line_data(view, data, type) : NULL;
+}
+
+
+/*
+ * View opening
+ */
+
+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);
+ int nviews = displayed_views();
+ struct view *base_view = display[0];
+
+ if (view == prev && nviews == 1 && !reload) {
+ report("Already in %s view", view->name);
+ return;
+ }
+
+ if (view->ops->open) {
+ if (!view->ops->open(view)) {
+ report("Failed to load %s view", view->name);
+ return;
+ }
+
+ } else if ((reload || strcmp(view->vid, view->id)) &&
+ !begin_update(view)) {
+ report("Failed to load %s view", view->name);
+ return;
+ }
+
+ if (split) {
+ display[1] = view;
+ if (!backgrounded)
+ current_view = 1;
+ } else {
+ /* Maximize the current view. */
+ memset(display, 0, sizeof(display));
+ current_view = 0;
+ display[current_view] = view;
+ }
+
+ /* Resize the view when switching between split- and full-screen,
+ * or when switching between two different full-screen views. */
+ if (nviews != displayed_views() ||
+ (nviews == 1 && base_view != display[0]))
+ 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) {
+ if (split && !backgrounded) {
+ /* "Blur" the previous view. */
+ update_view_title(prev);
+ }
+
+ view->parent = prev;
+ }
+
+ if (view->pipe && view->lines == 0) {
+ /* Clear the old view and let the incremental updating refill
+ * the screen. */
+ wclear(view->win);
+ report("");
+ } else {
+ redraw_view(view);
+ report("");
+ }
+
+ /* If the view is backgrounded the above calls to report()
+ * won't redraw the view title. */
+ if (backgrounded)
+ update_view_title(view);
+}
+
+static void
+open_external_viewer(const char *cmd)
+{
+ def_prog_mode(); /* save current tty modes */
+ endwin(); /* restore original tty modes */
+ system(cmd);
+ fprintf(stderr, "Press Enter to continue");
+ getc(stdin);
+ reset_prog_mode();
+ redraw_display();
+}
+
+static void
+open_mergetool(const char *file)
+{
+ char cmd[SIZEOF_STR];
+ char file_sq[SIZEOF_STR];
+
+ if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
+ string_format(cmd, "git mergetool %s", file_sq)) {
+ open_external_viewer(cmd);
+ }
+}
+
+static void
+open_editor(bool from_root, const char *file)
+{
+ char cmd[SIZEOF_STR];
+ char file_sq[SIZEOF_STR];
+ char *editor;
+ char *prefix = from_root ? opt_cdup : "";
+
+ editor = getenv("GIT_EDITOR");
+ if (!editor && *opt_editor)
+ editor = opt_editor;
+ if (!editor)
+ editor = getenv("VISUAL");
+ if (!editor)
+ editor = getenv("EDITOR");
+ if (!editor)
+ editor = "vi";
+
+ if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
+ string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
+ open_external_viewer(cmd);
+ }
+}
+
+static void
+open_run_request(enum request request)
+{
+ struct run_request *req = get_run_request(request);
+ char buf[SIZEOF_STR * 2];
+ size_t bufpos;
+ char *cmd;
+
+ if (!req) {
+ report("Unknown run request");
+ return;
+ }
+
+ bufpos = 0;
+ cmd = req->cmd;
+
+ while (cmd) {
+ char *next = strstr(cmd, "%(");
+ int len = next - cmd;
+ char *value;
+
+ if (!next) {
+ len = strlen(cmd);
+ value = "";
+
+ } else if (!strncmp(next, "%(head)", 7)) {
+ value = ref_head;
+
+ } else if (!strncmp(next, "%(commit)", 9)) {
+ value = ref_commit;
+
+ } else if (!strncmp(next, "%(blob)", 7)) {
+ value = ref_blob;
+
+ } else {
+ report("Unknown replacement in run request: `%s`", req->cmd);
+ return;
+ }
+
+ if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
+ return;
+
+ if (next)
+ next = strchr(next, ')') + 1;
+ cmd = next;
+ }
+
+ open_external_viewer(buf);
+}
+
+/*
+ * User request switch noodle
+ */
+
+static int
+view_driver(struct view *view, enum request request)
+{
+ int i;
+
+ if (request == REQ_NONE) {
+ doupdate();
+ return TRUE;
+ }
+
+ if (request > REQ_NONE) {
+ open_run_request(request);
+ return TRUE;
+ }
+
+ if (view && view->lines) {
+ request = view->ops->request(view, request, &view->line[view->lineno]);
+ if (request == REQ_NONE)
+ return TRUE;
+ }
+
+ 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_BLOB:
+ if (!ref_blob[0]) {
+ report("No file chosen, press %s to open tree view",
+ get_key(REQ_VIEW_TREE));
+ break;
+ }
+ open_view(view, request, OPEN_DEFAULT);
+ break;
+
+ case REQ_VIEW_PAGER:
+ if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
+ report("No pager content, press %s to run command from prompt",
+ get_key(REQ_PROMPT));
+ break;
+ }
+ open_view(view, request, OPEN_DEFAULT);
+ break;
+
+ case REQ_VIEW_STAGE:
+ if (!VIEW(REQ_VIEW_STAGE)->lines) {
+ report("No stage content, press %s to open the status view and choose file",
+ get_key(REQ_VIEW_STATUS));
+ break;
+ }
+ open_view(view, request, OPEN_DEFAULT);
+ break;
+
+ case REQ_VIEW_STATUS:
+ if (opt_is_inside_work_tree == FALSE) {
+ report("The status view requires a working tree");
+ break;
+ }
+ open_view(view, request, OPEN_DEFAULT);
+ break;
+
+ case REQ_VIEW_MAIN:
+ case REQ_VIEW_DIFF:
+ case REQ_VIEW_LOG:
+ case REQ_VIEW_TREE:
+ case REQ_VIEW_HELP:
+ open_view(view, request, OPEN_DEFAULT);
+ break;
+
+ case REQ_NEXT:
+ case REQ_PREVIOUS:
+ request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
+
+ if ((view == VIEW(REQ_VIEW_DIFF) &&
+ view->parent == VIEW(REQ_VIEW_MAIN)) ||
+ (view == VIEW(REQ_VIEW_STAGE) &&
+ view->parent == VIEW(REQ_VIEW_STATUS)) ||
+ (view == VIEW(REQ_VIEW_BLOB) &&
+ view->parent == VIEW(REQ_VIEW_TREE))) {
+ int line;
+
+ view = view->parent;
+ line = view->lineno;
+ move_view(view, request);
+ if (view_is_displayed(view))
+ update_view_title(view);
+ if (line != view->lineno)
+ view->ops->request(view, REQ_ENTER,
+ &view->line[view->lineno]);
+
+ } else {
+ move_view(view, request);
+ }
+ break;
+
+ case REQ_VIEW_NEXT:
+ {
+ int nviews = displayed_views();
+ 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_REFRESH:
+ report("Refreshing is not yet supported for the %s view", view->name);
+ break;
+
+ case REQ_TOGGLE_LINENO:
+ opt_line_number = !opt_line_number;
+ redraw_display();
+ break;
+
+ case REQ_TOGGLE_DATE:
+ opt_date = !opt_date;
+ redraw_display();
+ break;
+
+ case REQ_TOGGLE_AUTHOR:
+ opt_author = !opt_author;
+ redraw_display();
+ break;
+
+ case REQ_TOGGLE_REV_GRAPH:
+ opt_rev_graph = !opt_rev_graph;
+ redraw_display();
+ break;
+
+ case REQ_TOGGLE_REFS:
+ opt_show_refs = !opt_show_refs;
+ redraw_display();
+ break;
+
+ case REQ_PROMPT:
+ /* Always reload^Wrerun commands from the prompt. */
+ open_view(view, opt_request, OPEN_RELOAD);
+ break;
+
+ case REQ_SEARCH:
+ case REQ_SEARCH_BACK:
+ search_view(view, request);
+ break;
+
+ case REQ_FIND_NEXT:
+ case REQ_FIND_PREV:
+ find_next(view, request);
+ break;
+
+ case REQ_STOP_LOADING:
+ for (i = 0; i < ARRAY_SIZE(views); i++) {
+ view = &views[i];
+ if (view->pipe)
+ report("Stopped loading the %s view", view->name),
+ end_update(view);
+ }
+ break;
+
+ case REQ_SHOW_VERSION:
+ report("tig-%s (built %s)", TIG_VERSION, __DATE__);
+ return TRUE;
+
+ case REQ_SCREEN_RESIZE:
+ resize_display();
+ /* Fall-through */
+ case REQ_SCREEN_REDRAW:
+ redraw_display();
+ break;
+
+ case REQ_EDIT:
+ report("Nothing to edit");
+ break;
+
+
+ case REQ_ENTER:
+ report("Nothing to enter");
+ break;
+
+
+ case REQ_VIEW_CLOSE:
+ /* XXX: Mark closed views by letting view->parent point to the
+ * view itself. Parents to closed view should never be
+ * followed. */
+ if (view->parent &&
+ view->parent->parent != view->parent) {
+ memset(display, 0, sizeof(display));
+ current_view = 0;
+ display[current_view] = view->parent;
+ view->parent = view;
+ resize_display();
+ redraw_display();
+ break;
+ }
+ /* Fall-through */
+ 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;
+}
+
+
+/*
+ * Pager backend
+ */
+
+static bool
+pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
+{
+ char *text = line->data;
+ enum line_type type = line->type;
+ int attr;
+
+ wmove(view->win, lineno, 0);
+
+ if (selected) {
+ type = LINE_CURSOR;
+ wchgat(view->win, -1, 0, type, NULL);
+ }
+
+ attr = get_line_attr(type);
+ wattrset(view->win, attr);
+
+ 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 (text && col_offset + col < view->width) {
+ int cols_max = view->width - col_offset - col;
+ char *pos = text;
+ int cols;
+
+ if (*text == '\t') {
+ text++;
+ assert(sizeof(spaces) > TABSIZE);
+ pos = spaces;
+ cols = opt_tab_size - (col % opt_tab_size);
+
+ } else {
+ text = strchr(text, '\t');
+ cols = line ? text - pos : strlen(pos);
+ }
+
+ waddnstr(view->win, pos, MIN(cols, cols_max));
+ col += cols;
+ }
+
+ } else {
+ int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
+
+ draw_text(view, text, view->width, 0, TRUE, tilde_attr);
+ }
+
+ return TRUE;
+}
+
+static bool
+add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
+{
+ char refbuf[SIZEOF_STR];
+ char *ref = NULL;
+ FILE *pipe;
+
+ if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
+ return TRUE;
+
+ pipe = popen(refbuf, "r");
+ if (!pipe)
+ return TRUE;
+
+ if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
+ ref = chomp_string(ref);
+ pclose(pipe);
+
+ if (!ref || !*ref)
+ return TRUE;
+
+ /* This is the only fatal call, since it can "corrupt" the buffer. */
+ if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+add_pager_refs(struct view *view, struct line *line)
+{
+ char buf[SIZEOF_STR];
+ char *commit_id = (char *)line->data + STRING_SIZE("commit ");
+ struct ref **refs;
+ size_t bufpos = 0, refpos = 0;
+ const char *sep = "Refs: ";
+ bool is_tag = FALSE;
+
+ assert(line->type == LINE_COMMIT);
+
+ refs = get_refs(commit_id);
+ if (!refs) {
+ if (view == VIEW(REQ_VIEW_DIFF))
+ goto try_add_describe_ref;
+ return;
+ }
+
+ do {
+ struct ref *ref = refs[refpos];
+ char *fmt = ref->tag ? "%s[%s]" :
+ ref->remote ? "%s<%s>" : "%s%s";
+
+ if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
+ return;
+ sep = ", ";
+ if (ref->tag)
+ is_tag = TRUE;
+ } while (refs[refpos++]->next);
+
+ if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
+try_add_describe_ref:
+ /* Add <tag>-g<commit_id> "fake" reference. */
+ if (!add_describe_ref(buf, &bufpos, commit_id, sep))
+ return;
+ }
+
+ if (bufpos == 0)
+ return;
+
+ if (!realloc_lines(view, view->line_size + 1))
+ return;
+
+ add_line_text(view, buf, LINE_PP_REFS);
+}
+
+static bool
+pager_read(struct view *view, char *data)
+{
+ struct line *line;
+
+ if (!data)
+ return TRUE;
+
+ line = add_line_text(view, data, get_line_type(data));
+ if (!line)
+ return FALSE;
+
+ if (line->type == LINE_COMMIT &&
+ (view == VIEW(REQ_VIEW_DIFF) ||
+ view == VIEW(REQ_VIEW_LOG)))
+ add_pager_refs(view, line);
+
+ return TRUE;
+}
+
+static enum request
+pager_request(struct view *view, enum request request, struct line *line)
+{
+ int split = 0;
+
+ if (request != REQ_ENTER)
+ return request;
+
+ if (line->type == LINE_COMMIT &&
+ (view == VIEW(REQ_VIEW_LOG) ||
+ view == VIEW(REQ_VIEW_PAGER))) {
+ open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
+ split = 1;
+ }
+
+ /* Always scroll the view even if it was split. That way
+ * you can use Enter to scroll through the log view and
+ * split open each commit diff. */
+ scroll_view(view, REQ_SCROLL_LINE_DOWN);
+
+ /* FIXME: A minor workaround. Scrolling the view will call report("")
+ * but if we are scrolling a non-current view this won't properly
+ * update the view title. */
+ if (split)
+ update_view_title(view);
+
+ return REQ_NONE;
+}
+
+static bool
+pager_grep(struct view *view, struct line *line)
+{
+ regmatch_t pmatch;
+ char *text = line->data;
+
+ if (!*text)
+ return FALSE;
+
+ if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+pager_select(struct view *view, struct line *line)
+{
+ if (line->type == LINE_COMMIT) {
+ char *text = (char *)line->data + STRING_SIZE("commit ");
+
+ if (view != VIEW(REQ_VIEW_PAGER))
+ string_copy_rev(view->ref, text);
+ string_copy_rev(ref_commit, text);
+ }
+}
+
+static struct view_ops pager_ops = {
+ "line",
+ NULL,
+ pager_read,
+ pager_draw,
+ pager_request,
+ pager_grep,
+ pager_select,
+};
+
+
+/*
+ * Help backend
+ */
+
+static bool
+help_open(struct view *view)
+{
+ char buf[BUFSIZ];
+ int lines = ARRAY_SIZE(req_info) + 2;
+ int i;
+
+ if (view->lines > 0)
+ return TRUE;
+
+ for (i = 0; i < ARRAY_SIZE(req_info); i++)
+ if (!req_info[i].request)
+ lines++;
+
+ lines += run_requests + 1;
+
+ view->line = calloc(lines, sizeof(*view->line));
+ if (!view->line)
+ return FALSE;
+
+ add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
+
+ for (i = 0; i < ARRAY_SIZE(req_info); i++) {
+ char *key;
+
+ if (req_info[i].request == REQ_NONE)
+ continue;
+
+ if (!req_info[i].request) {
+ add_line_text(view, "", LINE_DEFAULT);
+ add_line_text(view, req_info[i].help, LINE_DEFAULT);
+ continue;
+ }
+
+ key = get_key(req_info[i].request);
+ if (!*key)
+ key = "(no key defined)";
+
+ if (!string_format(buf, " %-25s %s", key, req_info[i].help))
+ continue;
+
+ add_line_text(view, buf, LINE_DEFAULT);
+ }
+
+ if (run_requests) {
+ add_line_text(view, "", LINE_DEFAULT);
+ add_line_text(view, "External commands:", LINE_DEFAULT);
+ }
+
+ for (i = 0; i < run_requests; i++) {
+ struct run_request *req = get_run_request(REQ_NONE + i + 1);
+ char *key;
+
+ if (!req)
+ continue;
+
+ key = get_key_name(req->key);
+ if (!*key)
+ key = "(no key defined)";
+
+ if (!string_format(buf, " %-10s %-14s `%s`",
+ keymap_table[req->keymap].name,
+ key, req->cmd))
+ continue;
+
+ add_line_text(view, buf, LINE_DEFAULT);
+ }
+
+ return TRUE;
+}
+
+static struct view_ops help_ops = {
+ "line",
+ help_open,
+ NULL,
+ pager_draw,
+ pager_request,
+ pager_grep,
+ pager_select,
+};
+
+
+/*
+ * Tree backend
+ */
+
+struct tree_stack_entry {
+ struct tree_stack_entry *prev; /* Entry below this in the stack */
+ unsigned long lineno; /* Line number to restore */
+ char *name; /* Position of name in opt_path */
+};
+
+/* The top of the path stack. */
+static struct tree_stack_entry *tree_stack = NULL;
+unsigned long tree_lineno = 0;