static void die(const char *err, ...);
static void report(const char *msg, ...);
static void set_nonblocking_input(bool loading);
+static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
/* This color name can be used to refer to the default term colors. */
#define COLOR_DEFAULT (-1)
-#define TIG_HELP "(d)iff, (l)og, (m)ain, (q)uit, (h)elp, (Enter) show diff"
+#define TIG_HELP "(d)iff, (l)og, (m)ain, (q)uit, (h)elp"
/* The format and size of the date column in the main view. */
#define DATE_FORMAT "%Y-%m-%d %H:%M"
#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
+#define AUTHOR_COLS 20
+
/* The default interval between line numbers. */
#define NUMBER_INTERVAL 1
REQ_STOP_LOADING,
REQ_TOGGLE_LINE_NUMBERS,
REQ_VIEW_NEXT,
+ REQ_VIEW_CLOSE,
REQ_MOVE_UP,
REQ_MOVE_UP_ENTER,
*/
static inline void
-string_ncopy(char *dst, char *src, int dstlen)
+string_ncopy(char *dst, const char *src, int dstlen)
{
strncpy(dst, src, dstlen - 1);
dst[dstlen - 1] = 0;
{
char c;
-#define BUFPUT(x) ( (bufsize < SIZEOF_CMD) && (buf[bufsize++] = (x)) )
+#define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
BUFPUT('\'');
while ((c = *src++)) {
* -------
**/
+static const char usage[] =
+VERSION " (" __DATE__ ")\n"
+"\n"
+"Usage: tig [options]\n"
+" or: tig [options] [--] [git log options]\n"
+" or: tig [options] log [git log options]\n"
+" or: tig [options] diff [git diff options]\n"
+" or: tig [options] show [git show options]\n"
+" or: tig [options] < [git command output]\n"
+"\n"
+"Options:\n"
+" -l Start up in log view\n"
+" -d Start up in diff view\n"
+" -n[I], --line-number[=I] Show line numbers with given interval\n"
+" -t[N], --tab-size[=N] Set number of spaces for tab expansion\n"
+" -- Mark end of tig options\n"
+" -v, --version Show version and exit\n"
+" -h, --help Show help message and exit\n";
+
/* Option and state variables. */
static bool opt_line_number = FALSE;
static int opt_num_interval = NUMBER_INTERVAL;
}
/**
+ * -h, --help::
+ * Show help message and exit.
+ **/
+ if (!strcmp(opt, "-h") ||
+ !strcmp(opt, "--help")) {
+ printf(usage);
+ return FALSE;
+ }
+
+ /**
* \--::
* End of tig(1) options. Useful when specifying command
* options for the main view. Example:
};
struct line_info {
- char *line; /* The start of line to match. */
+ const char *line; /* The start of line to match. */
int linelen; /* Size of string to match. */
int fg, bg, attr; /* Color and text attributes for the lines. */
};
#define TIG_PAGER_CMD \
""
+
/**
* The viewer
* ----------
+ * The display consists of a status window on the last line of the screen and
+ * one or more views. The default is to only show one view at the time but it
+ * is possible to split both the main and log view to also show the commit
+ * diff.
*
- * tig(1) presents various 'views' of a repository. Each view is based on output
- * from an external command, most often 'git log', 'git diff', or 'git show'.
+ * If you are in the log view and press 'Enter' when the current line is a
+ * commit line, such as:
*
- * The main view::
- * Is the default view, and it shows a one line summary of each commit
- * in the chosen list of revision. The summary includes commit date,
- * author, and the first line of the log message. Additionally, any
- * repository references, such as tags, will be shown.
+ * commit 4d55caff4cc89335192f3e566004b4ceef572521
*
- * The log view::
- * Presents a more rich view of the revision log showing the whole log
- * message and the diffstat.
- *
- * The diff view::
- * Shows either the diff of the current working tree, that is, what
- * has changed since the last commit, or the commit diff complete
- * with log message, diffstat and diff.
- *
- * The pager view::
- * Is used for displaying both input from stdin and output from git
- * commands entered in the internal prompt.
+ * You will split the view so that the log view is displayed in the top window
+ * and the diff view in the bottom window. You can switch between the two
+ * views by pressing 'Tab'. To maximize the log view again, simply press 'l'.
+ **/
+
+struct view;
+
+/* The display array of active views and the index of the current view. */
+static struct view *display[2];
+static unsigned int current_view;
+
+#define foreach_view(view, i) \
+ for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
+
+
+/**
+ * Current head and commit ID
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * The viewer keeps track of both what head and commit ID you are currently
+ * viewing. The commit ID will follow the cursor line and change everytime time
+ * you highlight a different commit. Whenever you reopen the diff view it
+ * will be reloaded, if the commit ID changed.
*
- * The help view::
- * Displays the information from the tig(1) man page. For the help view
- * to work you need to have the tig(1) man page installed.
+ * The head ID is used when opening the main and log view to indicate from
+ * what revision to show history.
**/
+static char ref_commit[SIZEOF_REF] = "HEAD";
+static char ref_head[SIZEOF_REF] = "HEAD";
+
+
struct view {
const char *name; /* View name */
- char *cmd_fmt; /* Default command line format */
- char *cmd_env; /* Command line set via environment */
- char *id; /* Points to either of ref_{head,commit} */
- size_t objsize; /* Size of objects in the line index */
+ const char *cmd_fmt; /* Default command line format */
+ const char *cmd_env; /* Command line set via environment */
+ const char *id; /* Points to either of ref_{head,commit} */
struct view_ops {
/* What type of content being displayed. Used in the
* title bar. */
- char *type;
+ const char *type;
/* Draw one line; @lineno must be < view->height. */
bool (*draw)(struct view *view, unsigned int lineno);
/* Read one line; updates view->line. */
unsigned long offset; /* Offset of the window top */
unsigned long lineno; /* Current line number */
+ /* If non-NULL, points to the view that opened this view. If this view
+ * is closed tig will switch back to the parent view. */
+ struct view *parent;
+
/* Buffering */
unsigned long lines; /* Total number of lines */
void **line; /* Line index; each line contains user data */
static struct view_ops pager_ops;
static struct view_ops main_ops;
-static char ref_head[SIZEOF_REF] = "HEAD";
-static char ref_commit[SIZEOF_REF] = "HEAD";
+#define VIEW_STR(name, cmd, env, ref, ops) \
+ { name, cmd, #env, ref, ops }
-#define VIEW_STR(name, cmd, env, ref, objsize, ops) \
- { name, cmd, #env, ref, objsize, ops }
+#define VIEW_(id, name, ops, ref) \
+ VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops)
-#define VIEW_(id, name, ops, ref, objsize) \
- VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, objsize, ops)
+/**
+ * Views
+ * ~~~~~
+ * tig(1) presents various 'views' of a repository. Each view is based on output
+ * from an external command, most often 'git log', 'git diff', or 'git show'.
+ *
+ * The main view::
+ * Is the default view, and it shows a one line summary of each commit
+ * in the chosen list of revisions. The summary includes commit date,
+ * author, and the first line of the log message. Additionally, any
+ * repository references, such as tags, will be shown.
+ *
+ * The log view::
+ * Presents a more rich view of the revision log showing the whole log
+ * message and the diffstat.
+ *
+ * The diff view::
+ * Shows either the diff of the current working tree, that is, what
+ * has changed since the last commit, or the commit diff complete
+ * with log message, diffstat and diff.
+ *
+ * The pager view::
+ * Is used for displaying both input from stdin and output from git
+ * commands entered in the internal prompt.
+ *
+ * The help view::
+ * Displays the information from the tig(1) man page. For the help view
+ * to work you need to have the tig(1) man page installed.
+ **/
static struct view views[] = {
- VIEW_(MAIN, "main", &main_ops, ref_head, sizeof(struct commit)),
- VIEW_(DIFF, "diff", &pager_ops, ref_commit, sizeof(char)),
- VIEW_(LOG, "log", &pager_ops, ref_head, sizeof(char)),
- VIEW_(HELP, "help", &pager_ops, ref_head, sizeof(char)),
- VIEW_(PAGER, "pager", &pager_ops, "static", sizeof(char)),
+ VIEW_(MAIN, "main", &main_ops, ref_head),
+ VIEW_(DIFF, "diff", &pager_ops, ref_commit),
+ VIEW_(LOG, "log", &pager_ops, ref_head),
+ VIEW_(HELP, "help", &pager_ops, "static"),
+ VIEW_(PAGER, "pager", &pager_ops, "static"),
};
#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
-/* The display array of active views and the index of the current view. */
-static struct view *display[2];
-static unsigned int current_view;
-
-#define foreach_view(view, i) \
- for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
-
static void
redraw_view_from(struct view *view, int lineno)
redraw_view_from(view, 0);
}
+
+/**
+ * Title windows
+ * ~~~~~~~~~~~~~
+ * Each view has a title window which shows the name of the view, current
+ * commit ID if available, and where the view is positioned:
+ *
+ * [main] c622eefaa485995320bc743431bae0d497b1d875 - commit 1 of 61 (1%)
+ *
+ * By default, the title of the current view is highlighted using bold font.
+ **/
+
+static void
+update_view_title(struct view *view)
+{
+ if (view == display[current_view])
+ wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
+ else
+ wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
+
+ werase(view->title);
+ wmove(view->title, 0, 0);
+
+ if (*view->ref)
+ wprintw(view->title, "[%s] %s", view->name, view->ref);
+ else
+ wprintw(view->title, "[%s]", view->name);
+
+ if (view->lines) {
+ wprintw(view->title, " - %s %d of %d (%d%%)",
+ view->ops->type,
+ view->lineno + 1,
+ view->lines,
+ (view->lineno + 1) * 100 / view->lines);
+ }
+
+ wrefresh(view->title);
+}
+
static void
resize_display(void)
{
offset = 0;
foreach_view (view, i) {
- /* Keep the size of the all view windows one lager than is
- * required. This makes current line management easier when the
- * cursor will go outside the window. */
+ /* Keep the height of all view->win windows one larger than is
+ * required so that the cursor can wrap-around on the last line
+ * without scrolling the window. */
if (!view->win) {
view->win = newwin(view->height + 1, 0, offset, 0);
if (!view->win)
}
static void
-update_view_title(struct view *view)
+redraw_display(void)
{
- if (view == display[current_view])
- wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
- else
- wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
-
- werase(view->title);
- wmove(view->title, 0, 0);
-
- /* [main] ref: 334b506... - commit 6 of 4383 (0%) */
-
- if (*view->ref)
- wprintw(view->title, "[%s] %s", view->name, view->ref);
- else
- wprintw(view->title, "[%s]", view->name);
+ struct view *view;
+ int i;
- if (view->lines) {
- wprintw(view->title, " - %s %d of %d (%d%%)",
- view->ops->type,
- view->lineno + 1,
- view->lines,
- (view->lineno + 1) * 100 / view->lines);
+ foreach_view (view, i) {
+ redraw_view(view);
+ update_view_title(view);
}
-
- wrefresh(view->title);
}
+
/*
* Navigation
*/
static bool
begin_update(struct view *view)
{
- char *id = view->id;
+ const char *id = view->id;
if (opt_cmd[0]) {
string_copy(view->cmd, opt_cmd);
* invalid so clear it. */
view->ref[0] = 0;
} else {
- char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
+ const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
if (snprintf(view->cmd, sizeof(view->cmd), format,
id, id, id, id, id) >= sizeof(view->cmd))
time_t secs = time(NULL) - view->start_time;
if (view == VIEW(REQ_VIEW_HELP)) {
- char *msg = TIG_HELP;
+ const char *msg = TIG_HELP;
if (view->lines == 0) {
/* Slightly ugly, but abusing view->ref keeps
bool split = !!(flags & OPEN_SPLIT);
bool reload = !!(flags & OPEN_RELOAD);
struct view *view = VIEW(request);
- struct view *displayed;
- int nviews;
-
- /* Cycle between displayed views and count the views. */
- foreach_view (displayed, nviews) {
- if (prev != view &&
- view == displayed &&
- !strcmp(view->vid, prev->vid)) {
- current_view = nviews;
- /* Blur out the title of the previous view. */
- update_view_title(prev);
- report("");
- return;
- }
- }
+ int nviews = display[1] ? 2 : 1;
if (view == prev && nviews == 1 && !reload) {
report("Already in %s view", view->name);
/* Continue loading split views in the background. */
if (!split)
end_update(prev);
+ view->parent = prev;
}
if (view->pipe) {
}
case REQ_TOGGLE_LINE_NUMBERS:
opt_line_number = !opt_line_number;
- redraw_view(view);
- update_view_title(view);
+ redraw_display();
break;
case REQ_PROMPT:
break;
case REQ_SHOW_VERSION:
- report("Version: %s", VERSION);
+ report("%s (built %s)", VERSION, __DATE__);
return TRUE;
case REQ_SCREEN_RESIZE:
resize_display();
/* Fall-through */
case REQ_SCREEN_REDRAW:
- foreach_view (view, i) {
- redraw_view(view);
- update_view_title(view);
- }
+ redraw_display();
break;
case REQ_SCREEN_UPDATE:
doupdate();
return TRUE;
+ case REQ_VIEW_CLOSE:
+ if (view->parent) {
+ memset(display, 0, sizeof(display));
+ current_view = 0;
+ display[current_view] = view->parent;
+ view->parent = NULL;
+ resize_display();
+ redraw_display();
+ break;
+ }
+ /* Fall-through */
case REQ_QUIT:
return FALSE;
pager_enter(struct view *view)
{
char *line = view->line[view->lineno];
+ int split = 0;
- if (get_line_type(line) == LINE_COMMIT) {
- if (view == VIEW(REQ_VIEW_LOG))
- open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT | OPEN_BACKGROUNDED);
- else
- open_view(view, REQ_VIEW_DIFF, OPEN_DEFAULT);
+ if ((view == VIEW(REQ_VIEW_LOG) ||
+ view == VIEW(REQ_VIEW_LOG)) &&
+ get_line_type(line) == LINE_COMMIT) {
+ open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
+ split = 1;
}
+ /* Always scroll the view even if it was split. That way
+ * you can use Enter to scroll through the log view and
+ * split open each commit diff. */
+ scroll_view(view, REQ_SCROLL_LINE_DOWN);
+
+ /* FIXME: A minor workaround. Scrolling the view will call report("")
+ * but if we are scolling a non-current view this won't properly update
+ * the view title. */
+ if (split)
+ update_view_title(view);
+
return TRUE;
}
enum line_type type;
int col = 0;
size_t timelen;
+ size_t authorlen;
+ int trimmed;
if (view->offset + lineno >= view->lines)
return FALSE;
if (type != LINE_CURSOR)
wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
- if (strlen(commit->author) > 19) {
- waddnstr(view->win, commit->author, 18);
+ /* FIXME: Make this optional, and add i18n.commitEncoding support. */
+ authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
+
+ if (trimmed) {
+ waddnstr(view->win, commit->author, authorlen);
if (type != LINE_CURSOR)
wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
waddch(view->win, '~');
waddstr(view->win, commit->author);
}
- col += 20;
+ col += AUTHOR_COLS;
if (type != LINE_CURSOR)
wattrset(view->win, A_NORMAL);
static bool
main_enter(struct view *view)
{
- open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT | OPEN_BACKGROUNDED);
+ open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
return TRUE;
}
* Switch to pager view.
* h::
* Show man page.
- * Return::
- * If on a commit line show the commit diff. Additionally, if in
- * main or log view this will split the view. To open the commit
- * diff in full size view either use 'd' or press Return twice.
+ * q::
+ * Close view, if multiple views are open it will jump back to the
+ * previous view in the view stack. If it is the last open view it
+ * will quit. Use 'Q' to quit all views at once.
+ * Enter::
+ * This key is "context sensitive" depending on what view you are
+ * currently in. When in log view on a commit line or in the main
+ * view, split the view and show the commit diff. In the diff view
+ * pressing Enter will simply scroll the view one line down.
* Tab::
* Switch to next view.
**/
{ 'p', REQ_VIEW_PAGER },
{ 'h', REQ_VIEW_HELP },
+ { 'q', REQ_VIEW_CLOSE },
{ KEY_TAB, REQ_VIEW_NEXT },
{ KEY_RETURN, REQ_ENTER },
* j::
* Move cursor one line down and enter.
* PgUp::
+ * b::
+ * -::
* Move cursor one page up.
* PgDown::
+ * Space::
* Move cursor one page down.
* Home::
* Jump to first line.
{ KEY_HOME, REQ_MOVE_FIRST_LINE },
{ KEY_END, REQ_MOVE_LAST_LINE },
{ KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
+ { ' ', REQ_MOVE_PAGE_DOWN },
{ KEY_PPAGE, REQ_MOVE_PAGE_UP },
+ { 'b', REQ_MOVE_PAGE_UP },
+ { '-', REQ_MOVE_PAGE_UP },
/**
* Scrolling
/**
* Misc
* ~~~~
- * q::
- * Quit
+ * Q::
+ * Quit.
* r::
* Redraw screen.
* z::
*
* :log -p
**/
- { 'q', REQ_QUIT },
+ { 'Q', REQ_QUIT },
{ 'z', REQ_STOP_LOADING },
{ 'v', REQ_SHOW_VERSION },
{ 'r', REQ_SCREEN_REDRAW },
/*
+ * Unicode / UTF-8 handling
+ *
+ * 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.
+ */
+
+/* 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)
+{
+ if (c >= 0x1100 &&
+ (c <= 0x115f /* Hangul Jamo */
+ || c == 0x2329
+ || c == 0x232a
+ || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
+ /* CJK ... Yi */
+ || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
+ || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
+ || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
+ || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
+ || (c >= 0xffe0 && c <= 0xffe6)
+ || (c >= 0x20000 && c <= 0x2fffd)
+ || (c >= 0x30000 && c <= 0x3fffd)))
+ return 2;
+
+ return 1;
+}
+
+/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
+ * Illegal bytes are set one. */
+static const unsigned char utf8_bytes[256] = {
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+ 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
+ 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. */
+static inline unsigned long
+utf8_to_unicode(const char *string, size_t length)
+{
+ unsigned long unicode;
+
+ switch (length) {
+ case 1:
+ unicode = string[0];
+ break;
+ case 2:
+ unicode = (string[0] & 0x1f) << 6;
+ unicode += (string[1] & 0x3f);
+ break;
+ case 3:
+ unicode = (string[0] & 0x0f) << 12;
+ unicode += ((string[1] & 0x3f) << 6);
+ unicode += (string[2] & 0x3f);
+ break;
+ case 4:
+ unicode = (string[0] & 0x0f) << 18;
+ unicode += ((string[1] & 0x3f) << 12);
+ unicode += ((string[2] & 0x3f) << 6);
+ unicode += (string[3] & 0x3f);
+ break;
+ case 5:
+ unicode = (string[0] & 0x0f) << 24;
+ unicode += ((string[1] & 0x3f) << 18);
+ unicode += ((string[2] & 0x3f) << 12);
+ unicode += ((string[3] & 0x3f) << 6);
+ unicode += (string[4] & 0x3f);
+ break;
+ case 6:
+ unicode = (string[0] & 0x01) << 30;
+ unicode += ((string[1] & 0x3f) << 24);
+ unicode += ((string[2] & 0x3f) << 18);
+ unicode += ((string[3] & 0x3f) << 12);
+ unicode += ((string[4] & 0x3f) << 6);
+ unicode += (string[5] & 0x3f);
+ break;
+ default:
+ die("Invalid unicode length");
+ }
+
+ /* Invalid characters could return the special 0xfffd value but NUL
+ * should be just as good. */
+ return unicode > 0xffff ? 0 : unicode;
+}
+
+/* Calculates how much of string can be shown within the given maximum width
+ * and sets trimmed parameter to non-zero value if all of string could not be
+ * shown.
+ *
+ * Additionally, adds to coloffset how many many columns to move to align with
+ * the expected position. Takes into account how multi-byte and double-width
+ * characters will effect the cursor position.
+ *
+ * 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 *coloffset, int *trimmed)
+{
+ const char *start = string;
+ const char *end = strchr(string, '\0');
+ size_t mbwidth = 0;
+ size_t width = 0;
+
+ *trimmed = 0;
+
+ while (string < end) {
+ int c = *(unsigned char *) string;
+ unsigned char bytes = utf8_bytes[c];
+ size_t ucwidth;
+ unsigned long unicode;
+
+ if (string + bytes > end)
+ break;
+
+ /* Change representation to figure out whether
+ * it is a single- or double-width character. */
+
+ unicode = utf8_to_unicode(string, bytes);
+ /* FIXME: Graceful handling of invalid unicode character. */
+ if (!unicode)
+ break;
+
+ ucwidth = unicode_width(unicode);
+ width += ucwidth;
+ if (width > max_width) {
+ *trimmed = 1;
+ break;
+ }
+
+ /* The column offset collects the differences between the
+ * number of bytes encoding a character and the number of
+ * columns will be used for rendering said character.
+ *
+ * So if some character A is encoded in 2 bytes, but will be
+ * represented on the screen using only 1 byte this will and up
+ * adding 1 to the multi-byte column offset.
+ *
+ * Assumes that no double-width character can be encoding in
+ * less than two bytes. */
+ if (bytes > ucwidth)
+ mbwidth += bytes - ucwidth;
+
+ string += bytes;
+ }
+
+ *coloffset += mbwidth;
+
+ return string - start;
+}
+
+
+/*
* Status management
*/
/* Whether or not the curses interface has been initialized. */
-bool cursed = FALSE;
+static bool cursed = FALSE;
/* The status window is used for polling keystrokes. */
static WINDOW *status_win;
static int
load_refs(void)
{
- char *cmd_env = getenv("TIG_LS_REMOTE");
- char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
+ const char *cmd_env = getenv("TIG_LS_REMOTE");
+ const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
FILE *pipe = popen(cmd, "r");
char buffer[BUFSIZ];
char *line;
* Main
*/
-static void
+#if __GNUC__ >= 3
+#define __NORETURN __attribute__((__noreturn__))
+#else
+#define __NORETURN
+#endif
+
+static void __NORETURN
quit(int sig)
{
/* XXX: Restore tty modes and let the OS cleanup the rest! */
exit(0);
}
-static void die(const char *err, ...)
+static void __NORETURN
+die(const char *err, ...)
{
va_list args;
* If you are interested only in those revisions that made changes to a
* specific file (or even several files) list the files like this:
*
- * $ tig log Makefile
+ * $ tig log Makefile README
*
* To avoid ambiguity with repository references such as tag name, be sure
* to separate file names from other git options using "\--". So if you
* If you are only interested in changed that happened between two dates
* you can use:
*
- * $ tig -- --after=May.5th --before=2006-05-16.15:44
+ * $ tig -- --after="May 5th" --before="2006-05-16 15:44"
*
- * NOTE: The dot (".") is used as a separator instead of a space to avoid
- * having to quote the option value. If you prefer use `--after="May 5th"`
- * instead of `--after="May 5th"`.
+ * NOTE: If you want to avoid having to quote dates containing spaces you
+ * can use "." instead, e.g. `--after=May.5th`.
*
* Limiting by commit ranges
* ~~~~~~~~~~~~~~~~~~~~~~~~~
* ----
* Known bugs and problems:
*
+ * - In it's current state tig is pretty much UTF-8 only.
+ *
* - If the screen width is very small the main view can draw
* outside the current view causing bad wrapping. Same goes
* for title and status windows.
* link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
* link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
* gitk(1): git repository browser written using tcl/tk,
+ * qgit(1): git repository browser written using c++/Qt,
* gitview(1): git repository browser written using python/gtk.
**/