X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/89d917a2473a8510ccf420d8d67d668c2bcda36f..c692b4e4a2fcddbde05187d2d37c228f91d36274:/tig.c diff --git a/tig.c b/tig.c index d9c9df8..21dd6c2 100644 --- 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 +#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,45 +3108,59 @@ 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; - char path[SIZEOF_STR]; + char path[SIZEOF_STR] = ""; char *info; size_t cmdsize = 0; - if (!status || line->type == LINE_STAT_NONE) { - report("No file has been chosen"); - return TRUE; + if (line->type == LINE_STAT_NONE || + (!status && line[1].type == LINE_STAT_NONE)) { + report("No file to diff"); + return REQ_NONE; } - if (sq_quote(path, 0, status->name) >= sizeof(path)) - return FALSE; + if (status && sq_quote(path, 0, status->name) >= sizeof(path)) + 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; - info = "Staged changes to %s"; + 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_SHOW_CMD, "", path)) - return FALSE; - info = "Unstaged changes to %s"; + return REQ_QUIT; + if (status) + info = "Unstaged changes to %s"; + else + info = "Unstaged changes"; break; case LINE_STAT_UNTRACKED: if (opt_pipe) - return FALSE; + return REQ_QUIT; + + + if (!status) { + report("No file to show"); + return REQ_NONE; + } + opt_pipe = fopen(status->name, "r"); info = "Untracked file %s"; break; @@ -3072,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) { @@ -3126,55 +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->lines - ? &view->line[view->lineno] : NULL; + struct line *line = &view->line[view->lineno]; + + assert(view->lines); - if (!line || !line->data) { - report("No file has been chosen"); + 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; } - 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 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 @@ -3214,7 +3359,7 @@ static struct view_ops status_ops = { status_open, NULL, status_draw, - status_enter, + status_request, status_grep, status_select, }; @@ -3646,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 @@ -3700,7 +3849,7 @@ static struct view_ops main_ops = { NULL, main_read, main_draw, - main_enter, + main_request, main_grep, main_select, }; @@ -4150,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); }