Code

autoconf: check whether to use git-config or git-repo-config
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index 0a42ee2fa8cbe93d9c86074159fb41bb7ed9d4e2..21dd6c2f8e1d7db9862dd03a2a0d15614917edfe 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -11,8 +11,8 @@
  * GNU General Public License for more details.
  */
 
-#ifndef VERSION
-#define VERSION "unknown-version"
+#ifndef TIG_VERSION
+#define TIG_VERSION "unknown-version"
 #endif
 
 #ifndef DEBUG
@@ -40,6 +40,8 @@
 
 #include <curses.h>
 
+#include "config.h"
+
 #if __GNUC__ >= 3
 #define __NORETURN __attribute__((__noreturn__))
 #else
@@ -90,6 +92,10 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 
 #define        SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
 
+#ifndef GIT_CONFIG
+#define "git config"
+#endif
+
 #define TIG_LS_REMOTE \
        "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
 
@@ -337,7 +343,8 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
        REQ_(STOP_LOADING,      "Stop all loading views"), \
        REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
        REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
-       REQ_(STATUS_UPDATE,     "Update file status") \
+       REQ_(STATUS_UPDATE,     "Update file status"), \
+       REQ_(EDIT,              "Open in editor")
 
 
 /* User action requests. */
@@ -389,7 +396,7 @@ get_request(const char *name)
  */
 
 static const char usage[] =
-"tig " VERSION " (" __DATE__ ")\n"
+"tig " TIG_VERSION " (" __DATE__ ")\n"
 "\n"
 "Usage: tig [options]\n"
 "   or: tig [options] [--] [git log options]\n"
@@ -424,6 +431,7 @@ static iconv_t opt_iconv            = ICONV_NONE;
 static char opt_search[SIZEOF_STR]     = "";
 static char opt_cdup[SIZEOF_STR]       = "";
 static char opt_git_dir[SIZEOF_STR]    = "";
+static char opt_editor[SIZEOF_STR]     = "";
 
 enum option_type {
        OPT_NONE,
@@ -500,7 +508,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)) {
@@ -514,7 +522,7 @@ parse_options(int argc, char *argv[])
                }
 
                if (check_option(opt, 'v', "version", OPT_NONE)) {
-                       printf("tig version %s\n", VERSION);
+                       printf("tig version %s\n", TIG_VERSION);
                        return FALSE;
                }
 
@@ -767,6 +775,7 @@ static struct keybinding default_keybindings[] = {
        { 'g',          REQ_TOGGLE_REV_GRAPH },
        { ':',          REQ_PROMPT },
        { 'u',          REQ_STATUS_UPDATE },
+       { 'e',          REQ_EDIT },
 
        /* Using the ncurses SIGWINCH handler. */
        { KEY_RESIZE,   REQ_SCREEN_RESIZE },
@@ -1263,8 +1272,8 @@ struct view_ops {
        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, change display based on current line. */
-       bool (*enter)(struct view *view, struct line *line);
+       /* 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 */
@@ -1368,7 +1377,7 @@ update_view_title(struct view *view)
 
        assert(view_is_displayed(view));
 
-       if (view->lines || view->pipe) {
+       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
@@ -1815,7 +1824,10 @@ begin_update(struct view *view)
                /* When running random commands, initially show the
                 * command in the title. However, it maybe later be
                 * overwritten if a commit line is selected. */
-               string_copy(view->ref, view->cmd);
+               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;
@@ -1920,7 +1932,7 @@ update_view(struct view *view)
                        line[linelen - 1] = 0;
 
                if (opt_iconv != ICONV_NONE) {
-                       char *inbuf = line;
+                       ICONV_INBUF_TYPE inbuf = line;
                        size_t inlen = linelen;
 
                        char *outbuf = out_buffer;
@@ -2117,18 +2129,48 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
                update_view_title(view);
 }
 
+static void
+open_editor(struct view *view, char *file)
+{
+       char cmd[SIZEOF_STR];
+       char file_sq[SIZEOF_STR];
+       char *editor;
+
+       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", editor, file_sq)) {
+               def_prog_mode();           /* save current tty modes */
+               endwin();                  /* restore original tty modes */
+               system(cmd);
+               reset_prog_mode();
+               redraw_display();
+       }
+}
 
 /*
  * User request switch noodle
  */
 
-static void status_update(struct view *view);
-
 static int
 view_driver(struct view *view, enum request request)
 {
        int i;
 
+       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:
@@ -2179,6 +2221,8 @@ view_driver(struct view *view, enum request request)
 
                if ((view == VIEW(REQ_VIEW_DIFF) &&
                     view->parent == VIEW(REQ_VIEW_MAIN)) ||
+                  (view == VIEW(REQ_VIEW_DIFF) &&
+                    view->parent == VIEW(REQ_VIEW_STATUS)) ||
                   (view == VIEW(REQ_VIEW_BLOB) &&
                     view->parent == VIEW(REQ_VIEW_TREE))) {
                        int line;
@@ -2188,20 +2232,14 @@ view_driver(struct view *view, enum request request)
                        move_view(view, request);
                        if (view_is_displayed(view))
                                update_view_title(view);
-                       if (line == view->lineno)
-                               break;
+                       if (line != view->lineno)
+                               view->ops->request(view, REQ_ENTER,
+                                                  &view->line[view->lineno]);
+
                } else {
                        move_view(view, request);
-                       break;
-               }
-               /* Fall-through */
-
-       case REQ_ENTER:
-               if (!view->lines) {
-                       report("Nothing to enter");
-                       break;
                }
-               return view->ops->enter(view, &view->line[view->lineno]);
+               break;
 
        case REQ_VIEW_NEXT:
        {
@@ -2254,7 +2292,7 @@ view_driver(struct view *view, enum request request)
                break;
 
        case REQ_SHOW_VERSION:
-               report("tig-%s (built %s)", VERSION, __DATE__);
+               report("tig-%s (built %s)", TIG_VERSION, __DATE__);
                return TRUE;
 
        case REQ_SCREEN_RESIZE:
@@ -2264,8 +2302,12 @@ view_driver(struct view *view, enum request request)
                redraw_display();
                break;
 
-       case REQ_STATUS_UPDATE:
-               status_update(view);
+       case REQ_EDIT:
+               report("Nothing to edit");
+               break;
+
+       case REQ_ENTER:
+               report("Nothing to enter");
                break;
 
        case REQ_NONE:
@@ -2469,11 +2511,14 @@ pager_read(struct view *view, char *data)
        return TRUE;
 }
 
-static bool
-pager_enter(struct view *view, struct line *line)
+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))) {
@@ -2492,7 +2537,7 @@ pager_enter(struct view *view, struct line *line)
        if (split)
                update_view_title(view);
 
-       return TRUE;
+       return REQ_NONE;
 }
 
 static bool
@@ -2527,7 +2572,7 @@ static struct view_ops pager_ops = {
        NULL,
        pager_read,
        pager_draw,
-       pager_enter,
+       pager_request,
        pager_grep,
        pager_select,
 };
@@ -2581,7 +2626,7 @@ static struct view_ops help_ops = {
        help_open,
        NULL,
        pager_draw,
-       pager_enter,
+       pager_request,
        pager_grep,
        pager_select,
 };
@@ -2591,6 +2636,50 @@ static struct view_ops help_ops = {
  * 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;
+
+static void
+pop_tree_stack_entry(void)
+{
+       struct tree_stack_entry *entry = tree_stack;
+
+       tree_lineno = entry->lineno;
+       entry->name[0] = 0;
+       tree_stack = entry->prev;
+       free(entry);
+}
+
+static void
+push_tree_stack_entry(char *name, unsigned long lineno)
+{
+       struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
+       size_t pathlen = strlen(opt_path);
+
+       if (!entry)
+               return;
+
+       entry->prev = tree_stack;
+       entry->name = opt_path + pathlen;
+       tree_stack = entry;
+
+       if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
+               pop_tree_stack_entry();
+               return;
+       }
+
+       /* Move the current line to the first tree entry. */
+       tree_lineno = 1;
+       entry->lineno = lineno;
+}
+
 /* Parse output from git-ls-tree(1):
  *
  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26        Makefile
@@ -2687,42 +2776,38 @@ tree_read(struct view *view, char *text)
        if (!add_line_text(view, text, type))
                return FALSE;
 
-       /* Move the current line to the first tree entry. */
-       if (first_read)
-               view->lineno++;
+       if (tree_lineno > view->lineno) {
+               view->lineno = tree_lineno;
+               tree_lineno = 0;
+       }
 
        return TRUE;
 }
 
-static bool
-tree_enter(struct view *view, struct line *line)
+static enum request
+tree_request(struct view *view, enum request request, struct line *line)
 {
        enum open_flags flags;
-       enum request request;
+
+       if (request != REQ_ENTER)
+               return request;
+
+       /* Cleanup the stack if the tree view is at a different tree. */
+       while (!*opt_path && tree_stack)
+               pop_tree_stack_entry();
 
        switch (line->type) {
        case LINE_TREE_DIR:
                /* Depending on whether it is a subdir or parent (updir?) link
                 * mangle the path buffer. */
                if (line == &view->line[1] && *opt_path) {
-                       size_t path_len = strlen(opt_path);
-                       char *dirsep = opt_path + path_len - 1;
-
-                       while (dirsep > opt_path && dirsep[-1] != '/')
-                               dirsep--;
-
-                       dirsep[0] = 0;
+                       pop_tree_stack_entry();
 
                } else {
-                       size_t pathlen = strlen(opt_path);
-                       size_t origlen = pathlen;
                        char *data = line->data;
                        char *basename = data + SIZEOF_TREE_ATTR;
 
-                       if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
-                               opt_path[origlen] = 0;
-                               return TRUE;
-                       }
+                       push_tree_stack_entry(basename, view->lineno);
                }
 
                /* Trees and subtrees share the same ID, so they are not not
@@ -2741,8 +2826,11 @@ tree_enter(struct view *view, struct line *line)
        }
 
        open_view(view, request, flags);
+       if (request == REQ_VIEW_TREE) {
+               view->lineno = tree_lineno;
+       }
 
-       return TRUE;
+       return REQ_NONE;
 }
 
 static void
@@ -2765,7 +2853,7 @@ static struct view_ops tree_ops = {
        NULL,
        tree_read,
        pager_draw,
-       tree_enter,
+       tree_request,
        pager_grep,
        tree_select,
 };
@@ -2781,7 +2869,7 @@ static struct view_ops blob_ops = {
        NULL,
        blob_read,
        pager_draw,
-       pager_enter,
+       pager_request,
        pager_grep,
        pager_select,
 };
@@ -3020,7 +3108,7 @@ status_draw(struct view *view, struct line *line, unsigned int lineno, bool sele
        return TRUE;
 }
 
-static bool
+static enum request
 status_enter(struct view *view, struct line *line)
 {
        struct status *status = line->data;
@@ -3031,22 +3119,22 @@ status_enter(struct view *view, struct line *line)
        if (line->type == LINE_STAT_NONE ||
            (!status && line[1].type == LINE_STAT_NONE)) {
                report("No file to diff");
-               return TRUE;
+               return REQ_NONE;
        }
 
        if (status && sq_quote(path, 0, status->name) >= sizeof(path))
-               return FALSE;
+               return REQ_QUIT;
 
        if (opt_cdup[0] &&
            line->type != LINE_STAT_UNTRACKED &&
            !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
-               return FALSE;
+               return REQ_QUIT;
 
        switch (line->type) {
        case LINE_STAT_STAGED:
                if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
                                        "--cached", path))
-                       return FALSE;
+                       return REQ_QUIT;
                if (status)
                        info = "Staged changes to %s";
                else
@@ -3056,7 +3144,7 @@ status_enter(struct view *view, struct line *line)
        case LINE_STAT_UNSTAGED:
                if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
                                        "", path))
-                       return FALSE;
+                       return REQ_QUIT;
                if (status)
                        info = "Unstaged changes to %s";
                else
@@ -3065,11 +3153,12 @@ status_enter(struct view *view, struct line *line)
 
        case LINE_STAT_UNTRACKED:
                if (opt_pipe)
-                       return FALSE;
+                       return REQ_QUIT;
+
 
                if (!status) {
                        report("No file to show");
-                       return TRUE;
+                       return REQ_NONE;
                }
 
                opt_pipe = fopen(status->name, "r");
@@ -3085,9 +3174,10 @@ status_enter(struct view *view, struct line *line)
                string_format(VIEW(REQ_VIEW_DIFF)->ref, info, status->name);
        }
 
-       return TRUE;
+       return REQ_NONE;
 }
 
+
 static bool
 status_update_file(struct view *view, struct status *status, enum line_type type)
 {
@@ -3139,56 +3229,97 @@ status_update_file(struct view *view, struct status *status, enum line_type type
        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];
+       struct line *line = &view->line[view->lineno];
+
+       assert(view->lines);
 
-               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->data) {
-                       report("No file has been chosen");
+               if (!line[-1].data) {
+                       report("Nothing to update");
                        return;
                }
 
-               if (!status_update_file(view, line->data, line->type))
-                       report("Failed to update file status");
-       } else {
-               report("This action is only valid for the status view");
+       } else if (!status_update_file(view, line->data, line->type)) {
+               report("Failed to update file status");
+       }
+
+       open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
+}
+
+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_EDIT:
+               if (!status)
+                       return request;
+
+               open_editor(view, status->name);
+               break;
+
+       case REQ_ENTER:
+               status_enter(view, line);
+               break;
+
+       default:
+               return request;
        }
+
+       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;
 
+       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 %s to unstage file for commit";
+               text = "Press %s to unstage %s for commit";
                break;
 
        case LINE_STAT_UNSTAGED:
-               text = "Press %s to stage file for commit  ";
+               text = "Press %s to stage %s for commit";
                break;
 
        case LINE_STAT_UNTRACKED:
-               text = "Press %s 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_format(view->ref, text, get_key(REQ_STATUS_UPDATE));
+       string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
 }
 
 static bool
@@ -3228,7 +3359,7 @@ static struct view_ops status_ops = {
        status_open,
        NULL,
        status_draw,
-       status_enter,
+       status_request,
        status_grep,
        status_select,
 };
@@ -3660,13 +3791,17 @@ main_read(struct view *view, char *line)
        return TRUE;
 }
 
-static bool
-main_enter(struct view *view, struct line *line)
+static enum request
+main_request(struct view *view, enum request request, struct line *line)
 {
        enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
 
-       open_view(view, REQ_VIEW_DIFF, flags);
-       return TRUE;
+       if (request == REQ_ENTER)
+               open_view(view, REQ_VIEW_DIFF, flags);
+       else
+               return request;
+
+       return REQ_NONE;
 }
 
 static bool
@@ -3714,7 +3849,7 @@ static struct view_ops main_ops = {
        NULL,
        main_read,
        main_draw,
-       main_enter,
+       main_request,
        main_grep,
        main_select,
 };
@@ -4164,13 +4299,16 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen
        if (!strcmp(name, "i18n.commitencoding"))
                string_ncopy(opt_encoding, value, valuelen);
 
+       if (!strcmp(name, "core.editor"))
+               string_ncopy(opt_editor, value, valuelen);
+
        return OK;
 }
 
 static int
 load_repo_config(void)
 {
-       return read_properties(popen("git repo-config --list", "r"),
+       return read_properties(popen(GIT_CONFIG " --list", "r"),
                               "=", read_repo_config_option);
 }