status: fix switching to the main view when starting in status view
[tig] / tig.c
diff --git a/tig.c b/tig.c
index 6adfb33..5b45844 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -336,7 +336,8 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
        REQ_(SHOW_VERSION,      "Show version information"), \
        REQ_(STOP_LOADING,      "Stop all loading views"), \
        REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
-       REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization")
+       REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
+       REQ_(STATUS_UPDATE,     "Update file status") \
 
 
 /* User action requests. */
@@ -499,7 +500,7 @@ parse_options(int argc, char *argv[])
 
                if (!strcmp(opt, "-S")) {
                        opt_request = REQ_VIEW_STATUS;
-                       break;
+                       continue;
                }
 
                if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
@@ -765,6 +766,7 @@ static struct keybinding default_keybindings[] = {
        { '.',          REQ_TOGGLE_LINENO },
        { 'g',          REQ_TOGGLE_REV_GRAPH },
        { ':',          REQ_PROMPT },
+       { 'u',          REQ_STATUS_UPDATE },
 
        /* Using the ncurses SIGWINCH handler. */
        { KEY_RESIZE,   REQ_SCREEN_RESIZE },
@@ -1153,7 +1155,7 @@ read_option(char *opt, size_t optlen, char *value, size_t valuelen)
 
        if (status == ERR) {
                fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
-                       config_lineno, optlen, opt, config_msg);
+                       config_lineno, (int) optlen, opt, config_msg);
                config_errors = TRUE;
        }
 
@@ -1289,8 +1291,8 @@ static struct view views[] = {
        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,  ""),
+       VIEW_(HELP,   "help",   &help_ops,   ""),
+       VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
        VIEW_(STATUS, "status", &status_ops, ""),
 };
 
@@ -1366,7 +1368,7 @@ update_view_title(struct view *view)
 
        assert(view_is_displayed(view));
 
-       if (view->lines || view->pipe) {
+       if (!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
@@ -1394,7 +1396,7 @@ update_view_title(struct view *view)
 
                if (minsize < view->width)
                        refsize = view->width - minsize + 7;
-               string_format_from(buf, &bufpos, " %.*s", refsize, view->ref);
+               string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
        }
 
        if (statelen && bufpos < view->width) {
@@ -2120,6 +2122,8 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
  * User request switch noodle
  */
 
+static void status_update(struct view *view);
+
 static int
 view_driver(struct view *view, enum request request)
 {
@@ -2152,7 +2156,7 @@ view_driver(struct view *view, enum request request)
                break;
 
        case REQ_VIEW_PAGER:
-               if (!VIEW(REQ_VIEW_PAGER)->lines) {
+               if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
                        report("No pager content, press %s to run command from prompt",
                               get_key(REQ_PROMPT));
                        break;
@@ -2177,10 +2181,15 @@ view_driver(struct view *view, enum request request)
                     view->parent == VIEW(REQ_VIEW_MAIN)) ||
                   (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)
+                               break;
                } else {
                        move_view(view, request);
                        break;
@@ -2255,6 +2264,10 @@ view_driver(struct view *view, enum request request)
                redraw_display();
                break;
 
+       case REQ_STATUS_UPDATE:
+               status_update(view);
+               break;
+
        case REQ_NONE:
                doupdate();
                return TRUE;
@@ -2907,6 +2920,9 @@ error_out:
 #define STATUS_LIST_OTHER_CMD \
        "git ls-files -z --others --exclude-per-directory=.gitignore"
 
+#define STATUS_DIFF_SHOW_CMD \
+       "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
+
 /* First parse staged info using git-diff-index(1), then parse unstaged
  * info using git-diff-files(1), and finally untracked files using
  * git-ls-files(1). */
@@ -3008,6 +3024,73 @@ static bool
 status_enter(struct view *view, struct line *line)
 {
        struct status *status = line->data;
+       char path[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 TRUE;
+       }
+
+       if (status && sq_quote(path, 0, status->name) >= sizeof(path))
+               return FALSE;
+
+       if (opt_cdup[0] &&
+           line->type != LINE_STAT_UNTRACKED &&
+           !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
+               return FALSE;
+
+       switch (line->type) {
+       case LINE_STAT_STAGED:
+               if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
+                                       "--cached", path))
+                       return FALSE;
+               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_SHOW_CMD,
+                                       "", path))
+                       return FALSE;
+               if (status)
+                       info = "Unstaged changes to %s";
+               else
+                       info = "Unstaged changes";
+               break;
+
+       case LINE_STAT_UNTRACKED:
+               if (opt_pipe)
+                       return FALSE;
+
+               if (!status) {
+                       report("No file to show");
+                       return TRUE;
+               }
+
+               opt_pipe = fopen(status->name, "r");
+               info = "Untracked file %s";
+               break;
+
+       default:
+               die("w00t");
+       }
+
+       open_view(view, REQ_VIEW_DIFF, OPEN_RELOAD | OPEN_SPLIT);
+       if (view_is_displayed(VIEW(REQ_VIEW_DIFF))) {
+               string_format(VIEW(REQ_VIEW_DIFF)->ref, info, status->name);
+       }
+
+       return TRUE;
+}
+
+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;
@@ -3015,15 +3098,12 @@ status_enter(struct view *view, struct line *line)
        size_t written = 0;
        FILE *pipe;
 
-       if (!status)
-               return TRUE;
-
        if (opt_cdup[0] &&
-           line->type != LINE_STAT_UNTRACKED &&
+           type != LINE_STAT_UNTRACKED &&
            !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
                return FALSE;
 
-       switch (line->type) {
+       switch (type) {
        case LINE_STAT_STAGED:
                if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
                                        status->old.mode,
@@ -3059,36 +3139,74 @@ status_enter(struct view *view, struct line *line)
        if (written != bufsize)
                return FALSE;
 
-       open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
        return TRUE;
 }
 
 static void
+status_update(struct view *view)
+{
+       if (view == VIEW(REQ_VIEW_STATUS)) {
+               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");
+               }
+
+               open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
+       } else {
+               report("This action is only valid for the status view");
+       }
+}
+
+static void
 status_select(struct view *view, struct line *line)
 {
+       struct status *status = line->data;
+       char file[SIZEOF_STR] = "all files";
        char *text;
 
+       if (status && !string_format(file, "'%s'", status->name))
+               return;
+
+       if (!status && line[1].type == LINE_STAT_NONE)
+               line++;
+
        switch (line->type) {
        case LINE_STAT_STAGED:
-               text = "Press Enter to unstage file for commit";
+               text = "Press %s to unstage %s for commit";
                break;
 
        case LINE_STAT_UNSTAGED:
-               text = "Press Enter to stage file for commit  ";
+               text = "Press %s to stage %s for commit";
                break;
 
        case LINE_STAT_UNTRACKED:
-               text = "Press Enter to stage file for addition";
+               text = "Press %s to stage %s for addition";
                break;
 
        case LINE_STAT_NONE:
-               return;
+               text = "Nothing to update";
+               break;
 
        default:
                die("w00t");
        }
 
-       string_ncopy(view->ref, text, strlen(text));
+       string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
 }
 
 static bool