index 1f7d866ac917a770dff577530fc3518d68f001d1..15a42a436bf83e71cc6c0d4e41897fb7e24a05a5 100644 (file)
--- a/tig.c
+++ b/tig.c
/* ncurses(3): Must be defined to have extended wide-character functions. */
#define _XOPEN_SOURCE_EXTENDED
-#include <curses.h>
+#ifdef HAVE_NCURSESW_NCURSES_H
+#include <ncursesw/ncurses.h>
+#else
+#ifdef HAVE_NCURSES_NCURSES_H
+#include <ncurses/ncurses.h>
+#else
+#include <ncurses.h>
+#endif
+#endif
#if __GNUC__ >= 3
#define __NORETURN __attribute__((__noreturn__))
static void report(const char *msg, ...);
static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
static void set_nonblocking_input(bool loading);
-static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
+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);
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define REVGRAPH_BRANCH '+'
#define REVGRAPH_COMMIT '*'
#define REVGRAPH_BOUND '^'
-#define REVGRAPH_LINE '|'
#define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
/* The default interval between line numbers. */
#define NUMBER_INTERVAL 5
-#define TABSIZE 8
+#define TAB_SIZE 8
#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
@@ -110,7 +118,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bo
#endif
#define TIG_LS_REMOTE \
- "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
+ "git ls-remote . 2>/dev/null"
#define TIG_DIFF_CMD \
"git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
@@ -118,8 +126,11 @@ static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bo
#define TIG_LOG_CMD \
"git log --no-color --cc --stat -n100 %s 2>/dev/null"
+#define TIG_MAIN_BASE \
+ "git log --no-color --pretty=raw --parents --topo-order"
+
#define TIG_MAIN_CMD \
- "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
+ TIG_MAIN_BASE " %s 2>/dev/null"
#define TIG_TREE_CMD \
"git ls-tree %s %s"
REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
REQ_(STATUS_UPDATE, "Update file status"), \
+ REQ_(STATUS_CHECKOUT, "Checkout file"), \
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_(EDIT, "Open in editor"), \
REQ_(NONE, "Do nothing")
static bool opt_date = TRUE;
static bool opt_author = TRUE;
static bool opt_line_number = FALSE;
+static bool opt_line_graphics = TRUE;
static bool opt_rev_graph = FALSE;
static bool opt_show_refs = TRUE;
static int opt_num_interval = NUMBER_INTERVAL;
-static int opt_tab_size = TABSIZE;
-static enum request opt_request = REQ_VIEW_MAIN;
+static int opt_tab_size = TAB_SIZE;
+static int opt_author_cols = AUTHOR_COLS-1;
static char opt_cmd[SIZEOF_STR] = "";
static char opt_path[SIZEOF_STR] = "";
static char opt_file[SIZEOF_STR] = "";
static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
static char opt_editor[SIZEOF_STR] = "";
-static bool
+static enum request
parse_options(int argc, char *argv[])
{
+ enum request request = REQ_VIEW_MAIN;
size_t buf_size;
char *subcommand;
bool seen_dashdash = FALSE;
int i;
if (!isatty(STDIN_FILENO)) {
- opt_request = REQ_VIEW_PAGER;
opt_pipe = stdin;
- return TRUE;
+ return REQ_VIEW_PAGER;
}
if (argc <= 1)
- return TRUE;
+ return REQ_VIEW_MAIN;
subcommand = argv[1];
if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
- opt_request = REQ_VIEW_STATUS;
if (!strcmp(subcommand, "-S"))
warn("`-S' has been deprecated; use `tig status' instead");
if (argc > 2)
warn("ignoring arguments after `%s'", subcommand);
- return TRUE;
+ return REQ_VIEW_STATUS;
} else if (!strcmp(subcommand, "blame")) {
- opt_request = REQ_VIEW_BLAME;
if (argc <= 2 || argc > 4)
die("invalid number of options to blame\n\n%s", usage);
}
string_ncopy(opt_file, argv[i], strlen(argv[i]));
- return TRUE;
+ return REQ_VIEW_BLAME;
} else if (!strcmp(subcommand, "show")) {
- opt_request = REQ_VIEW_DIFF;
+ request = REQ_VIEW_DIFF;
} else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
- opt_request = subcommand[0] == 'l'
- ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
+ request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
warn("`tig %s' has been deprecated", subcommand);
} else {
if (!subcommand)
/* XXX: This is vulnerable to the user overriding
* options required for the main view parser. */
- string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
+ string_copy(opt_cmd, TIG_MAIN_BASE);
else
string_format(opt_cmd, "git %s", subcommand);
} else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
printf("tig version %s\n", TIG_VERSION);
- return FALSE;
+ return REQ_NONE;
} else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
printf("%s\n", usage);
- return FALSE;
+ return REQ_NONE;
}
opt_cmd[buf_size++] = ' ';
opt_cmd[buf_size] = 0;
- return TRUE;
+ return request;
}
LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
-LINE(BLAME_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
-LINE(BLAME_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
enum line_type {
#define LINE(type, line, fg, bg, attr) \
LINE_##type
- LINE_INFO
+ LINE_INFO,
+ LINE_NONE
#undef LINE
};
{ KEY_UP, REQ_PREVIOUS },
{ KEY_DOWN, REQ_NEXT },
{ 'R', REQ_REFRESH },
- { 'M', REQ_MAXIMIZE },
+ { KEY_F(5), REQ_REFRESH },
+ { 'O', REQ_MAXIMIZE },
/* Cursor navigation */
{ 'k', REQ_MOVE_UP },
{ 'F', REQ_TOGGLE_REFS },
{ ':', REQ_PROMPT },
{ 'u', REQ_STATUS_UPDATE },
+ { '!', REQ_STATUS_CHECKOUT },
{ 'M', REQ_STATUS_MERGE },
+ { '@', REQ_STAGE_NEXT },
{ ',', REQ_TREE_PARENT },
{ 'e', REQ_EDIT },
static enum request
add_run_request(enum keymap keymap, int key, int argc, char **argv)
{
- struct run_request *tmp;
- struct run_request req = { keymap, key };
+ struct run_request *req;
+ char cmd[SIZEOF_STR];
size_t bufpos;
for (bufpos = 0; argc > 0; argc--, argv++)
- if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
+ if (!string_format_from(cmd, &bufpos, "%s ", *argv))
return REQ_NONE;
- req.cmd[bufpos - 1] = 0;
-
- tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
- if (!tmp)
+ req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
+ if (!req)
return REQ_NONE;
- run_request = tmp;
- run_request[run_requests++] = req;
+ run_request = req;
+ req = &run_request[run_requests++];
+ string_copy(req->cmd, cmd);
+ req->keymap = keymap;
+ req->key = key;
return REQ_NONE + run_requests;
}
!strcmp(s, "yes")) ? TRUE : FALSE;
}
+static int
+parse_int(const char *s, int default_value, int min, int max)
+{
+ int value = atoi(s);
+
+ return (value < min || value > max) ? default_value : value;
+}
+
/* Wants: name = value */
static int
option_set_command(int argc, char *argv[])
return OK;
}
+ if (!strcmp(argv[0], "line-graphics")) {
+ opt_line_graphics = parse_bool(argv[2]);
+ return OK;
+ }
+
if (!strcmp(argv[0], "line-number-interval")) {
- opt_num_interval = atoi(argv[2]);
+ opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
+ return OK;
+ }
+
+ if (!strcmp(argv[0], "author-width")) {
+ opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
return OK;
}
if (!strcmp(argv[0], "tab-size")) {
- opt_tab_size = atoi(argv[2]);
+ opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
return OK;
}
size_t line_size; /* Total number of used lines */
unsigned int digits; /* Number of digits in the lines member. */
+ /* Drawing */
+ struct line *curline; /* Line currently being drawn. */
+ enum line_type curtype; /* Attribute currently used for drawing. */
+ unsigned long col; /* Column when drawing. */
+
/* Loading */
FILE *pipe;
time_t start_time;
/* Read one line; updates view->line. */
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);
+ 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. */
#define view_is_displayed(view) \
(view == display[0] || view == display[1])
+
+enum line_graphic {
+ LINE_GRAPHIC_VLINE
+};
+
+static int line_graphics[] = {
+ /* LINE_GRAPHIC_VLINE: */ '|'
+};
+
+static inline void
+set_view_attr(struct view *view, enum line_type type)
+{
+ if (!view->curline->selected && view->curtype != type) {
+ wattrset(view->win, get_line_attr(type));
+ wchgat(view->win, -1, 0, type, NULL);
+ view->curtype = type;
+ }
+}
+
static int
-draw_text(struct view *view, const char *string, int max_len,
- bool use_tilde, bool selected)
+draw_chars(struct view *view, enum line_type type, const char *string,
+ int max_len, bool use_tilde)
{
int len = 0;
+ int col = 0;
int trimmed = FALSE;
if (max_len <= 0)
return 0;
if (opt_utf8) {
- len = utf8_length(string, max_len, &trimmed, use_tilde);
+ len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
} else {
- len = strlen(string);
+ col = len = strlen(string);
if (len > max_len) {
if (use_tilde) {
max_len -= 1;
}
- len = max_len;
+ col = len = max_len;
trimmed = TRUE;
}
}
+ set_view_attr(view, type);
waddnstr(view->win, string, len);
if (trimmed && use_tilde) {
- if (!selected)
- wattrset(view->win, get_line_attr(LINE_DELIMITER));
+ set_view_attr(view, LINE_DELIMITER);
waddch(view->win, '~');
- len++;
+ col++;
}
- return len;
+ return col;
}
static int
-draw_lineno(struct view *view, unsigned int lineno, int max, bool selected)
+draw_space(struct view *view, enum line_type type, int max, int spaces)
{
- static char fmt[] = "%1ld";
- char number[10] = " ";
+ static char space[] = " ";
+ int col = 0;
+
+ spaces = MIN(max, spaces);
+
+ while (spaces > 0) {
+ int len = MIN(spaces, sizeof(space) - 1);
+
+ col += draw_chars(view, type, space, spaces, FALSE);
+ spaces -= len;
+ }
+
+ return col;
+}
+
+static bool
+draw_lineno(struct view *view, unsigned int lineno)
+{
+ char number[10];
int digits3 = view->digits < 3 ? 3 : view->digits;
int max_number = MIN(digits3, STRING_SIZE(number));
- bool showtrimmed = FALSE;
+ int max = view->width - view->col;
int col;
+ if (max < max_number)
+ max_number = max;
+
lineno += view->offset + 1;
if (lineno == 1 || (lineno % opt_num_interval) == 0) {
+ static char fmt[] = "%1ld";
+
if (view->digits <= 9)
fmt[1] = '0' + digits3;
if (!string_format(number, fmt, lineno))
number[0] = 0;
- showtrimmed = TRUE;
+ col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
+ } else {
+ col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
}
- if (max < max_number)
- max_number = max;
-
- if (!selected)
- wattrset(view->win, get_line_attr(LINE_LINE_NUMBER));
- col = draw_text(view, number, max_number, showtrimmed, selected);
if (col < max) {
- if (!selected)
- wattrset(view->win, A_NORMAL);
- waddch(view->win, ACS_VLINE);
+ set_view_attr(view, LINE_DEFAULT);
+ waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
col++;
}
- if (col < max) {
+
+ if (col < max)
+ col += draw_space(view, LINE_DEFAULT, max - col, 1);
+ view->col += col;
+
+ return view->width - view->col <= 0;
+}
+
+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);
+ return view->width - view->col <= 0;
+}
+
+static bool
+draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
+{
+ int max = view->width - view->col;
+ int i;
+
+ if (max < size)
+ size = max;
+
+ 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++)
+ waddch(view->win, graphic[i]);
+
+ view->col += size;
+ if (size < max) {
waddch(view->win, ' ');
- col++;
+ view->col++;
}
- return col;
+ return view->width - view->col <= 0;
}
-static int
-draw_date(struct view *view, struct tm *time, int max, bool selected)
+static bool
+draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
{
- char buf[DATE_COLS];
+ int max = MIN(view->width - view->col, len);
int col;
+
+ if (text)
+ col = draw_chars(view, type, text, max - 1, trim);
+ 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;
+}
+
+static bool
+draw_date(struct view *view, struct tm *time)
+{
+ char buf[DATE_COLS];
+ char *date;
int timelen = 0;
- if (max > DATE_COLS)
- max = DATE_COLS;
if (time)
timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
- if (!timelen) {
- memset(buf, ' ', sizeof(buf) - 1);
- buf[sizeof(buf) - 1] = 0;
- }
+ date = timelen ? buf : NULL;
- if (!selected)
- wattrset(view->win, get_line_attr(LINE_DATE));
- col = draw_text(view, buf, max, FALSE, selected);
- if (col < max) {
- if (!selected)
- wattrset(view->win, get_line_attr(LINE_DEFAULT));
- waddch(view->win, ' ');
- col++;
- }
-
- return col;
+ return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
}
static bool
line = &view->line[view->offset + lineno];
wmove(view->win, lineno, 0);
+ view->col = 0;
+ view->curline = line;
+ view->curtype = LINE_NONE;
+ line->selected = FALSE;
if (selected) {
+ set_view_attr(view, LINE_CURSOR);
line->selected = TRUE;
view->ops->select(view, line);
- wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
- wattrset(view->win, get_line_attr(LINE_CURSOR));
} else if (line->selected) {
- line->selected = FALSE;
wclrtoeol(view->win);
}
scrollok(view->win, FALSE);
- draw_ok = view->ops->draw(view, line, lineno, selected);
+ draw_ok = view->ops->draw(view, line, lineno);
scrollok(view->win, TRUE);
return draw_ok;
*/
static void
-end_update(struct view *view)
+end_update(struct view *view, bool force)
{
if (!view->pipe)
return;
+ while (!view->ops->read(view, NULL))
+ if (!force)
+ return;
set_nonblocking_input(FALSE);
if (view->pipe == stdin)
fclose(view->pipe);
static bool
begin_update(struct view *view)
{
- if (view->pipe)
- end_update(view);
-
if (opt_cmd[0]) {
string_copy(view->cmd, opt_cmd);
opt_cmd[0] = 0;
update_view_title(view);
check_pipe:
- if (ferror(view->pipe)) {
+ if (ferror(view->pipe) && errno != 0) {
report("Failed to read: %s", strerror(errno));
- goto end;
+ end_update(view, TRUE);
} else if (feof(view->pipe)) {
report("");
- goto end;
+ end_update(view, FALSE);
}
return TRUE;
alloc_error:
report("Allocation failure");
-
-end:
- if (view->ops->read(view, NULL))
- end_update(view);
+ end_update(view, TRUE);
return FALSE;
}
OPEN_SPLIT = 1, /* Split current view. */
OPEN_BACKGROUNDED = 2, /* Backgrounded. */
OPEN_RELOAD = 4, /* Reload view even if it is the current. */
- OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
+ OPEN_NOMAXIMIZE = 8 /* Do not maximize the current view. */
};
static void
(nviews == 1 && base_view != display[0]))
resize_display();
+ if (view->pipe)
+ end_update(view, TRUE);
+
if (view->ops->open) {
if (!view->ops->open(view)) {
report("Failed to load %s view", view->name);
update_view_title(view);
}
+static void
+run_confirm(const char *cmd, const char *prompt)
+{
+ if (prompt_yesno(prompt)) {
+ system(cmd);
+ }
+}
+
static void
open_external_viewer(const char *cmd)
{
open_run_request(request);
/* FIXME: When all views can refresh always do this. */
if (view == VIEW(REQ_VIEW_STATUS) ||
+ view == VIEW(REQ_VIEW_MAIN) ||
view == VIEW(REQ_VIEW_STAGE))
request = REQ_REFRESH;
else
redraw_display();
break;
- case REQ_PROMPT:
- /* Always reload^Wrerun commands from the prompt. */
- open_view(view, opt_request, OPEN_RELOAD);
- break;
-
case REQ_SEARCH:
case REQ_SEARCH_BACK:
search_view(view, request);
view = &views[i];
if (view->pipe)
report("Stopped loading the %s view", view->name),
- end_update(view);
+ end_update(view, TRUE);
}
break;
report("Nothing to edit");
break;
-
case REQ_ENTER:
report("Nothing to enter");
break;
-
case REQ_VIEW_CLOSE:
/* XXX: Mark closed views by letting view->parent point to the
* view itself. Parents to closed view should never be
*/
static bool
-pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
+pager_draw(struct view *view, struct line *line, unsigned int lineno)
{
- static char spaces[] = " ";
char *text = line->data;
- int col = 0;
-
- if (opt_line_number) {
- col += draw_lineno(view, lineno, view->width, selected);
- if (col >= view->width)
- return TRUE;
- }
-
- if (!selected)
- wattrset(view->win, get_line_attr(line->type));
-
- if (opt_tab_size < TABSIZE) {
- int col_offset = col;
-
- col = 0;
- while (text && col_offset + col < view->width) {
- int cols_max = view->width - col_offset - col;
- char *pos = text;
- int cols;
-
- if (*text == '\t') {
- text++;
- assert(sizeof(spaces) > TABSIZE);
- pos = spaces;
- cols = opt_tab_size - (col % opt_tab_size);
-
- } else {
- text = strchr(text, '\t');
- cols = line ? text - pos : strlen(pos);
- }
- waddnstr(view->win, pos, MIN(cols, cols_max));
- col += cols;
- }
-
- } else {
- draw_text(view, text, view->width - col, TRUE, selected);
- }
+ if (opt_line_number && draw_lineno(view, lineno))
+ return TRUE;
+ draw_text(view, line->type, text, TRUE);
return TRUE;
}
size_t linelen = strlen(line);
struct blame *blame = malloc(sizeof(*blame) + linelen);
- if (!line)
- return FALSE;
-
blame->commit = NULL;
strncpy(blame->text, line, linelen);
blame->text[linelen] = 0;
- return add_line_data(view, blame, LINE_BLAME_COMMIT) != NULL;
+ return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
}
}
}
static bool
-blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
+blame_draw(struct view *view, struct line *line, unsigned int lineno)
{
struct blame *blame = line->data;
- int col = 0;
-
- if (opt_date) {
- struct tm *time = blame->commit && *blame->commit->filename
- ? &blame->commit->time : NULL;
+ struct tm *time = NULL;
+ char *id = NULL, *author = NULL;
- col += draw_date(view, time, view->width, selected);
- if (col >= view->width)
- return TRUE;
+ if (blame->commit && *blame->commit->filename) {
+ id = blame->commit->id;
+ author = blame->commit->author;
+ time = &blame->commit->time;
}
- if (opt_author) {
- int max = MIN(AUTHOR_COLS - 1, view->width - col);
+ if (opt_date && draw_date(view, time))
+ return TRUE;
- if (!selected)
- wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
- if (blame->commit)
- draw_text(view, blame->commit->author, max, TRUE, selected);
- col += AUTHOR_COLS;
- if (col >= view->width)
- return TRUE;
- wmove(view->win, lineno, col);
- }
+ if (opt_author &&
+ draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
+ return TRUE;
- {
- int max = MIN(ID_COLS - 1, view->width - col);
-
- if (!selected)
- wattrset(view->win, get_line_attr(LINE_BLAME_ID));
- if (blame->commit)
- draw_text(view, blame->commit->id, max, FALSE, -1);
- col += ID_COLS;
- if (col >= view->width)
- return TRUE;
- wmove(view->win, lineno, col);
- }
+ if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
+ return TRUE;
- col += draw_lineno(view, lineno, view->width - col, selected);
- if (col >= view->width)
+ if (draw_lineno(view, lineno))
return TRUE;
- col += draw_text(view, blame->text, view->width - col, TRUE, selected);
+ draw_text(view, LINE_DEFAULT, blame->text, TRUE);
return TRUE;
}
struct blame_commit *commit = blame->commit;
regmatch_t pmatch;
-#define MATCH(text) \
- (*text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
+#define MATCH(text, on) \
+ (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
if (commit) {
char buf[DATE_COLS + 1];
- if (MATCH(commit->title) ||
- MATCH(commit->author) ||
- MATCH(commit->id))
+ if (MATCH(commit->title, 1) ||
+ MATCH(commit->author, opt_author) ||
+ MATCH(commit->id, opt_date))
return TRUE;
if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
- MATCH(buf))
+ MATCH(buf, 1))
return TRUE;
}
- return MATCH(blame->text);
+ return MATCH(blame->text, 1);
#undef MATCH
}
static char status_onbranch[SIZEOF_STR];
static struct status stage_status;
static enum line_type stage_line_type;
+static size_t stage_chunks;
+static int *stage_chunk;
/* Get fields from the diff line:
* :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
#define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
#define STATUS_DIFF_FILES_CMD "git diff-files -z"
#define STATUS_LIST_OTHER_CMD \
- "git ls-files -z --others --exclude-per-directory=.gitignore"
+ "git ls-files -z --others --exclude-standard"
#define STATUS_LIST_NO_HEAD_CMD \
- "git ls-files -z --cached --exclude-per-directory=.gitignore"
+ "git ls-files -z --cached --exclude-standard"
#define STATUS_DIFF_INDEX_SHOW_CMD \
"git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
static bool
status_open(struct view *view)
{
- struct stat statbuf;
- char exclude[SIZEOF_STR];
- char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
- char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
unsigned long prev_lineno = view->lineno;
- char indexstatus = 0;
size_t i;
for (i = 0; i < view->lines; i++)
else if (!string_format(status_onbranch, "On branch %s", opt_head))
return FALSE;
- if (opt_no_head) {
- string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
- indexstatus = 'A';
- }
+ system("git update-index -q --refresh >/dev/null 2>/dev/null");
- if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
+ if (opt_no_head &&
+ !status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
+ return FALSE;
+ else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED))
return FALSE;
- if (stat(exclude, &statbuf) >= 0) {
- size_t cmdsize = strlen(othercmd);
-
- if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
- sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
- return FALSE;
-
- cmdsize = strlen(indexcmd);
- if (opt_no_head &&
- (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
- sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
- return FALSE;
- }
-
- system("git update-index -q --refresh 2>/dev/null");
-
- if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
- !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
- !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
+ if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
+ !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
return FALSE;
/* If all went well restore the previous line number to stay in
}
static bool
-status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
+status_draw(struct view *view, struct line *line, unsigned int lineno)
{
struct status *status = line->data;
+ enum line_type type;
char *text;
- int col = 0;
-
- if (selected) {
- /* No attributes. */
-
- } else if (line->type == LINE_STAT_HEAD) {
- wattrset(view->win, get_line_attr(LINE_STAT_HEAD));
- wchgat(view->win, -1, 0, LINE_STAT_HEAD, NULL);
-
- } else if (!status && line->type != LINE_STAT_NONE) {
- wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
- wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
-
- } else {
- wattrset(view->win, get_line_attr(line->type));
- }
if (!status) {
switch (line->type) {
case LINE_STAT_STAGED:
+ type = LINE_STAT_SECTION;
text = "Changes to be committed:";
break;
case LINE_STAT_UNSTAGED:
+ type = LINE_STAT_SECTION;
text = "Changed but not updated:";
break;
case LINE_STAT_UNTRACKED:
+ type = LINE_STAT_SECTION;
text = "Untracked files:";
break;
case LINE_STAT_NONE:
+ type = LINE_DEFAULT;
text = " (no files)";
break;
case LINE_STAT_HEAD:
+ type = LINE_STAT_HEAD;
text = status_onbranch;
break;
@@ -4058,15 +4080,16 @@ status_draw(struct view *view, struct line *line, unsigned int lineno, bool sele
return FALSE;
}
} else {
- char buf[] = { status->status, ' ', ' ', ' ', 0 };
+ static char buf[] = { '?', ' ', ' ', ' ', 0 };
- col += draw_text(view, buf, view->width, TRUE, selected);
- if (!selected)
- wattrset(view->win, A_NORMAL);
+ buf[0] = status->status;
+ if (draw_text(view, line->type, buf, TRUE))
+ return TRUE;
+ type = LINE_DEFAULT;
text = status->new.name;
}
- draw_text(view, text, view->width - col, TRUE, selected);
+ draw_text(view, type, text, TRUE);
return TRUE;
}
}
stage_line_type = line->type;
+ stage_chunks = 0;
string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
}
return FALSE;
}
- if (!status_update_files(view, line + 1))
+ if (!status_update_files(view, line + 1)) {
report("Failed to update file status");
+ return FALSE;
+ }
} else if (!status_update_file(line->data, line->type)) {
report("Failed to update file status");
+ return FALSE;
}
return TRUE;
}
+static bool
+status_checkout(struct view *view)
+{
+ struct line *line = &view->line[view->lineno];
+
+ assert(view->lines);
+
+ if (!line->data || line->type != LINE_STAT_UNSTAGED) {
+ /* This should work even for the "On branch" line. */
+ if (line < view->line + view->lines && !line[1].data) {
+ report("Nothing to checkout");
+ } else if (line->type == LINE_STAT_UNTRACKED) {
+ report("Cannot checkout untracked files");
+ } else if (line->type == LINE_STAT_STAGED) {
+ report("Cannot checkout staged files");
+ } else {
+ report("Cannot checkout multiple files");
+ }
+ return FALSE;
+
+ } else {
+ struct status *status = line->data;
+ char cmd[SIZEOF_STR];
+ char file_sq[SIZEOF_STR];
+
+ if (sq_quote(file_sq, 0, status->old.name) < sizeof(file_sq) &&
+ string_format(cmd, "git checkout %s%s", opt_cdup, file_sq)) {
+ run_confirm(cmd, "Are you sure you want to overwrite any changes?");
+ }
+
+ return TRUE;
+ }
+}
+
static enum request
status_request(struct view *view, enum request request, struct line *line)
{
return REQ_NONE;
break;
+ case REQ_STATUS_CHECKOUT:
+ if (!status_checkout(view))
+ return REQ_NONE;
+ break;
+
case REQ_STATUS_MERGE:
if (!status || status->status != 'U') {
report("Merging only possible for files with unmerged status ('U').");
return FALSE;
}
+ } else if (!stage_status.status) {
+ view = VIEW(REQ_VIEW_STATUS);
+
+ for (line = view->line; line < view->line + view->lines; line++)
+ if (line->type == stage_line_type)
+ break;
+
+ if (!status_update_files(view, line + 1)) {
+ report("Failed to update files");
+ return FALSE;
+ }
+
} else if (!status_update_file(&stage_status, stage_line_type)) {
report("Failed to update file");
return FALSE;
return TRUE;
}
+static void
+stage_next(struct view *view, struct line *line)
+{
+ int i;
+
+ if (!stage_chunks) {
+ static size_t alloc = 0;
+ int *tmp;
+
+ for (line = view->line; line < view->line + view->lines; line++) {
+ if (line->type != LINE_DIFF_CHUNK)
+ continue;
+
+ tmp = realloc_items(stage_chunk, &alloc,
+ stage_chunks, sizeof(*tmp));
+ if (!tmp) {
+ report("Allocation failure");
+ return;
+ }
+
+ stage_chunk = tmp;
+ stage_chunk[stage_chunks++] = line - view->line;
+ }
+ }
+
+ for (i = 0; i < stage_chunks; i++) {
+ if (stage_chunk[i] > view->lineno) {
+ do_scroll_view(view, stage_chunk[i] - view->lineno);
+ report("Chunk %d of %d", i + 1, stage_chunks);
+ return;
+ }
+ }
+
+ report("No next chunk found");
+}
+
static enum request
stage_request(struct view *view, enum request request, struct line *line)
{
switch (request) {
case REQ_STATUS_UPDATE:
- stage_update(view, line);
+ if (!stage_update(view, line))
+ return REQ_NONE;
break;
+ case REQ_STAGE_NEXT:
+ if (stage_line_type == LINE_STAT_UNTRACKED) {
+ report("File is untracked; press %s to add",
+ get_key(REQ_STATUS_UPDATE));
+ return REQ_NONE;
+ }
+ stage_next(view, line);
+ return REQ_NONE;
+
case REQ_EDIT:
if (!stage_status.new.name[0])
return request;
commit->graph[commit->graph_size++] = symbol;
}
+static void
+clear_rev_graph(struct rev_graph *graph)
+{
+ graph->boundary = 0;
+ graph->size = graph->pos = 0;
+ graph->commit = NULL;
+ memset(graph->parents, 0, sizeof(*graph->parents));
+}
+
static void
done_rev_graph(struct rev_graph *graph)
{
}
}
- graph->size = graph->pos = 0;
- graph->commit = NULL;
- memset(graph->parents, 0, sizeof(*graph->parents));
+ clear_rev_graph(graph);
}
static void
};
enum { DEFAULT, RSHARP, RDIAG, LDIAG };
static struct rev_filler fillers[] = {
- { ' ', REVGRAPH_LINE },
+ { ' ', '|' },
{ '`', '.' },
{ '\'', ' ' },
{ '/', ' ' },
struct rev_filler *filler;
size_t i;
+ if (opt_line_graphics)
+ fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
+
filler = &fillers[DEFAULT];
for (i = 0; i < graph->pos; i++) {
*/
static bool
-main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
+main_draw(struct view *view, struct line *line, unsigned int lineno)
{
struct commit *commit = line->data;
- enum line_type type;
- int col = 0;
if (!*commit->author)
return FALSE;
- if (selected) {
- type = LINE_CURSOR;
- } else {
- type = LINE_MAIN_COMMIT;
- }
-
- if (opt_date) {
- col += draw_date(view, &commit->time, view->width, selected);
- if (col >= view->width)
- return TRUE;
- }
- if (type != LINE_CURSOR)
- wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
-
- if (opt_author) {
- int max_len;
-
- max_len = view->width - col;
- if (max_len > AUTHOR_COLS - 1)
- max_len = AUTHOR_COLS - 1;
- draw_text(view, commit->author, max_len, TRUE, selected);
- col += AUTHOR_COLS;
- if (col >= view->width)
- return TRUE;
- }
-
- if (opt_rev_graph && commit->graph_size) {
- size_t graph_size = view->width - col;
- size_t i;
-
- if (type != LINE_CURSOR)
- wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
- wmove(view->win, lineno, col);
- if (graph_size > commit->graph_size)
- graph_size = commit->graph_size;
- /* Using waddch() instead of waddnstr() ensures that
- * they'll be rendered correctly for the cursor line. */
- for (i = 0; i < graph_size; i++)
- waddch(view->win, commit->graph[i]);
+ if (opt_date && draw_date(view, &commit->time))
+ return TRUE;
- col += commit->graph_size + 1;
- if (col >= view->width)
- return TRUE;
- waddch(view->win, ' ');
- }
- if (type != LINE_CURSOR)
- wattrset(view->win, A_NORMAL);
+ if (opt_author &&
+ draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
+ return TRUE;
- wmove(view->win, lineno, col);
+ if (opt_rev_graph && commit->graph_size &&
+ draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
+ return TRUE;
if (opt_show_refs && commit->refs) {
size_t i = 0;
do {
- if (type == LINE_CURSOR)
- ;
- else if (commit->refs[i]->head)
- wattrset(view->win, get_line_attr(LINE_MAIN_HEAD));
+ enum line_type type;
+
+ if (commit->refs[i]->head)
+ type = LINE_MAIN_HEAD;
else if (commit->refs[i]->ltag)
- wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
+ type = LINE_MAIN_LOCAL_TAG;
else if (commit->refs[i]->tag)
- wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
+ type = LINE_MAIN_TAG;
else if (commit->refs[i]->tracked)
- wattrset(view->win, get_line_attr(LINE_MAIN_TRACKED));
+ type = LINE_MAIN_TRACKED;
else if (commit->refs[i]->remote)
- wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
+ type = LINE_MAIN_REMOTE;
else
- wattrset(view->win, get_line_attr(LINE_MAIN_REF));
-
- col += draw_text(view, "[", view->width - col, TRUE, selected);
- col += draw_text(view, commit->refs[i]->name, view->width - col,
- TRUE, selected);
- col += draw_text(view, "]", view->width - col, TRUE, selected);
- if (type != LINE_CURSOR)
- wattrset(view->win, A_NORMAL);
- col += draw_text(view, " ", view->width - col, TRUE, selected);
- if (col >= view->width)
+ type = LINE_MAIN_REF;
+
+ if (draw_text(view, type, "[", TRUE) ||
+ draw_text(view, type, commit->refs[i]->name, TRUE) ||
+ draw_text(view, type, "]", TRUE))
+ return TRUE;
+
+ if (draw_text(view, LINE_DEFAULT, " ", TRUE))
return TRUE;
} while (commit->refs[i++]->next);
}
- if (type != LINE_CURSOR)
- wattrset(view->win, get_line_attr(type));
-
- draw_text(view, commit->title, view->width - col, TRUE, selected);
+ draw_text(view, LINE_DEFAULT, commit->title, TRUE);
return TRUE;
}
struct commit *commit;
if (!line) {
+ int i;
+
if (!view->lines && !view->parent)
die("No revisions match the given arguments.");
+ if (view->lines > 0) {
+ commit = view->line[view->lines - 1].data;
+ if (!*commit->author) {
+ view->lines--;
+ free(commit);
+ graph->commit = NULL;
+ }
+ }
update_rev_graph(graph);
+
+ for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
+ clear_rev_graph(&graph_stacks[i]);
return TRUE;
}
{
enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
- if (request == REQ_ENTER)
+ switch (request) {
+ case REQ_ENTER:
open_view(view, REQ_VIEW_DIFF, flags);
- else
+ break;
+ case REQ_REFRESH:
+ string_copy(opt_cmd, view->cmd);
+ open_view(view, REQ_VIEW_MAIN, OPEN_RELOAD);
+ break;
+ default:
return request;
+ }
return REQ_NONE;
}
+static bool
+grep_refs(struct ref **refs, regex_t *regex)
+{
+ regmatch_t pmatch;
+ size_t i = 0;
+
+ if (!refs)
+ return FALSE;
+ do {
+ if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
+ return TRUE;
+ } while (refs[i++]->next);
+
+ return FALSE;
+}
+
static bool
main_grep(struct view *view, struct line *line)
{
struct commit *commit = line->data;
- enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
+ enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
char buf[DATE_COLS + 1];
regmatch_t pmatch;
switch (state) {
case S_TITLE: text = commit->title; break;
- case S_AUTHOR: text = commit->author; break;
+ case S_AUTHOR:
+ if (!opt_author)
+ continue;
+ text = commit->author;
+ break;
case S_DATE:
+ if (!opt_date)
+ continue;
if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
continue;
text = buf;
break;
-
+ case S_REFS:
+ if (!opt_show_refs)
+ continue;
+ if (grep_refs(commit->refs, view->regex) == TRUE)
+ return TRUE;
+ continue;
default:
return FALSE;
}
*
* Returns the number of bytes to output from string to satisfy max_width. */
static size_t
-utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
+utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
{
const char *start = string;
const char *end = strchr(string, '\0');
unsigned char last_bytes = 0;
- size_t width = 0;
+ size_t last_ucwidth = 0;
+ *width = 0;
*trimmed = 0;
while (string < end) {
@@ -5245,17 +5381,20 @@ utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
break;
ucwidth = unicode_width(unicode);
- width += ucwidth;
- if (width > max_width) {
+ *width += ucwidth;
+ if (*width > max_width) {
*trimmed = 1;
- if (reserve && width - ucwidth == max_width) {
+ *width -= ucwidth;
+ if (reserve && *width == max_width) {
string -= last_bytes;
+ *width -= last_ucwidth;
}
break;
}
string += bytes;
last_bytes = bytes;
+ last_ucwidth = ucwidth;
}
return string - start;
/* Enable keyboard mapping */
keypad(status_win, TRUE);
wbkgdset(status_win, get_line_attr(LINE_STATUS));
+
+ TABSIZE = opt_tab_size;
+ if (opt_line_graphics) {
+ line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
+ }
+}
+
+static bool
+prompt_yesno(const char *prompt)
+{
+ enum { WAIT, STOP, CANCEL } status = WAIT;
+ bool answer = FALSE;
+
+ while (status == WAIT) {
+ struct view *view;
+ int i, key;
+
+ input_mode = TRUE;
+
+ foreach_view (view, i)
+ update_view(view);
+
+ input_mode = FALSE;
+
+ mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
+ wclrtoeol(status_win);
+
+ /* Refresh, accept single keystroke of input */
+ key = wgetch(status_win);
+ switch (key) {
+ case ERR:
+ break;
+
+ 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 *
if (load_git_config() == ERR)
die("Failed to load repo config.");
- if (!parse_options(argc, argv))
+ request = parse_options(argc, argv);
+ if (request == REQ_NONE)
return 0;
/* Require a git repository unless when running in pager mode. */
- if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
+ if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
die("Not a git repository");
if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
view->cmd_env = getenv(view->cmd_env);
- request = opt_request;
-
init_display();
while (view_driver(display[current_view], request)) {
if (cmd && string_format(opt_cmd, "git %s", cmd)) {
if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
- opt_request = REQ_VIEW_DIFF;
+ request = REQ_VIEW_DIFF;
} else {
- opt_request = REQ_VIEW_PAGER;
+ request = REQ_VIEW_PAGER;
}
- break;
+
+ /* Always reload^Wrerun commands from the prompt. */
+ open_view(view, request, OPEN_RELOAD);
}
request = REQ_NONE;