index fa03109e7fa2836835d309c9268d408cfa364bf4..ce406c86fbed25706912987126721960c61cbbc0 100644 (file)
--- a/tig.c
+++ b/tig.c
static void warn(const char *msg, ...);
static void report(const char *msg, ...);
static void set_nonblocking_input(bool loading);
-static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
static int load_refs(void);
+static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
/* The default interval between line numbers. */
#define NUMBER_INTERVAL 5
+#define SCROLL_INTERVAL 1
#define TAB_SIZE 8
#define GIT_CONFIG "config"
#endif
-/* Some ascii-shorthands fitted into the ncurses namespace. */
+/* Some ASCII-shorthands fitted into the ncurses namespace. */
#define KEY_TAB '\t'
#define KEY_RETURN '\r'
#define KEY_ESC 27
#define string_add(dst, from, src) \
string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
+static size_t
+string_expand_length(const char *line, int tabsize)
+{
+ size_t size, pos;
+
+ for (pos = 0; line[pos]; pos++) {
+ if (line[pos] == '\t' && tabsize > 0)
+ size += tabsize - (size % tabsize);
+ else
+ size++;
+ }
+ return size;
+}
+
+static void
+string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
+{
+ size_t size, pos;
+
+ for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
+ if (src[pos] == '\t') {
+ size_t expanded = tabsize - (size % tabsize);
+
+ if (expanded + size >= dstlen - 1)
+ expanded = dstlen - size - 1;
+ memcpy(dst + size, " ", expanded);
+ size += expanded;
+ } else {
+ dst[size++] = src[pos];
+ }
+ }
+
+ dst[size] = 0;
+}
+
static char *
chomp_string(char *name)
{
return done_io(&io) || error;
}
-static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
+static int
+io_load(struct io *io, const char *separators,
+ int (*read_property)(char *, size_t, char *, size_t))
+{
+ char *name;
+ int state = OK;
+
+ if (!start_io(io))
+ return ERR;
+
+ while (state == OK && (name = io_get(io, '\n', TRUE))) {
+ char *value;
+ size_t namelen;
+ size_t valuelen;
+
+ name = chomp_string(name);
+ namelen = strcspn(name, separators);
+
+ if (name[namelen]) {
+ name[namelen] = 0;
+ value = chomp_string(name + namelen + 1);
+ valuelen = strlen(value);
+
+ } else {
+ value = "";
+ valuelen = 0;
+ }
+
+ state = read_property(name, namelen, value, valuelen);
+ }
+
+ if (state != ERR && io_error(io))
+ state = ERR;
+ done_io(io);
+
+ return state;
+}
+
+static int
+run_io_load(const char **argv, const char *separators,
+ int (*read_property)(char *, size_t, char *, size_t))
+{
+ struct io io = {};
+
+ return init_io_rd(&io, argv, NULL, FORMAT_NONE)
+ ? io_load(&io, separators, read_property) : ERR;
+}
+
/*
* User requests
@@ -698,6 +781,8 @@ static int read_properties(struct io *io, const char *separators, int (*read)(ch
REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
\
REQ_GROUP("Scrolling") \
+ REQ_(SCROLL_LEFT, "Scroll two columns left"), \
+ REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
* Options
*/
-static const char usage[] =
-"tig " TIG_VERSION " (" __DATE__ ")\n"
-"\n"
-"Usage: tig [options] [revs] [--] [paths]\n"
-" or: tig show [options] [revs] [--] [paths]\n"
-" or: tig blame [rev] path\n"
-" or: tig status\n"
-" or: tig < [git command output]\n"
-"\n"
-"Options:\n"
-" -v, --version Show version and exit\n"
-" -h, --help Show help message and exit";
-
/* Option and state variables. */
static bool opt_date = TRUE;
static bool opt_author = TRUE;
#define is_initial_commit() (!*opt_head_rev)
#define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
-static enum request
-parse_options(int argc, const char *argv[], const char ***run_argv)
-{
- enum request request = REQ_VIEW_MAIN;
- const char *subcommand;
- bool seen_dashdash = FALSE;
- /* XXX: This is vulnerable to the user overriding options
- * required for the main view parser. */
- static const char *custom_argv[SIZEOF_ARG] = {
- "git", "log", "--no-color", "--pretty=raw", "--parents",
- "--topo-order", NULL
- };
- int i, j = 6;
-
- if (!isatty(STDIN_FILENO))
- return REQ_VIEW_PAGER;
-
- if (argc <= 1)
- return REQ_VIEW_MAIN;
-
- subcommand = argv[1];
- if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
- if (!strcmp(subcommand, "-S"))
- warn("`-S' has been deprecated; use `tig status' instead");
- if (argc > 2)
- warn("ignoring arguments after `%s'", subcommand);
- return REQ_VIEW_STATUS;
-
- } else if (!strcmp(subcommand, "blame")) {
- if (argc <= 2 || argc > 4)
- die("invalid number of options to blame\n\n%s", usage);
-
- i = 2;
- if (argc == 4) {
- string_ncopy(opt_ref, argv[i], strlen(argv[i]));
- i++;
- }
-
- string_ncopy(opt_file, argv[i], strlen(argv[i]));
- return REQ_VIEW_BLAME;
-
- } else if (!strcmp(subcommand, "show")) {
- request = REQ_VIEW_DIFF;
-
- } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
- request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
- warn("`tig %s' has been deprecated", subcommand);
-
- } else {
- subcommand = NULL;
- }
-
- if (subcommand) {
- custom_argv[1] = subcommand;
- j = 2;
- }
-
- for (i = 1 + !!subcommand; i < argc; i++) {
- const char *opt = argv[i];
-
- if (seen_dashdash || !strcmp(opt, "--")) {
- seen_dashdash = TRUE;
-
- } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
- printf("tig version %s\n", TIG_VERSION);
- return REQ_NONE;
-
- } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
- printf("%s\n", usage);
- return REQ_NONE;
- }
-
- custom_argv[j++] = opt;
- if (j >= ARRAY_SIZE(custom_argv))
- die("command too long");
- }
-
- custom_argv[j] = NULL;
- *run_argv = custom_argv;
-
- return request;
-}
-
/*
* Line-oriented content detection.
LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
-LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
+LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
-LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
-LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
-LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
{ '-', REQ_MOVE_PAGE_UP },
/* Scrolling */
+ { KEY_LEFT, REQ_SCROLL_LEFT },
+ { KEY_RIGHT, REQ_SCROLL_RIGHT },
{ KEY_IC, REQ_SCROLL_LINE_UP },
{ KEY_DC, REQ_SCROLL_LINE_DOWN },
{ 'w', REQ_SCROLL_PAGE_UP },
static bool config_errors;
static const char *config_msg;
-/* Wants: object fgcolor bgcolor [attr] */
+/* Wants: object fgcolor bgcolor [attribute] */
static int
option_color_command(int argc, const char *argv[])
{
} else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
info = get_line_info("date");
+ } else if (!string_enum_compare(argv[0], "main-author", strlen("main-author"))) {
+ info = get_line_info("author");
+
} else {
config_msg = "Unknown color name";
return ERR;
int arglen = strlen(arg);
switch (arg[0]) {
- case '"':
+ case '\"':
case '\'':
if (arglen == 1 || arg[arglen - 1] != arg[0]) {
config_msg = "Unmatched quotation";
{
struct io io = {};
- /* It's ok that the file doesn't exist. */
+ /* It's OK that the file doesn't exist. */
if (!io_open(&io, path))
return;
config_lineno = 0;
config_errors = FALSE;
- if (read_properties(&io, " \t", read_option) == ERR ||
+ if (io_load(&io, " \t", read_option) == ERR ||
config_errors == TRUE)
warn("Errors while loading %s.", path);
}
/* Navigation */
unsigned long offset; /* Offset of the window top */
+ unsigned long yoffset; /* Offset from the window side. */
unsigned long lineno; /* Current line number */
unsigned long p_offset; /* Previous offset of the window top */
+ unsigned long p_yoffset;/* Previous offset from the window side */
unsigned long p_lineno; /* Previous current line number */
bool p_restore; /* Should the previous position be restored. */
/* Searching */
char grep[SIZEOF_STR]; /* Search string */
- regex_t *regex; /* Pre-compiled regex */
+ regex_t *regex; /* Pre-compiled regexp */
/* If non-NULL, points to the view that opened this view. If this view
* is closed tig will switch back to the parent view. */
enum line_type curtype; /* Attribute currently used for drawing. */
unsigned long col; /* Column when drawing. */
bool has_scrolled; /* View was scrolled. */
+ bool can_hscroll; /* View can be scrolled horizontally. */
/* Loading */
struct io io;
bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
/* 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. */
+ /* Search for regexp in a line. */
bool (*grep)(struct view *view, struct line *line);
/* Select line */
void (*select)(struct view *view, struct line *line);
int len = 0;
int col = 0;
int trimmed = FALSE;
+ size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
if (max_len <= 0)
return 0;
if (opt_utf8) {
- len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
+ len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
} else {
col = len = strlen(string);
if (len > max_len) {
}
set_view_attr(view, type);
- waddnstr(view->win, string, len);
+ if (len > 0)
+ waddnstr(view->win, string, len);
if (trimmed && use_tilde) {
set_view_attr(view, LINE_DELIMITER);
waddch(view->win, '~');
col++;
}
+ if (view->col + col >= view->width + view->yoffset)
+ view->can_hscroll = TRUE;
+
return col;
}
static bool
draw_lineno(struct view *view, unsigned int lineno)
{
+ size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
char number[10];
int digits3 = view->digits < 3 ? 3 : view->digits;
int max_number = MIN(digits3, STRING_SIZE(number));
col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
}
- if (col < max) {
+ if (col < max && skip <= col) {
set_view_attr(view, LINE_DEFAULT);
waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
- col++;
}
+ col++;
- if (col < max)
- col += draw_space(view, LINE_DEFAULT, max - col, 1);
view->col += col;
+ if (col < max && skip <= col)
+ col = draw_space(view, LINE_DEFAULT, max - col, 1);
+ view->col++;
- return view->width - view->col <= 0;
+ return view->width + view->yoffset <= view->col;
}
static bool
draw_text(struct view *view, enum line_type type, const char *string, bool trim)
{
- view->col += draw_chars(view, type, string, view->width - view->col, trim);
+ view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
return view->width - view->col <= 0;
}
static bool
draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
{
+ size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
int max = view->width - view->col;
int i;
@@ -1964,14 +1968,13 @@ draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t si
set_view_attr(view, type);
/* Using waddch() instead of waddnstr() ensures that
* they'll be rendered correctly for the cursor line. */
- for (i = 0; i < size; i++)
+ for (i = skip; i < size; i++)
waddch(view->win, graphic[i]);
view->col += size;
- if (size < max) {
+ if (size < max && skip <= size)
waddch(view->win, ' ');
- view->col++;
- }
+ view->col++;
return view->width - view->col <= 0;
}
@@ -1987,8 +1990,9 @@ draw_field(struct view *view, enum line_type type, const char *text, int len, bo
else
col = draw_space(view, type, max - 1, max - 1);
- view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
- return view->width - view->col <= 0;
+ view->col += col;
+ view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
+ return view->width + view->yoffset <= view->col;
}
static bool
author = initials;
}
- return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim);
+ return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
+}
+
+static bool
+draw_mode(struct view *view, mode_t mode)
+{
+ static const char dir_mode[] = "drwxr-xr-x";
+ static const char link_mode[] = "lrwxrwxrwx";
+ static const char exe_mode[] = "-rwxr-xr-x";
+ static const char file_mode[] = "-rw-r--r--";
+ const char *str;
+
+ if (S_ISDIR(mode))
+ str = dir_mode;
+ else if (S_ISLNK(mode))
+ str = link_mode;
+ else if (mode & S_IXUSR)
+ str = exe_mode;
+ else
+ str = file_mode;
+
+ return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
}
static bool
{
assert(0 <= lineno && lineno < view->height);
+ if (lineno == 0)
+ view->can_hscroll = FALSE;
+
for (; lineno < view->height; lineno++) {
if (!draw_view_line(view, lineno))
break;
assert(view_is_displayed(view));
switch (request) {
+ case REQ_SCROLL_LEFT:
+ if (view->yoffset == 0) {
+ report("Cannot scroll beyond the first column");
+ return;
+ }
+ if (view->yoffset <= SCROLL_INTERVAL)
+ view->yoffset = 0;
+ else
+ view->yoffset -= SCROLL_INTERVAL;
+ redraw_view_from(view, 0);
+ report("");
+ return;
+ case REQ_SCROLL_RIGHT:
+ if (!view->can_hscroll) {
+ report("Cannot scroll beyond the last column");
+ return;
+ }
+ view->yoffset += SCROLL_INTERVAL;
+ redraw_view(view);
+ report("");
+ return;
case REQ_SCROLL_PAGE_DOWN:
lines = view->height;
case REQ_SCROLL_LINE_DOWN:
free(view->line);
view->p_offset = view->offset;
+ view->p_yoffset = view->yoffset;
view->p_lineno = view->lineno;
view->line = NULL;
view->offset = 0;
+ view->yoffset = 0;
view->lines = 0;
view->lineno = 0;
view->line_alloc = 0;
werase(view->win);
view->offset = view->p_offset;
+ view->yoffset = view->p_yoffset;
view->lineno = view->p_lineno;
view->p_restore = FALSE;
move_view(view, request);
break;
+ case REQ_SCROLL_LEFT:
+ case REQ_SCROLL_RIGHT:
case REQ_SCROLL_LINE_DOWN:
case REQ_SCROLL_LINE_UP:
case REQ_SCROLL_PAGE_DOWN:
static bool
pager_draw(struct view *view, struct line *line, unsigned int lineno)
{
- char *text = line->data;
+ char text[SIZEOF_STR];
if (opt_line_number && draw_lineno(view, lineno))
return TRUE;
+ string_expand(text, sizeof(text), line->data, opt_tab_size);
draw_text(view, line->type, text, TRUE);
return TRUE;
}
/* Parse output from git-ls-tree(1):
*
- * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
- * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
* 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
- * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
*/
#define SIZEOF_TREE_ATTR \
- STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
+ STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
#define SIZEOF_TREE_MODE \
STRING_SIZE("100644 ")
};
struct io io = {};
+ if (!view->lines) {
+ tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
+ report("Tree is empty");
+ return TRUE;
+ }
+
if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
report("Failed to load tree data");
return TRUE;
if (textlen <= SIZEOF_TREE_ATTR)
return FALSE;
if (view->lines == 0 &&
- !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
+ !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
return FALSE;
/* Strip the path part ... */
{
struct tree_entry *entry = line->data;
- if (line->type == LINE_TREE_PARENT) {
+ if (line->type == LINE_TREE_HEAD) {
if (draw_text(view, line->type, "Directory path /", TRUE))
return TRUE;
} else {
- char mode[11] = "-r--r--r--";
-
- if (S_ISDIR(entry->mode)) {
- mode[3] = mode[6] = mode[9] = 'x';
- mode[0] = 'd';
- }
- if (S_ISLNK(entry->mode))
- mode[0] = 'l';
- if (entry->mode & S_IWUSR)
- mode[2] = 'w';
- if (entry->mode & S_IXUSR)
- mode[3] = 'x';
- if (entry->mode & S_IXGRP)
- mode[6] = 'x';
- if (entry->mode & S_IXOTH)
- mode[9] = 'x';
- if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
+ if (draw_mode(view, entry->mode))
return TRUE;
if (opt_author && draw_author(view, entry->author))
switch (line->type) {
case LINE_TREE_DIR:
- /* Depending on whether it is a subdir or parent (updir?) link
+ /* Depending on whether it is a subdirectory or parent link
* mangle the path buffer. */
if (line == &view->line[1] && *opt_path) {
pop_tree_stack_entry();
return FALSE;
} else {
- size_t linelen = strlen(line);
+ size_t linelen = string_expand_length(line, opt_tab_size);
struct blame *blame = malloc(sizeof(*blame) + linelen);
+ if (!blame)
+ return FALSE;
+
blame->commit = NULL;
- strncpy(blame->text, line, linelen);
- blame->text[linelen] = 0;
+ string_expand(blame->text, linelen + 1, line, opt_tab_size);
return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
}
}
static bool
status_run(struct view *view, const char *argv[], char status, enum line_type type)
{
- struct status *file = NULL;
struct status *unmerged = NULL;
char *buf;
struct io io = {};
@@ -4584,6 +4627,8 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
add_line_data(view, NULL, type);
while ((buf = io_get(&io, 0, TRUE))) {
+ struct status *file = unmerged;
+
if (!file) {
file = calloc(1, sizeof(*file));
if (!file || !add_line_data(view, file, type))
@@ -4596,7 +4641,7 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
if (status == 'A')
string_copy(file->old.rev, NULL_ID);
- } else if (!file->status) {
+ } else if (!file->status || file == unmerged) {
if (!status_get_diff(file, buf, strlen(buf)))
goto error_out;
@@ -4604,21 +4649,13 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
if (!buf)
break;
- /* Collapse all 'M'odified entries that follow a
- * associated 'U'nmerged entry. */
- if (file->status == 'U') {
- unmerged = file;
-
- } else if (unmerged) {
- int collapse = !strcmp(buf, unmerged->new.name);
-
+ /* Collapse all modified entries that follow an
+ * associated unmerged entry. */
+ if (unmerged == file) {
+ unmerged->status = 'U';
unmerged = NULL;
- if (collapse) {
- free(file);
- file = NULL;
- view->lines--;
- continue;
- }
+ } else if (file->status == 'U') {
+ unmerged = file;
}
}
case LINE_STAT_NONE:
type = LINE_DEFAULT;
- text = " (no files)";
+ text = " (no files)";
break;
case LINE_STAT_HEAD:
return FALSE;
} else {
+ char mode[10] = "100644";
+ const char *reset_argv[] = {
+ "git", "update-index", "--cacheinfo", mode,
+ status->old.rev, status->old.name, NULL
+ };
const char *checkout_argv[] = {
"git", "checkout", "--", status->old.name, NULL
};
if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
return FALSE;
- return run_io_fg(checkout_argv, opt_cdup);
+ string_format(mode, "%o", status->old.mode);
+ return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
+ run_io_fg(checkout_argv, opt_cdup);
}
}
/* FIXME: More graceful handling of titles; append "..." to
* shortened titles, etc. */
- string_ncopy(commit->title, line, strlen(line));
+ string_expand(commit->title, sizeof(commit->title), line, 1);
view->line[view->lines - 1].dirty = 1;
}
/*
* Unicode / UTF-8 handling
*
- * NOTE: Much of the following code for dealing with unicode is derived from
+ * NOTE: Much of the following code for dealing with Unicode is derived from
* ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
- * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
+ * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
*/
-/* I've (over)annotated a lot of code snippets because I am not entirely
- * confident that the approach taken by this small UTF-8 interface is correct.
- * --jonas */
-
static inline int
unicode_width(unsigned long c)
{
3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
};
-/* Decode UTF-8 multi-byte representation into a unicode character. */
+/* Decode UTF-8 multi-byte representation into a Unicode character. */
static inline unsigned long
utf8_to_unicode(const char *string, size_t length)
{
unicode += (string[5] & 0x3f);
break;
default:
- die("Invalid unicode length");
+ die("Invalid Unicode length");
}
/* Invalid characters could return the special 0xfffd value but NUL
*
* Returns the number of bytes to output from string to satisfy max_width. */
static size_t
-utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
+utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
{
- const char *start = string;
+ const char *string = *start;
const char *end = strchr(string, '\0');
unsigned char last_bytes = 0;
size_t last_ucwidth = 0;
@@ -6070,11 +6110,15 @@ utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool
* it is a single- or double-width character. */
unicode = utf8_to_unicode(string, bytes);
- /* FIXME: Graceful handling of invalid unicode character. */
+ /* FIXME: Graceful handling of invalid Unicode character. */
if (!unicode)
break;
ucwidth = unicode_width(unicode);
+ if (skip > 0) {
+ skip -= ucwidth <= skip ? ucwidth : skip;
+ *start += bytes;
+ }
*width += ucwidth;
if (*width > max_width) {
*trimmed = 1;
@@ -6087,11 +6131,11 @@ utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool
}
string += bytes;
- last_bytes = bytes;
+ last_bytes = ucwidth ? bytes : 0;
last_ucwidth = ucwidth;
}
- return string - start;
+ return string - *start;
}
if (!cursed)
die("Failed to initialize curses");
- nonl(); /* Tell curses not to do NL->CR/NL on output */
+ nonl(); /* Disable conversion and detect newlines from input. */
cbreak(); /* Take input chars one at a time, no wait for \n */
noecho(); /* Don't echo input */
leaveok(stdscr, FALSE);
* Repository properties
*/
-static int
-git_properties(const char **argv, const char *separators,
- int (*read_property)(char *, size_t, char *, size_t))
-{
- struct io io = {};
-
- if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
- return read_properties(&io, separators, read_property);
- return ERR;
-}
-
static struct ref *refs = NULL;
static size_t refs_alloc = 0;
static size_t refs_size = 0;
}
if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
- /* it's an annotated tag, replace the previous sha1 with the
+ /* it's an annotated tag, replace the previous SHA1 with the
* resolved commit id; relies on the fact git-ls-remote lists
* the commit id of an annotated tag right before the commit id
* it points to. */
while (id_refs_size > 0)
free(id_refs[--id_refs_size]);
- return git_properties(ls_remote_argv, "\t", read_ref);
+ return run_io_load(ls_remote_argv, "\t", read_ref);
}
static int
{
const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
- return git_properties(config_list_argv, "=", read_repo_config_option);
+ return run_io_load(config_list_argv, "=", read_repo_config_option);
}
static int
}
}
- return git_properties(rev_parse_argv, "=", read_repo_info);
-}
-
-static int
-read_properties(struct io *io, const char *separators,
- int (*read_property)(char *, size_t, char *, size_t))
-{
- char *name;
- int state = OK;
-
- if (!start_io(io))
- return ERR;
-
- while (state == OK && (name = io_get(io, '\n', TRUE))) {
- char *value;
- size_t namelen;
- size_t valuelen;
-
- name = chomp_string(name);
- namelen = strcspn(name, separators);
-
- if (name[namelen]) {
- name[namelen] = 0;
- value = chomp_string(name + namelen + 1);
- valuelen = strlen(value);
-
- } else {
- value = "";
- valuelen = 0;
- }
-
- state = read_property(name, namelen, value, valuelen);
- }
-
- if (state != ERR && io_error(io))
- state = ERR;
- done_io(io);
-
- return state;
+ return run_io_load(rev_parse_argv, "=", read_repo_info);
}
* Main
*/
+static const char usage[] =
+"tig " TIG_VERSION " (" __DATE__ ")\n"
+"\n"
+"Usage: tig [options] [revs] [--] [paths]\n"
+" or: tig show [options] [revs] [--] [paths]\n"
+" or: tig blame [rev] path\n"
+" or: tig status\n"
+" or: tig < [git command output]\n"
+"\n"
+"Options:\n"
+" -v, --version Show version and exit\n"
+" -h, --help Show help message and exit";
+
static void __NORETURN
quit(int sig)
{
va_end(args);
}
+static enum request
+parse_options(int argc, const char *argv[])
+{
+ enum request request = REQ_VIEW_MAIN;
+ const char *subcommand;
+ bool seen_dashdash = FALSE;
+ /* XXX: This is vulnerable to the user overriding options
+ * required for the main view parser. */
+ const char *custom_argv[SIZEOF_ARG] = {
+ "git", "log", "--no-color", "--pretty=raw", "--parents",
+ "--topo-order", NULL
+ };
+ int i, j = 6;
+
+ if (!isatty(STDIN_FILENO)) {
+ io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
+ return REQ_VIEW_PAGER;
+ }
+
+ if (argc <= 1)
+ return REQ_NONE;
+
+ subcommand = argv[1];
+ if (!strcmp(subcommand, "status")) {
+ if (argc > 2)
+ warn("ignoring arguments after `%s'", subcommand);
+ return REQ_VIEW_STATUS;
+
+ } else if (!strcmp(subcommand, "blame")) {
+ if (argc <= 2 || argc > 4)
+ die("invalid number of options to blame\n\n%s", usage);
+
+ i = 2;
+ if (argc == 4) {
+ string_ncopy(opt_ref, argv[i], strlen(argv[i]));
+ i++;
+ }
+
+ string_ncopy(opt_file, argv[i], strlen(argv[i]));
+ return REQ_VIEW_BLAME;
+
+ } else if (!strcmp(subcommand, "show")) {
+ request = REQ_VIEW_DIFF;
+
+ } else {
+ subcommand = NULL;
+ }
+
+ if (subcommand) {
+ custom_argv[1] = subcommand;
+ j = 2;
+ }
+
+ for (i = 1 + !!subcommand; i < argc; i++) {
+ const char *opt = argv[i];
+
+ if (seen_dashdash || !strcmp(opt, "--")) {
+ seen_dashdash = TRUE;
+
+ } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
+ printf("tig version %s\n", TIG_VERSION);
+ quit(0);
+
+ } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
+ printf("%s\n", usage);
+ quit(0);
+ }
+
+ custom_argv[j++] = opt;
+ if (j >= ARRAY_SIZE(custom_argv))
+ die("command too long");
+ }
+
+ if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
+ die("Failed to format arguments");
+
+ return request;
+}
+
int
main(int argc, const char *argv[])
{
- const char **run_argv = NULL;
+ enum request request = parse_options(argc, argv);
struct view *view;
- enum request request;
size_t i;
signal(SIGINT, quit);
if (load_git_config() == ERR)
die("Failed to load repo config.");
- request = parse_options(argc, argv, &run_argv);
- if (request == REQ_NONE)
- return 0;
-
/* Require a git repository unless when running in pager mode. */
if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
die("Not a git repository");
init_display();
- if (request == REQ_VIEW_PAGER || run_argv) {
- if (request == REQ_VIEW_PAGER)
- io_open(&VIEW(request)->io, "");
- else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
- die("Failed to format arguments");
+ if (request != REQ_NONE)
open_view(NULL, request, OPEN_PREPARED);
- request = REQ_NONE;
- }
+ request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
while (view_driver(display[current_view], request)) {
int key = get_input(0);
if (search)
string_ncopy(opt_search, search, strlen(search));
+ else if (*opt_search)
+ request = request == REQ_SEARCH ?
+ REQ_FIND_NEXT :
+ REQ_FIND_PREV;
else
request = REQ_NONE;
break;