Code

Oops, always specify --no-color when using git diff
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index 74f78df589921a65ae038903944c2f4c0be5029e..88338d0f2832b59f6390d56a8f100efe0bac948a 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -313,6 +313,7 @@ enum io_type {
        IO_FG,                  /* Execute command with same std{in,out,err}. */
        IO_RD,                  /* Read only fork+exec IO. */
        IO_WR,                  /* Write only fork+exec IO. */
+       IO_AP,                  /* Append fork+exec output to file. */
 };
 
 struct io {
@@ -411,6 +412,8 @@ start_io(struct io *io)
        if ((io->type == IO_RD || io->type == IO_WR) &&
            pipe(pipefds) < 0)
                return FALSE;
+       else if (io->type == IO_AP)
+               pipefds[1] = io->pipe;
 
        if ((io->pid = fork())) {
                if (pipefds[!(io->type == IO_WR)] != -1)
@@ -424,7 +427,8 @@ start_io(struct io *io)
                if (io->type != IO_FG) {
                        int devnull = open("/dev/null", O_RDWR);
                        int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
-                       int writefd = io->type == IO_RD ? pipefds[1] : devnull;
+                       int writefd = (io->type == IO_RD || io->type == IO_AP)
+                                                       ? pipefds[1] : devnull;
 
                        dup2(readfd,  STDIN_FILENO);
                        dup2(writefd, STDOUT_FILENO);
@@ -486,6 +490,19 @@ run_io_fg(const char **argv, const char *dir)
        return run_io_do(&io);
 }
 
+static bool
+run_io_append(const char **argv, enum format_flags flags, int fd)
+{
+       struct io io = {};
+
+       init_io(&io, NULL, IO_AP);
+       io.pipe = fd;
+       if (format_argv(io.argv, argv, flags))
+               return run_io_do(&io);
+       close(fd);
+       return FALSE;
+}
+
 static bool
 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
 {
@@ -692,7 +709,6 @@ static int read_properties(struct io *io, const char *separators, int (*read)(ch
        REQ_GROUP("Misc") \
        REQ_(PROMPT,            "Bring up the prompt"), \
        REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
-       REQ_(SCREEN_RESIZE,     "Resize the screen"), \
        REQ_(SHOW_VERSION,      "Show version information"), \
        REQ_(STOP_LOADING,      "Stop all loading views"), \
        REQ_(EDIT,              "Open in editor"), \
@@ -1099,9 +1115,6 @@ static struct keybinding default_keybindings[] = {
        { '@',          REQ_STAGE_NEXT },
        { ',',          REQ_TREE_PARENT },
        { 'e',          REQ_EDIT },
-
-       /* Using the ncurses SIGWINCH handler. */
-       { KEY_RESIZE,   REQ_SCREEN_RESIZE },
 };
 
 #define KEYMAP_INFO \
@@ -1535,7 +1548,7 @@ option_bind_command(int argc, const char *argv[])
 
        request = get_request(argv[2]);
        if (request == REQ_NONE) {
-               const char *obsolete[] = { "cherry-pick" };
+               const char *obsolete[] = { "cherry-pick", "screen-resize" };
                size_t namelen = strlen(argv[2]);
                int i;
 
@@ -1712,6 +1725,9 @@ struct view {
        /* Navigation */
        unsigned long offset;   /* Offset of the window top */
        unsigned long lineno;   /* Current line number */
+       unsigned long p_offset; /* Previous offset of the window top */
+       unsigned long p_lineno; /* Previous current line number */
+       bool p_restore;         /* Should the previous position be restored. */
 
        /* Searching */
        char grep[SIZEOF_STR];  /* Search string */
@@ -2393,32 +2409,28 @@ move_view(struct view *view, enum request request)
 
 static void search_view(struct view *view, enum request request);
 
-static bool
-find_next_line(struct view *view, unsigned long lineno, struct line *line)
+static void
+select_view_line(struct view *view, unsigned long lineno)
 {
-       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);
+               if (view_is_displayed(view))
+                       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);
+               if (view_is_displayed(view)) {
+                       draw_view_line(view, old_lineno);
+                       draw_view_line(view, view->lineno - view->offset);
+                       redrawwin(view->win);
+                       wrefresh(view->win);
+               } else {
+                       view->ops->select(view, &view->line[view->lineno]);
+               }
        }
-
-       report("Line %ld matches '%s'", lineno + 1, view->grep);
-       return TRUE;
 }
 
 static void
@@ -2456,10 +2468,11 @@ find_next(struct view *view, enum request request)
        /* 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))
+               if (view->ops->grep(view, &view->line[lineno])) {
+                       select_view_line(view, lineno);
+                       report("Line %ld matches '%s'", lineno + 1, view->grep);
                        return;
+               }
        }
 
        report("No match found for '%s'", view->grep);
@@ -2506,6 +2519,9 @@ reset_view(struct view *view)
                free(view->line[i].data);
        free(view->line);
 
+       view->p_offset = view->offset;
+       view->p_lineno = view->lineno;
+
        view->line = NULL;
        view->offset = 0;
        view->lines  = 0;
@@ -2587,6 +2603,43 @@ format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags fl
        return src_argv[argc] == NULL;
 }
 
+static bool
+restore_view_position(struct view *view)
+{
+       if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
+               return FALSE;
+
+       /* Changing the view position cancels the restoring. */
+       /* FIXME: Changing back to the first line is not detected. */
+       if (view->offset != 0 || view->lineno != 0) {
+               view->p_restore = FALSE;
+               return FALSE;
+       }
+
+       if (view->p_lineno >= view->lines) {
+               view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
+               if (view->p_offset >= view->p_lineno) {
+                       unsigned long half = view->height / 2;
+
+                       if (view->p_lineno > half)
+                               view->p_offset = view->p_lineno - half;
+                       else
+                               view->p_offset = 0;
+               }
+       }
+
+       if (view_is_displayed(view) &&
+           view->offset != view->p_offset &&
+           view->lineno != view->p_lineno)
+               werase(view->win);
+
+       view->offset = view->p_offset;
+       view->lineno = view->p_lineno;
+       view->p_restore = FALSE;
+
+       return TRUE;
+}
+
 static void
 end_update(struct view *view, bool force)
 {
@@ -2758,6 +2811,9 @@ update_view(struct view *view)
                end_update(view, FALSE);
        }
 
+       if (restore_view_position(view))
+               redraw = TRUE;
+
        if (!view_is_displayed(view))
                return TRUE;
 
@@ -2873,6 +2929,7 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
                        report("Failed to load %s view", view->name);
                        return;
                }
+               restore_view_position(view);
 
        } else if ((reload || strcmp(view->vid, view->id)) &&
                   !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
@@ -2902,6 +2959,7 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
                /* Clear the old view and let the incremental updating refill
                 * the screen. */
                werase(view->win);
+               view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
                report("");
        } else if (view_is_displayed(view)) {
                redraw_view(view);
@@ -3167,9 +3225,6 @@ view_driver(struct view *view, enum request request)
                report("tig-%s (built %s)", TIG_VERSION, __DATE__);
                return TRUE;
 
-       case REQ_SCREEN_RESIZE:
-               resize_display();
-               /* Fall-through */
        case REQ_SCREEN_REDRAW:
                redraw_display(TRUE);
                break;
@@ -3648,6 +3703,22 @@ tree_read(struct view *view, char *text)
        return TRUE;
 }
 
+static void
+open_blob_editor()
+{
+       char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
+       int fd = mkstemp(file);
+
+       if (fd == -1)
+               report("Failed to create temporary file");
+       else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
+               report("Failed to save blob data to file");
+       else
+               open_editor(FALSE, file);
+       if (fd != -1)
+               unlink(file);
+}
+
 static enum request
 tree_request(struct view *view, enum request request, struct line *line)
 {
@@ -3667,7 +3738,7 @@ tree_request(struct view *view, enum request request, struct line *line)
                if (line->type != LINE_TREE_FILE) {
                        report("Edit only supported for files");
                } else if (!is_head_commit(view->vid)) {
-                       report("Edit only supported for files in the current work tree");
+                       open_blob_editor();
                } else {
                        open_editor(TRUE, opt_file);
                }
@@ -3768,6 +3839,18 @@ blob_read(struct view *view, char *line)
        return add_line_text(view, line, LINE_DEFAULT) != NULL;
 }
 
+static enum request
+blob_request(struct view *view, enum request request, struct line *line)
+{
+       switch (request) {
+       case REQ_EDIT:
+               open_blob_editor();
+               return REQ_NONE;
+       default:
+               return pager_request(view, request, line);
+       }
+}
+
 static const char *blob_argv[SIZEOF_ARG] = {
        "git", "cat-file", "blob", "%(blob)", NULL
 };
@@ -3778,7 +3861,7 @@ static struct view_ops blob_ops = {
        NULL,
        blob_read,
        pager_draw,
-       pager_request,
+       blob_request,
        pager_grep,
        pager_select,
 };
@@ -3812,6 +3895,7 @@ struct blame_commit {
        char author[75];                /* Author of the commit. */
        struct tm time;                 /* Date from the author ident. */
        char filename[128];             /* Name of file. */
+       bool has_previous;              /* Was a "previous" line detected. */
 };
 
 struct blame {
@@ -4001,6 +4085,9 @@ blame_read(struct view *view, char *line)
        } else if (match_blame_header("summary ", &line)) {
                string_ncopy(commit->title, line, strlen(line));
 
+       } else if (match_blame_header("previous ", &line)) {
+               commit->has_previous = TRUE;
+
        } else if (match_blame_header("filename ", &line)) {
                string_ncopy(commit->filename, line, strlen(line));
                commit = NULL;
@@ -4078,11 +4165,17 @@ blame_request(struct view *view, enum request request, struct line *line)
                if (!strcmp(blame->commit->id, NULL_ID)) {
                        struct view *diff = VIEW(REQ_VIEW_DIFF);
                        const char *diff_index_argv[] = {
-                               "git", "diff-index", "--root", "--cached",
-                                       "--patch-with-stat", "-C", "-M",
-                                       "HEAD", "--", view->vid, NULL
+                               "git", "diff-index", "--root", "--patch-with-stat",
+                                       "-C", "-M", "HEAD", "--", view->vid, NULL
                        };
 
+                       if (!blame->commit->has_previous) {
+                               diff_index_argv[1] = "diff";
+                               diff_index_argv[2] = "--no-color";
+                               diff_index_argv[5] = "--";
+                               diff_index_argv[6] = "/dev/null";
+                       }
+
                        if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
                                report("Failed to allocate diff command");
                                break;
@@ -4323,14 +4416,38 @@ static const char *update_index_argv[] = {
        "git", "update-index", "-q", "--unmerged", "--refresh", NULL
 };
 
+/* Restore the previous line number to stay in the context or select a
+ * line with something that can be updated. */
+static void
+status_restore(struct view *view)
+{
+       if (view->p_lineno >= view->lines)
+               view->p_lineno = view->lines - 1;
+       while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
+               view->p_lineno++;
+       while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
+               view->p_lineno--;
+
+       /* If the above fails, always skip the "On branch" line. */
+       if (view->p_lineno < view->lines)
+               view->lineno = view->p_lineno;
+       else
+               view->lineno = 1;
+
+       if (view->lineno < view->offset)
+               view->offset = view->lineno;
+       else if (view->offset + view->height <= view->lineno)
+               view->offset = view->lineno - view->height + 1;
+
+       view->p_restore = FALSE;
+}
+
 /* 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). */
 static bool
 status_open(struct view *view)
 {
-       unsigned long prev_lineno = view->lineno;
-
        reset_view(view);
 
        add_line_data(view, NULL, LINE_STAT_HEAD);
@@ -4354,27 +4471,10 @@ status_open(struct view *view)
            !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
                return FALSE;
 
-       /* If all went well restore the previous line number to stay in
-        * the context or select a line with something that can be
-        * updated. */
-       if (prev_lineno >= view->lines)
-               prev_lineno = view->lines - 1;
-       while (prev_lineno < view->lines && !view->line[prev_lineno].data)
-               prev_lineno++;
-       while (prev_lineno > 0 && !view->line[prev_lineno].data)
-               prev_lineno--;
-
-       /* If the above fails, always skip the "On branch" line. */
-       if (prev_lineno < view->lines)
-               view->lineno = prev_lineno;
-       else
-               view->lineno = 1;
-
-       if (view->lineno < view->offset)
-               view->offset = view->lineno;
-       else if (view->offset + view->height <= view->lineno)
-               view->offset = view->lineno - view->height + 1;
-
+       /* Restore the exact position or use the specialized restore
+        * mode? */
+       if (!view->p_restore)
+               status_restore(view);
        return TRUE;
 }
 
@@ -4513,7 +4613,7 @@ status_enter(struct view *view, struct line *line)
        }
 
        split = view_is_displayed(view) ? OPEN_SPLIT : 0;
-       open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
+       open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
        if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
                if (status) {
                        stage_status = *status;
@@ -4533,17 +4633,22 @@ static bool
 status_exists(struct status *status, enum line_type type)
 {
        struct view *view = VIEW(REQ_VIEW_STATUS);
-       struct line *line;
+       unsigned long lineno;
 
-       for (line = view->line; line < view->line + view->lines; line++) {
+       for (lineno = 0; lineno < view->lines; lineno++) {
+               struct line *line = &view->line[lineno];
                struct status *pos = line->data;
 
                if (line->type != type)
                        continue;
-               if (!pos && (!status || !status->status))
+               if (!pos && (!status || !status->status) && line[1].data) {
+                       select_view_line(view, lineno);
                        return TRUE;
-               if (pos && !strcmp(status->new.name, pos->new.name))
+               }
+               if (pos && !strcmp(status->new.name, pos->new.name)) {
+                       select_view_line(view, lineno);
                        return TRUE;
+               }
        }
 
        return FALSE;
@@ -5061,12 +5166,15 @@ stage_request(struct view *view, enum request request, struct line *line)
                return request;
        }
 
+       VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
        open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
 
        /* Check whether the staged entry still exists, and close the
         * stage view if it doesn't. */
-       if (!status_exists(&stage_status, stage_line_type))
+       if (!status_exists(&stage_status, stage_line_type)) {
+               status_restore(VIEW(REQ_VIEW_STATUS));
                return REQ_VIEW_CLOSE;
+       }
 
        if (stage_line_type == LINE_STAT_UNTRACKED) {
                if (!suffixcmp(stage_status.new.name, -1, "/")) {
@@ -5883,32 +5991,61 @@ init_display(void)
        }
 }
 
-static bool
-prompt_yesno(const char *prompt)
+static int
+get_input(bool prompting)
 {
-       enum { WAIT, STOP, CANCEL  } status = WAIT;
-       bool answer = FALSE;
-
-       while (status == WAIT) {
-               struct view *view;
-               int i, key;
+       struct view *view;
+       int i, key;
 
+       if (prompting)
                input_mode = TRUE;
 
+       while (true) {
                foreach_view (view, i)
                        update_view(view);
 
-               input_mode = FALSE;
+               /* Refresh, accept single keystroke of input */
+               key = wgetch(status_win);
+
+               /* wgetch() with nodelay() enabled returns ERR when
+                * there's no input. */
+               if (key == ERR) {
+                       doupdate();
+
+               } else if (key == KEY_RESIZE) {
+                       int height, width;
+
+                       getmaxyx(stdscr, height, width);
+
+                       /* Resize the status view and let the view driver take
+                        * care of resizing the displayed views. */
+                       resize_display();
+                       redraw_display(TRUE);
+                       wresize(status_win, 1, width);
+                       mvwin(status_win, height - 1, 0);
+                       wrefresh(status_win);
+
+               } else {
+                       input_mode = FALSE;
+                       return key;
+               }
+       }
+}
+
+static bool
+prompt_yesno(const char *prompt)
+{
+       enum { WAIT, STOP, CANCEL  } status = WAIT;
+       bool answer = FALSE;
+
+       while (status == WAIT) {
+               int key;
 
                mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
                wclrtoeol(status_win);
 
-               /* Refresh, accept single keystroke of input */
-               key = wgetch(status_win);
+               key = get_input(TRUE);
                switch (key) {
-               case ERR:
-                       break;
-
                case 'y':
                case 'Y':
                        answer = TRUE;
@@ -5943,21 +6080,12 @@ read_prompt(const char *prompt)
        int pos = 0;
 
        while (status == READING) {
-               struct view *view;
-               int i, key;
-
-               input_mode = TRUE;
-
-               foreach_view (view, i)
-                       update_view(view);
-
-               input_mode = FALSE;
+               int key;
 
                mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
                wclrtoeol(status_win);
 
-               /* Refresh, accept single keystroke of input */
-               key = wgetch(status_win);
+               key = get_input(TRUE);
                switch (key) {
                case KEY_RETURN:
                case KEY_ENTER:
@@ -5976,9 +6104,6 @@ read_prompt(const char *prompt)
                        status = CANCEL;
                        break;
 
-               case ERR:
-                       break;
-
                default:
                        if (pos >= sizeof(buf)) {
                                report("Input string too long");
@@ -6419,23 +6544,9 @@ main(int argc, const char *argv[])
        }
 
        while (view_driver(display[current_view], request)) {
-               int key;
-               int i;
+               int key = get_input(FALSE);
 
-               foreach_view (view, i)
-                       update_view(view);
                view = display[current_view];
-
-               /* Refresh, accept single keystroke of input */
-               key = wgetch(status_win);
-
-               /* wgetch() with nodelay() enabled returns ERR when there's no
-                * input. */
-               if (key == ERR) {
-                       request = REQ_NONE;
-                       continue;
-               }
-
                request = get_keybinding(view->keymap, key);
 
                /* Some low-level request handling. This keeps access to
@@ -6479,19 +6590,6 @@ main(int argc, const char *argv[])
                                request = REQ_NONE;
                        break;
                }
-               case REQ_SCREEN_RESIZE:
-               {
-                       int height, width;
-
-                       getmaxyx(stdscr, height, width);
-
-                       /* Resize the status view and let the view driver take
-                        * care of resizing the displayed views. */
-                       wresize(status_win, 1, width);
-                       mvwin(status_win, height - 1, 0);
-                       wrefresh(status_win);
-                       break;
-               }
                default:
                        break;
                }