+static enum request
+status_enter(struct view *view, struct line *line)
+{
+ struct status *status = line->data;
+ char oldpath[SIZEOF_STR] = "";
+ char newpath[SIZEOF_STR] = "";
+ char *info;
+ size_t cmdsize = 0;
+
+ if (line->type == LINE_STAT_NONE ||
+ (!status && line[1].type == LINE_STAT_NONE)) {
+ report("No file to diff");
+ return REQ_NONE;
+ }
+
+ if (status) {
+ if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
+ return REQ_QUIT;
+ /* Diffs for unmerged entries are empty when pasing the
+ * new path, so leave it empty. */
+ if (status->status != 'U' &&
+ sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
+ return REQ_QUIT;
+ }
+
+ if (opt_cdup[0] &&
+ line->type != LINE_STAT_UNTRACKED &&
+ !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
+ return REQ_QUIT;
+
+ switch (line->type) {
+ case LINE_STAT_STAGED:
+ if (!string_format_from(opt_cmd, &cmdsize,
+ STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
+ return REQ_QUIT;
+ if (status)
+ info = "Staged changes to %s";
+ else
+ info = "Staged changes";
+ break;
+
+ case LINE_STAT_UNSTAGED:
+ if (!string_format_from(opt_cmd, &cmdsize,
+ STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
+ return REQ_QUIT;
+ if (status)
+ info = "Unstaged changes to %s";
+ else
+ info = "Unstaged changes";
+ break;
+
+ case LINE_STAT_UNTRACKED:
+ if (opt_pipe)
+ return REQ_QUIT;
+
+
+ if (!status) {
+ report("No file to show");
+ return REQ_NONE;
+ }
+
+ opt_pipe = fopen(status->new.name, "r");
+ info = "Untracked file %s";
+ break;
+
+ default:
+ die("line type %d not handled in switch", line->type);
+ }
+
+ open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
+ if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
+ if (status) {
+ stage_status = *status;
+ } else {
+ memset(&stage_status, 0, sizeof(stage_status));
+ }
+
+ stage_line_type = line->type;
+ string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
+ }
+
+ return REQ_NONE;
+}
+
+
+static bool
+status_update_file(struct view *view, struct status *status, enum line_type type)
+{
+ char cmd[SIZEOF_STR];
+ char buf[SIZEOF_STR];
+ size_t cmdsize = 0;
+ size_t bufsize = 0;
+ size_t written = 0;
+ FILE *pipe;
+
+ if (opt_cdup[0] &&
+ type != LINE_STAT_UNTRACKED &&
+ !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
+ return FALSE;
+
+ switch (type) {
+ case LINE_STAT_STAGED:
+ if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
+ status->old.mode,
+ status->old.rev,
+ status->old.name, 0))
+ return FALSE;
+
+ string_add(cmd, cmdsize, "git update-index -z --index-info");
+ break;
+
+ case LINE_STAT_UNSTAGED:
+ case LINE_STAT_UNTRACKED:
+ if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
+ return FALSE;
+
+ string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
+ break;
+
+ default:
+ die("line type %d not handled in switch", type);
+ }
+
+ pipe = popen(cmd, "w");
+ if (!pipe)
+ return FALSE;
+
+ while (!ferror(pipe) && written < bufsize) {
+ written += fwrite(buf + written, 1, bufsize - written, pipe);
+ }
+
+ pclose(pipe);
+
+ if (written != bufsize)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+status_update(struct view *view)
+{
+ struct line *line = &view->line[view->lineno];
+
+ assert(view->lines);
+
+ if (!line->data) {
+ while (++line < view->line + view->lines && line->data) {
+ if (!status_update_file(view, line->data, line->type))
+ report("Failed to update file status");
+ }
+
+ if (!line[-1].data) {
+ report("Nothing to update");
+ return;
+ }
+
+ } else if (!status_update_file(view, line->data, line->type)) {
+ report("Failed to update file status");
+ }
+}
+
+static enum request
+status_request(struct view *view, enum request request, struct line *line)
+{
+ struct status *status = line->data;
+
+ switch (request) {
+ case REQ_STATUS_UPDATE:
+ status_update(view);
+ break;
+
+ case REQ_STATUS_MERGE:
+ if (!status || status->status != 'U') {
+ report("Merging only possible for files with unmerged status ('U').");
+ return REQ_NONE;
+ }
+ open_mergetool(status->new.name);
+ break;
+
+ case REQ_EDIT:
+ if (!status)
+ return request;
+
+ open_editor(status->status != '?', status->new.name);
+ break;
+
+ case REQ_ENTER:
+ /* After returning the status view has been split to
+ * show the stage view. No further reloading is
+ * necessary. */
+ status_enter(view, line);
+ return REQ_NONE;
+
+ case REQ_REFRESH:
+ /* Simply reload the view. */
+ break;
+
+ default:
+ return request;
+ }
+
+ open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
+
+ return REQ_NONE;
+}
+
+static void
+status_select(struct view *view, struct line *line)
+{
+ struct status *status = line->data;
+ char file[SIZEOF_STR] = "all files";
+ char *text;
+ char *key;
+
+ if (status && !string_format(file, "'%s'", status->new.name))
+ return;
+
+ if (!status && line[1].type == LINE_STAT_NONE)
+ line++;
+
+ switch (line->type) {
+ case LINE_STAT_STAGED:
+ text = "Press %s to unstage %s for commit";
+ break;
+
+ case LINE_STAT_UNSTAGED:
+ text = "Press %s to stage %s for commit";
+ break;
+
+ case LINE_STAT_UNTRACKED:
+ text = "Press %s to stage %s for addition";
+ break;
+
+ case LINE_STAT_NONE:
+ text = "Nothing to update";
+ break;
+
+ default:
+ die("line type %d not handled in switch", line->type);
+ }
+
+ if (status && status->status == 'U') {
+ text = "Press %s to resolve conflict in %s";
+ key = get_key(REQ_STATUS_MERGE);
+
+ } else {
+ key = get_key(REQ_STATUS_UPDATE);
+ }
+
+ string_format(view->ref, text, key, file);
+}
+
+static bool
+status_grep(struct view *view, struct line *line)
+{
+ struct status *status = line->data;
+ enum { S_STATUS, S_NAME, S_END } state;
+ char buf[2] = "?";
+ regmatch_t pmatch;
+
+ if (!status)
+ return FALSE;
+
+ for (state = S_STATUS; state < S_END; state++) {
+ char *text;
+
+ switch (state) {
+ case S_NAME: text = status->new.name; break;
+ case S_STATUS:
+ buf[0] = status->status;
+ text = buf;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static struct view_ops status_ops = {
+ "file",
+ status_open,
+ NULL,
+ status_draw,
+ status_request,
+ status_grep,
+ status_select,
+};
+
+
+static bool
+stage_diff_line(FILE *pipe, struct line *line)
+{
+ char *buf = line->data;
+ size_t bufsize = strlen(buf);
+ size_t written = 0;
+
+ while (!ferror(pipe) && written < bufsize) {
+ written += fwrite(buf + written, 1, bufsize - written, pipe);
+ }
+
+ fputc('\n', pipe);
+
+ return written == bufsize;
+}
+
+static struct line *
+stage_diff_hdr(struct view *view, struct line *line)
+{
+ int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
+ struct line *diff_hdr;
+
+ if (line->type == LINE_DIFF_CHUNK)
+ diff_hdr = line - 1;
+ else
+ diff_hdr = view->line + 1;
+
+ while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
+ if (diff_hdr->type == LINE_DIFF_HEADER)
+ return diff_hdr;
+
+ diff_hdr += diff_hdr_dir;
+ }
+
+ return NULL;
+}
+