X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=tig.c;h=dc1d2251464c031c7744d22bd9ebac928975c0a4;hb=37b8cf4848889adb48c16a0f1c90ea068cbe211a;hp=0e1028a7d709ffda3200e85b63c2d583785ebb5e;hpb=499019c495095ecf265becbc1361548a676dc2ae;p=tig.git diff --git a/tig.c b/tig.c index 0e1028a..dc1d225 100644 --- a/tig.c +++ b/tig.c @@ -69,7 +69,6 @@ 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 bool prompt_yesno(const char *prompt); static int load_refs(void); #define ABS(x) ((x) >= 0 ? (x) : -(x)) @@ -171,6 +170,17 @@ set_from_int_map(struct int_map *map, size_t map_size, return ERR; } +enum input_status { + INPUT_OK, + INPUT_SKIP, + INPUT_STOP, + INPUT_CANCEL +}; + +typedef enum input_status (*input_handler)(void *data, char *buf, int c); + +static char *prompt_input(const char *prompt, input_handler handler, void *data); +static bool prompt_yesno(const char *prompt); /* * String helpers @@ -282,7 +292,7 @@ argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd) cmd[valuelen] = 0; argv[(*argc)++] = chomp_string(cmd); - cmd += valuelen + advance; + cmd = chomp_string(cmd + valuelen + advance); } if (*argc < SIZEOF_ARG) @@ -368,7 +378,7 @@ io_open(struct io *io, const char *name) static bool kill_io(struct io *io) { - return kill(io->pid, SIGKILL) != -1; + return io->pid == 0 || kill(io->pid, SIGKILL) != -1; } static bool @@ -642,7 +652,54 @@ run_io_buf(const char **argv, char buf[], size_t bufsize) 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 @@ -666,6 +723,7 @@ static int read_properties(struct io *io, const char *separators, int (*read)(ch REQ_(ENTER, "Enter current line and scroll"), \ REQ_(NEXT, "Move to next"), \ REQ_(PREVIOUS, "Move to previous"), \ + REQ_(PARENT, "Move to parent"), \ REQ_(VIEW_NEXT, "Move focus to next view"), \ REQ_(REFRESH, "Reload and refresh"), \ REQ_(MAXIMIZE, "Maximize the current view"), \ @@ -677,7 +735,6 @@ static int read_properties(struct io *io, const char *separators, int (*read)(ch REQ_(STATUS_REVERT, "Revert file changes"), \ REQ_(STATUS_MERGE, "Merge file using external tool"), \ REQ_(STAGE_NEXT, "Find next chunk to stage"), \ - REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \ \ REQ_GROUP("Cursor navigation") \ REQ_(MOVE_UP, "Move cursor one line up"), \ @@ -797,6 +854,7 @@ static char opt_codeset[20] = "UTF-8"; static iconv_t opt_iconv = ICONV_NONE; static char opt_search[SIZEOF_STR] = ""; static char opt_cdup[SIZEOF_STR] = ""; +static char opt_prefix[SIZEOF_STR] = ""; static char opt_git_dir[SIZEOF_STR] = ""; static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */ static char opt_editor[SIZEOF_STR] = ""; @@ -939,7 +997,9 @@ LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \ 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_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \ +LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \ +LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \ +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), \ LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \ @@ -1113,7 +1173,7 @@ static struct keybinding default_keybindings[] = { { '!', REQ_STATUS_REVERT }, { 'M', REQ_STATUS_MERGE }, { '@', REQ_STAGE_NEXT }, - { ',', REQ_TREE_PARENT }, + { ',', REQ_PARENT }, { 'e', REQ_EDIT }, }; @@ -1548,16 +1608,25 @@ option_bind_command(int argc, const char *argv[]) request = get_request(argv[2]); if (request == REQ_NONE) { - const char *obsolete[] = { "cherry-pick", "screen-resize" }; + struct { + const char *name; + enum request request; + } obsolete[] = { + { "cherry-pick", REQ_NONE }, + { "screen-resize", REQ_NONE }, + { "tree-parent", REQ_PARENT }, + }; size_t namelen = strlen(argv[2]); int i; for (i = 0; i < ARRAY_SIZE(obsolete); i++) { - if (namelen == strlen(obsolete[i]) && - !string_enum_compare(obsolete[i], argv[2], namelen)) { - config_msg = "Obsolete request name"; - return ERR; - } + if (namelen != strlen(obsolete[i].name) || + string_enum_compare(obsolete[i].name, argv[2], namelen)) + continue; + if (obsolete[i].request != REQ_NONE) + add_keybinding(keymap, obsolete[i].request, key); + config_msg = "Obsolete request name"; + return ERR; } } if (request == REQ_NONE && *argv[2]++ == '!') @@ -1627,8 +1696,8 @@ read_option(char *opt, size_t optlen, char *value, size_t valuelen) } if (status == ERR) { - fprintf(stderr, "Error on line %d, near '%.*s': %s\n", - config_lineno, (int) optlen, opt, config_msg); + warn("Error on line %d, near '%.*s': %s", + config_lineno, (int) optlen, opt, config_msg); config_errors = TRUE; } @@ -1648,9 +1717,9 @@ load_option_file(const char *path) 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) - fprintf(stderr, "Errors while loading %s.\n", path); + warn("Errors while loading %s.", path); } static int @@ -1692,9 +1761,6 @@ struct view_ops; static struct view *display[2]; static unsigned int current_view; -/* Reading from the prompt? */ -static bool input_mode = FALSE; - #define foreach_displayed_view(view, i) \ for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++) @@ -1725,6 +1791,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 */ @@ -1744,6 +1813,7 @@ struct view { struct line *curline; /* Line currently being drawn. */ enum line_type curtype; /* Attribute currently used for drawing. */ unsigned long col; /* Column when drawing. */ + bool has_scrolled; /* View was scrolled. */ /* Loading */ struct io io; @@ -1982,12 +2052,37 @@ draw_date(struct view *view, struct tm *time) return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE); } +static bool +draw_author(struct view *view, const char *author) +{ + bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author; + + if (!trim) { + static char initials[10]; + size_t pos; + +#define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@') + + memset(initials, 0, sizeof(initials)); + for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) { + while (is_initial_sep(*author)) + author++; + strncpy(&initials[pos], author, sizeof(initials) - 1 - pos); + while (*author && !is_initial_sep(author[1])) + author++; + } + + author = initials; + } + + return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim); +} + static bool draw_view_line(struct view *view, unsigned int lineno) { struct line *line; bool selected = (view->offset + lineno == view->lineno); - bool draw_ok; assert(view_is_displayed(view)); @@ -2011,11 +2106,7 @@ draw_view_line(struct view *view, unsigned int lineno) view->ops->select(view, line); } - scrollok(view->win, FALSE); - draw_ok = view->ops->draw(view, line, lineno); - scrollok(view->win, TRUE); - - return draw_ok; + return view->ops->draw(view, line, lineno); } static void @@ -2036,11 +2127,7 @@ redraw_view_dirty(struct view *view) if (!dirty) return; - redrawwin(view->win); - if (input_mode) - wnoutrefresh(view->win); - else - wrefresh(view->win); + wnoutrefresh(view->win); } static void @@ -2053,11 +2140,7 @@ redraw_view_from(struct view *view, int lineno) break; } - redrawwin(view->win); - if (input_mode) - wnoutrefresh(view->win); - else - wrefresh(view->win); + wnoutrefresh(view->win); } static void @@ -2120,12 +2203,7 @@ update_view_title(struct view *view) mvwaddnstr(view->title, 0, 0, buf, bufpos); wclrtoeol(view->title); - wmove(view->title, 0, view->width - 1); - - if (input_mode) - wnoutrefresh(view->title); - else - wrefresh(view->title); + wnoutrefresh(view->title); } static void @@ -2163,7 +2241,7 @@ resize_display(void) if (!view->win) die("Failed to create %s view", view->name); - scrollok(view->win, TRUE); + scrollok(view->win, FALSE); view->title = newwin(1, 0, offset + view->height, 0); if (!view->title) @@ -2193,19 +2271,6 @@ redraw_display(bool clear) } } -static void -update_display_cursor(struct view *view) -{ - /* Move the cursor to the right-most column of the cursor line. - * - * XXX: This could turn out to be a bit expensive, but it ensures that - * the cursor does not jump around. */ - if (view->lines) { - wmove(view->win, view->lineno - view->offset, view->width - 1); - wrefresh(view->win); - } -} - static void toggle_view_option(bool *option, const char *help) { @@ -2249,19 +2314,19 @@ do_scroll_view(struct view *view, int lines) int line = lines > 0 ? view->height - lines : 0; int end = line + ABS(lines); + scrollok(view->win, TRUE); wscrl(view->win, lines); + scrollok(view->win, FALSE); - for (; line < end; line++) { - if (!draw_view_line(view, line)) - break; - } + while (line < end && draw_view_line(view, line)) + line++; if (redraw_current_line) draw_view_line(view, view->lineno - view->offset); + wnoutrefresh(view->win); } - redrawwin(view->win); - wrefresh(view->win); + view->has_scrolled = TRUE; report(""); } @@ -2394,8 +2459,7 @@ move_view(struct view *view, enum request request) /* Draw the current line */ draw_view_line(view, view->lineno - view->offset); - redrawwin(view->win); - wrefresh(view->win); + wnoutrefresh(view->win); report(""); } @@ -2422,8 +2486,7 @@ select_view_line(struct view *view, unsigned long lineno) 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); + wnoutrefresh(view->win); } else { view->ops->select(view, &view->line[view->lineno]); } @@ -2516,6 +2579,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; @@ -2597,6 +2663,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) { @@ -2715,7 +2818,7 @@ update_view(struct view *view) if (view->lines == 0) { time_t secs = time(NULL) - view->start_time; - if (secs > view->update_secs) { + if (secs > 1 && secs > view->update_secs) { if (view->update_secs == 0) redraw_view(view); update_view_title(view); @@ -2740,8 +2843,11 @@ update_view(struct view *view) line = out_buffer; } - if (!view->ops->read(view, line)) - goto alloc_error; + if (!view->ops->read(view, line)) { + report("Allocation failure"); + end_update(view, TRUE); + return FALSE; + } } { @@ -2768,6 +2874,9 @@ update_view(struct view *view) end_update(view, FALSE); } + if (restore_view_position(view)) + redraw = TRUE; + if (!view_is_displayed(view)) return TRUE; @@ -2780,11 +2889,6 @@ update_view(struct view *view) * commit reference in view->ref it'll be available here. */ update_view_title(view); return TRUE; - -alloc_error: - report("Allocation failure"); - end_update(view, TRUE); - return FALSE; } static struct line * @@ -2879,10 +2983,13 @@ open_view(struct view *prev, enum request request, enum open_flags flags) resize_display(); if (view->ops->open) { + if (view->pipe) + end_update(view, TRUE); if (!view->ops->open(view)) { 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))) { @@ -2912,6 +3019,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); @@ -3217,6 +3325,110 @@ view_driver(struct view *view, enum request request) } +/* + * View backend utilities + */ + +/* Parse author lines where the name may be empty: + * author 1138474660 +0100 + */ +static void +parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm) +{ + char *nameend = strchr(ident, '<'); + char *emailend = strchr(ident, '>'); + + if (nameend && emailend) + *nameend = *emailend = 0; + ident = chomp_string(ident); + if (!*ident) { + if (nameend) + ident = chomp_string(nameend + 1); + if (!*ident) + ident = "Unknown"; + } + + string_ncopy_do(author, authorsize, ident, strlen(ident)); + + /* Parse epoch and timezone */ + if (emailend && emailend[1] == ' ') { + char *secs = emailend + 2; + char *zone = strchr(secs, ' '); + time_t time = (time_t) atol(secs); + + if (zone && strlen(zone) == STRING_SIZE(" +0700")) { + long tz; + + zone++; + tz = ('0' - zone[1]) * 60 * 60 * 10; + tz += ('0' - zone[2]) * 60 * 60; + tz += ('0' - zone[3]) * 60; + tz += ('0' - zone[4]) * 60; + + if (zone[0] == '-') + tz = -tz; + + time -= tz; + } + + gmtime_r(&time, tm); + } +} + +static enum input_status +select_commit_parent_handler(void *data, char *buf, int c) +{ + size_t parents = *(size_t *) data; + int parent = 0; + + if (!isdigit(c)) + return INPUT_SKIP; + + if (*buf) + parent = atoi(buf) * 10; + parent += c - '0'; + + if (parent > parents) + return INPUT_SKIP; + return INPUT_OK; +} + +static bool +select_commit_parent(const char *id, char rev[SIZEOF_REV]) +{ + char buf[SIZEOF_STR * 4]; + const char *revlist_argv[] = { + "git", "rev-list", "-1", "--parents", id, NULL + }; + int parents; + + if (!run_io_buf(revlist_argv, buf, sizeof(buf)) || + !*chomp_string(buf) || + (parents = (strlen(buf) / 40) - 1) < 0) { + report("Failed to get parent information"); + return FALSE; + + } else if (parents == 0) { + report("The selected commit has no parents"); + return FALSE; + } + + if (parents > 1) { + char prompt[SIZEOF_STR]; + char *result; + + if (!string_format(prompt, "Which parent? [1..%d] ", parents)) + return FALSE; + result = prompt_input(prompt, select_commit_parent_handler, &parents); + if (!result) + return FALSE; + parents = atoi(result); + } + + string_copy_rev(rev, &buf[41 * parents]); + return TRUE; +} + /* * Pager backend */ @@ -3435,22 +3647,13 @@ static struct view_ops diff_ops = { static bool help_open(struct view *view) { - int lines = ARRAY_SIZE(req_info) + 2; + char buf[SIZEOF_STR]; + size_t bufpos; int i; if (view->lines > 0) return TRUE; - for (i = 0; i < ARRAY_SIZE(req_info); i++) - if (!req_info[i].request) - lines++; - - lines += run_requests + 1; - - view->line = calloc(lines, sizeof(*view->line)); - if (!view->line) - return FALSE; - add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT); for (i = 0; i < ARRAY_SIZE(req_info); i++) { @@ -3469,8 +3672,14 @@ help_open(struct view *view) if (!*key) key = "(no key defined)"; - add_line_format(view, LINE_DEFAULT, " %-25s %s", - key, req_info[i].help); + for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) { + buf[bufpos] = tolower(req_info[i].name[bufpos]); + if (buf[bufpos] == '_') + buf[bufpos] = '-'; + } + + add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", + key, buf, req_info[i].help); } if (run_requests) { @@ -3481,8 +3690,6 @@ help_open(struct view *view) for (i = 0; i < run_requests; i++) { struct run_request *req = get_run_request(REQ_NONE + i + 1); const char *key; - char cmd[SIZEOF_STR]; - size_t bufpos; int argc; if (!req) @@ -3493,12 +3700,12 @@ help_open(struct view *view) key = "(no key defined)"; for (bufpos = 0, argc = 0; req->argv[argc]; argc++) - if (!string_format_from(cmd, &bufpos, "%s%s", + if (!string_format_from(buf, &bufpos, "%s%s", argc ? " " : "", req->argv[argc])) return REQ_NONE; add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`", - keymap_table[req->keymap].name, key, cmd); + keymap_table[req->keymap].name, key, buf); } return TRUE; @@ -3575,16 +3782,27 @@ push_tree_stack_entry(const char *name, unsigned long lineno) #define SIZEOF_TREE_ATTR \ STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t") -#define TREE_UP_FORMAT "040000 tree %s\t.." +#define SIZEOF_TREE_MODE \ + STRING_SIZE("100644 ") + +#define TREE_ID_OFFSET \ + STRING_SIZE("100644 blob ") + +struct tree_entry { + char id[SIZEOF_REV]; + mode_t mode; + struct tm time; /* Date from the author ident. */ + char author[75]; /* Author of the commit. */ + char name[1]; +}; static const char * tree_path(struct line *line) { - const char *path = line->data; - - return path + SIZEOF_TREE_ATTR; + return ((struct tree_entry *) line->data)->name; } + static int tree_compare_entry(struct line *line1, struct line *line2) { @@ -3593,30 +3811,120 @@ tree_compare_entry(struct line *line1, struct line *line2) return strcmp(tree_path(line1), tree_path(line2)); } +static struct line * +tree_entry(struct view *view, enum line_type type, const char *path, + const char *mode, const char *id) +{ + struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path)); + struct line *line = entry ? add_line_data(view, entry, type) : NULL; + + if (!entry || !line) { + free(entry); + return NULL; + } + + strncpy(entry->name, path, strlen(path)); + if (mode) + entry->mode = strtoul(mode, NULL, 8); + if (id) + string_copy_rev(entry->id, id); + + return line; +} + +static bool +tree_read_date(struct view *view, char *text, bool *read_date) +{ + static char author_name[SIZEOF_STR]; + static struct tm author_time; + + if (!text && *read_date) { + *read_date = FALSE; + return TRUE; + + } else if (!text) { + char *path = *opt_path ? opt_path : "."; + /* Find next entry to process */ + const char *log_file[] = { + "git", "log", "--no-color", "--pretty=raw", + "--cc", "--raw", view->id, "--", path, NULL + }; + struct io io = {}; + + if (!run_io_rd(&io, log_file, FORMAT_NONE)) { + report("Failed to load tree data"); + return TRUE; + } + + done_io(view->pipe); + view->io = io; + *read_date = TRUE; + return FALSE; + + } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) { + parse_author_line(text + STRING_SIZE("author "), + author_name, sizeof(author_name), &author_time); + + } else if (*text == ':') { + char *pos; + size_t annotated = 1; + size_t i; + + pos = strchr(text, '\t'); + if (!pos) + return TRUE; + text = pos + 1; + if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix))) + text += strlen(opt_prefix); + if (*opt_path && !strncmp(text, opt_path, strlen(opt_path))) + text += strlen(opt_path); + pos = strchr(text, '/'); + if (pos) + *pos = 0; + + for (i = 1; i < view->lines; i++) { + struct line *line = &view->line[i]; + struct tree_entry *entry = line->data; + + annotated += !!*entry->author; + if (*entry->author || strcmp(entry->name, text)) + continue; + + string_copy(entry->author, author_name); + memcpy(&entry->time, &author_time, sizeof(entry->time)); + line->dirty = 1; + break; + } + + if (annotated == view->lines) + kill_io(view->pipe); + } + return TRUE; +} + static bool tree_read(struct view *view, char *text) { - size_t textlen = text ? strlen(text) : 0; + static bool read_date = FALSE; + struct tree_entry *data; struct line *entry, *line; enum line_type type; + size_t textlen = text ? strlen(text) : 0; + char *path = text + SIZEOF_TREE_ATTR; + + if (read_date || !text) + return tree_read_date(view, text, &read_date); - if (!text) - return TRUE; if (textlen <= SIZEOF_TREE_ATTR) return FALSE; - - type = text[STRING_SIZE("100644 ")] == 't' - ? LINE_TREE_DIR : LINE_TREE_FILE; - if (view->lines == 0 && - !add_line_format(view, LINE_DEFAULT, "Directory path /%s", opt_path)) + !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL)) return FALSE; /* Strip the path part ... */ if (*opt_path) { size_t pathlen = textlen - SIZEOF_TREE_ATTR; size_t striplen = strlen(opt_path); - char *path = text + SIZEOF_TREE_ATTR; if (pathlen > striplen) memmove(path, path + striplen, @@ -3624,14 +3932,15 @@ tree_read(struct view *view, char *text) /* Insert "link" to parent directory. */ if (view->lines == 1 && - !add_line_format(view, LINE_TREE_DIR, TREE_UP_FORMAT, view->ref)) + !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref)) return FALSE; } - entry = add_line_text(view, text, type); + type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE; + entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET); if (!entry) return FALSE; - text = entry->data; + data = entry->data; /* Skip "Directory ..." and ".." line. */ for (line = &view->line[1 + !!*opt_path]; line < entry; line++) { @@ -3640,7 +3949,7 @@ tree_read(struct view *view, char *text) memmove(line + 1, line, (entry - line) * sizeof(*entry)); - line->data = text; + line->data = data; line->type = type; for (; line <= entry; line++) line->dirty = line->cleareol = 1; @@ -3655,6 +3964,45 @@ tree_read(struct view *view, char *text) return TRUE; } +static bool +tree_draw(struct view *view, struct line *line, unsigned int lineno) +{ + struct tree_entry *entry = line->data; + + if (line->type == LINE_TREE_PARENT) { + 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)) + return TRUE; + + if (opt_author && draw_author(view, entry->author)) + return TRUE; + + if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL)) + return TRUE; + } + if (draw_text(view, line->type, entry->name, TRUE)) + return TRUE; + return TRUE; +} + static void open_blob_editor() { @@ -3696,7 +4044,7 @@ tree_request(struct view *view, enum request request, struct line *line) } return REQ_NONE; - case REQ_TREE_PARENT: + case REQ_PARENT: if (!*opt_path) { /* quit view if at top of tree */ return REQ_VIEW_CLOSE; @@ -3741,13 +4089,12 @@ tree_request(struct view *view, enum request request, struct line *line) break; default: - return TRUE; + return REQ_NONE; } open_view(view, request, flags); - if (request == REQ_VIEW_TREE) { + if (request == REQ_VIEW_TREE) view->lineno = tree_lineno; - } return REQ_NONE; } @@ -3755,17 +4102,17 @@ tree_request(struct view *view, enum request request, struct line *line) static void tree_select(struct view *view, struct line *line) { - char *text = (char *)line->data + STRING_SIZE("100644 blob "); + struct tree_entry *entry = line->data; if (line->type == LINE_TREE_FILE) { - string_copy_rev(ref_blob, text); + string_copy_rev(ref_blob, entry->id); string_format(opt_file, "%s%s", opt_path, tree_path(line)); } else if (line->type != LINE_TREE_DIR) { return; } - string_copy_rev(view->ref, text); + string_copy_rev(view->ref, entry->id); } static const char *tree_argv[SIZEOF_ARG] = { @@ -3777,7 +4124,7 @@ static struct view_ops tree_ops = { tree_argv, NULL, tree_read, - pager_draw, + tree_draw, tree_request, pager_grep, tree_select, @@ -3847,6 +4194,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 { @@ -4011,7 +4359,7 @@ blame_read(struct view *view, char *line) if (!commit) { commit = parse_blame_commit(view, line, &blamed); string_format(view->ref, "%s %2d%%", view->vid, - blamed * 100 / view->lines); + view->lines ? blamed * 100 / view->lines : 0); } else if (match_blame_header("author ", &line)) { string_ncopy(commit->author, line, strlen(line)); @@ -4036,6 +4384,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; @@ -4060,8 +4411,7 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno) if (opt_date && draw_date(view, time)) return TRUE; - if (opt_author && - draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE)) + if (opt_author && draw_author(view, author)) return TRUE; if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE)) @@ -4100,6 +4450,12 @@ blame_request(struct view *view, enum request request, struct line *line) } break; + case REQ_PARENT: + if (check_blame_commit(blame) && + select_commit_parent(blame->commit->id, opt_ref)) + open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH); + break; + case REQ_ENTER: if (!blame->commit) { report("No commit loaded yet"); @@ -4113,11 +4469,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[6] = "--"; + diff_index_argv[7] = "/dev/null"; + } + if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) { report("Failed to allocate diff command"); break; @@ -4126,6 +4488,8 @@ blame_request(struct view *view, enum request request, struct line *line) } open_view(view, REQ_VIEW_DIFF, flags); + if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID)) + string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID); break; default: @@ -4298,6 +4662,7 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty unmerged = NULL; if (collapse) { free(file); + file = NULL; view->lines--; continue; } @@ -4358,14 +4723,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); @@ -4389,27 +4778,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; } @@ -4548,7 +4920,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; @@ -4568,17 +4940,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; @@ -5096,12 +5473,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, "/")) { @@ -5371,8 +5751,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno) if (opt_date && draw_date(view, &commit->time)) return TRUE; - if (opt_author && - draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE)) + if (opt_author && draw_author(view, commit->author)) return TRUE; if (opt_rev_graph && commit->graph_size && @@ -5478,56 +5857,13 @@ main_read(struct view *view, char *line) break; case LINE_AUTHOR: - { - /* Parse author lines where the name may be empty: - * author 1138474660 +0100 - */ - char *ident = line + STRING_SIZE("author "); - char *nameend = strchr(ident, '<'); - char *emailend = strchr(ident, '>'); - - if (!nameend || !emailend) - break; - + parse_author_line(line + STRING_SIZE("author "), + commit->author, sizeof(commit->author), + &commit->time); update_rev_graph(view, graph); graph = graph->next; - - *nameend = *emailend = 0; - ident = chomp_string(ident); - if (!*ident) { - ident = chomp_string(nameend + 1); - if (!*ident) - ident = "Unknown"; - } - - string_ncopy(commit->author, ident, strlen(ident)); - view->line[view->lines - 1].dirty = 1; - - /* Parse epoch and timezone */ - if (emailend[1] == ' ') { - char *secs = emailend + 2; - char *zone = strchr(secs, ' '); - time_t time = (time_t) atol(secs); - - if (zone && strlen(zone) == STRING_SIZE(" +0700")) { - long tz; - - zone++; - tz = ('0' - zone[1]) * 60 * 60 * 10; - tz += ('0' - zone[2]) * 60 * 60; - tz += ('0' - zone[3]) * 60; - tz += ('0' - zone[4]) * 60; - - if (zone[0] == '-') - tz = -tz; - - time -= tz; - } - - gmtime_r(&time, &commit->time); - } break; - } + default: /* Fill in the commit title if it has not already been set. */ if (commit->title[0]) @@ -5813,10 +6149,17 @@ utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool /* Whether or not the curses interface has been initialized. */ static bool cursed = FALSE; +/* Terminal hacks and workarounds. */ +static bool use_scroll_redrawwin; +static bool use_scroll_status_wclear; + /* The status window is used for polling keystrokes. */ static WINDOW *status_win; -static bool status_empty = TRUE; +/* Reading from the prompt? */ +static bool input_mode = FALSE; + +static bool status_empty = FALSE; /* Update status and title window. */ static void @@ -5848,6 +6191,8 @@ report(const char *msg, ...) va_start(args, msg); wmove(status_win, 0, 0); + if (view->has_scrolled && use_scroll_status_wclear) + wclear(status_win); if (*msg) { vwprintw(status_win, msg, args); status_empty = FALSE; @@ -5855,13 +6200,12 @@ report(const char *msg, ...) status_empty = TRUE; } wclrtoeol(status_win); - wrefresh(status_win); + wnoutrefresh(status_win); va_end(args); } update_view_title(view); - update_display_cursor(view); } /* Controls when nodelay should be in effect when polling user input. */ @@ -5878,6 +6222,7 @@ set_nonblocking_input(bool loading) static void init_display(void) { + const char *term; int x, y; /* Initialize the curses library */ @@ -5898,7 +6243,7 @@ init_display(void) nonl(); /* Tell curses not to do NL->CR/NL on output */ cbreak(); /* Take input chars one at a time, no wait for \n */ noecho(); /* Don't echo input */ - leaveok(stdscr, TRUE); + leaveok(stdscr, FALSE); if (has_colors()) init_colors(); @@ -5916,41 +6261,77 @@ init_display(void) if (opt_line_graphics) { line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE; } + + term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM"); + if (term && !strcmp(term, "gnome-terminal")) { + /* In the gnome-terminal-emulator, the message from + * scrolling up one line when impossible followed by + * scrolling down one line causes corruption of the + * status line. This is fixed by calling wclear. */ + use_scroll_status_wclear = TRUE; + use_scroll_redrawwin = FALSE; + + } else if (term && !strcmp(term, "xrvt-xpm")) { + /* No problems with full optimizations in xrvt-(unicode) + * and aterm. */ + use_scroll_status_wclear = use_scroll_redrawwin = FALSE; + + } else { + /* When scrolling in (u)xterm the last line in the + * scrolling direction will update slowly. */ + use_scroll_redrawwin = TRUE; + use_scroll_status_wclear = FALSE; + } } static int -get_input(bool prompting) +get_input(int prompt_position) { struct view *view; - int i, key; + int i, key, cursor_y, cursor_x; - if (prompting) + if (prompt_position) input_mode = TRUE; - while (true) { - foreach_view (view, i) + while (TRUE) { + foreach_view (view, i) { update_view(view); + if (view_is_displayed(view) && view->has_scrolled && + use_scroll_redrawwin) + redrawwin(view->win); + view->has_scrolled = FALSE; + } + + /* Update the cursor position. */ + if (prompt_position) { + getbegyx(status_win, cursor_y, cursor_x); + cursor_x = prompt_position; + } else { + view = display[current_view]; + getbegyx(view->win, cursor_y, cursor_x); + cursor_x = view->width - 1; + cursor_y += view->lineno - view->offset; + } + setsyx(cursor_y, cursor_x); /* Refresh, accept single keystroke of input */ + doupdate(); 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); + wnoutrefresh(status_win); + resize_display(); + redraw_display(TRUE); } else { input_mode = FALSE; @@ -5959,76 +6340,38 @@ get_input(bool prompting) } } -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); - - key = get_input(TRUE); - switch (key) { - case 'y': - case 'Y': - answer = TRUE; - status = STOP; - break; - - case KEY_ESC: - case KEY_RETURN: - case KEY_ENTER: - case KEY_BACKSPACE: - case 'n': - case 'N': - case '\n': - default: - answer = FALSE; - status = CANCEL; - } - } - - /* Clear the status window */ - status_empty = FALSE; - report(""); - - return answer; -} - static char * -read_prompt(const char *prompt) +prompt_input(const char *prompt, input_handler handler, void *data) { - enum { READING, STOP, CANCEL } status = READING; + enum input_status status = INPUT_OK; static char buf[SIZEOF_STR]; - int pos = 0; + size_t pos = 0; + + buf[pos] = 0; - while (status == READING) { + while (status == INPUT_OK || status == INPUT_SKIP) { int key; mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf); wclrtoeol(status_win); - key = get_input(TRUE); + key = get_input(pos + 1); switch (key) { case KEY_RETURN: case KEY_ENTER: case '\n': - status = pos ? STOP : CANCEL; + status = pos ? INPUT_STOP : INPUT_CANCEL; break; case KEY_BACKSPACE: if (pos > 0) - pos--; + buf[--pos] = 0; else - status = CANCEL; + status = INPUT_CANCEL; break; case KEY_ESC: - status = CANCEL; + status = INPUT_CANCEL; break; default: @@ -6037,7 +6380,8 @@ read_prompt(const char *prompt) return NULL; } - if (isprint(key)) + status = handler(data, buf, key); + if (status == INPUT_OK) buf[pos++] = (char) key; } } @@ -6046,7 +6390,7 @@ read_prompt(const char *prompt) status_empty = FALSE; report(""); - if (status == CANCEL) + if (status == INPUT_CANCEL) return NULL; buf[pos++] = 0; @@ -6054,21 +6398,43 @@ read_prompt(const char *prompt) return buf; } -/* - * Repository properties - */ +static enum input_status +prompt_yesno_handler(void *data, char *buf, int c) +{ + if (c == 'y' || c == 'Y') + return INPUT_STOP; + if (c == 'n' || c == 'N') + return INPUT_CANCEL; + return INPUT_SKIP; +} -static int -git_properties(const char **argv, const char *separators, - int (*read_property)(char *, size_t, char *, size_t)) +static bool +prompt_yesno(const char *prompt) { - struct io io = {}; + char prompt2[SIZEOF_STR]; - if (init_io_rd(&io, argv, NULL, FORMAT_NONE)) - return read_properties(&io, separators, read_property); - return ERR; + if (!string_format(prompt2, "%s [Yy/Nn]", prompt)) + return FALSE; + + return !!prompt_input(prompt2, prompt_yesno_handler, NULL); } +static enum input_status +read_prompt_handler(void *data, char *buf, int c) +{ + return isprint(c) ? INPUT_OK : INPUT_SKIP; +} + +static char * +read_prompt(const char *prompt) +{ + return prompt_input(prompt, read_prompt_handler, NULL); +} + +/* + * Repository properties + */ + static struct ref *refs = NULL; static size_t refs_alloc = 0; static size_t refs_size = 0; @@ -6243,7 +6609,7 @@ load_refs(void) 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 @@ -6285,7 +6651,7 @@ load_git_config(void) { 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 @@ -6301,8 +6667,12 @@ read_repo_info(char *name, size_t namelen, char *value, size_t valuelen) * the option else either "true" or "false" is printed. * Default to true for the unknown case. */ opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE; - } else { + + } else if (*name == '.') { string_ncopy(opt_cdup, name, namelen); + + } else { + string_ncopy(opt_prefix, name, namelen); } return OK; @@ -6316,7 +6686,7 @@ load_repo_info(void) }; const char *rev_parse_argv[] = { "git", "rev-parse", "--git-dir", "--is-inside-work-tree", - "--show-cdup", NULL + "--show-cdup", "--show-prefix", NULL }; if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) { @@ -6328,45 +6698,7 @@ load_repo_info(void) } } - 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); } @@ -6471,7 +6803,7 @@ main(int argc, const char *argv[]) } while (view_driver(display[current_view], request)) { - int key = get_input(FALSE); + int key = get_input(0); view = display[current_view]; request = get_keybinding(view->keymap, key);