index 0a42ee2fa8cbe93d9c86074159fb41bb7ed9d4e2..21dd6c2f8e1d7db9862dd03a2a0d15614917edfe 100644 (file)
--- a/tig.c
+++ b/tig.c
* GNU General Public License for more details.
*/
-#ifndef VERSION
-#define VERSION "unknown-version"
+#ifndef TIG_VERSION
+#define TIG_VERSION "unknown-version"
#endif
#ifndef DEBUG
#include <curses.h>
+#include "config.h"
+
#if __GNUC__ >= 3
#define __NORETURN __attribute__((__noreturn__))
#else
#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"
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. */
*/
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"
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,
if (!strcmp(opt, "-S")) {
opt_request = REQ_VIEW_STATUS;
- break;
+ continue;
}
if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
}
if (check_option(opt, 'v', "version", OPT_NONE)) {
- printf("tig version %s\n", VERSION);
+ printf("tig version %s\n", TIG_VERSION);
return FALSE;
}
{ 'g', REQ_TOGGLE_REV_GRAPH },
{ ':', REQ_PROMPT },
{ 'u', REQ_STATUS_UPDATE },
+ { 'e', REQ_EDIT },
/* Using the ncurses SIGWINCH handler. */
{ KEY_RESIZE, REQ_SCREEN_RESIZE },
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 */
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
/* 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;
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;
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:
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;
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:
{
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:
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:
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))) {
if (split)
update_view_title(view);
- return TRUE;
+ return REQ_NONE;
}
static bool
NULL,
pager_read,
pager_draw,
- pager_enter,
+ pager_request,
pager_grep,
pager_select,
};
help_open,
NULL,
pager_draw,
- pager_enter,
+ pager_request,
pager_grep,
pager_select,
};
* 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
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
}
open_view(view, request, flags);
+ if (request == REQ_VIEW_TREE) {
+ view->lineno = tree_lineno;
+ }
- return TRUE;
+ return REQ_NONE;
}
static void
NULL,
tree_read,
pager_draw,
- tree_enter,
+ tree_request,
pager_grep,
tree_select,
};
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;
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
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
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");
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
status_open,
NULL,
status_draw,
- status_enter,
+ status_request,
status_grep,
status_select,
};
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
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);
}