+
+/**
+ * KEYS
+ * ----
+ * Below the default key bindings are shown.
+ **/
+
+struct keymap {
+ int alias;
+ int request;
+};
+
+static struct keymap keymap[] = {
+ /**
+ * View switching
+ * ~~~~~~~~~~~~~~
+ * m::
+ * Switch to main view.
+ * d::
+ * Switch to diff view.
+ * l::
+ * Switch to log view.
+ * p::
+ * Switch to pager view.
+ * h::
+ * Show man page.
+ **/
+ { 'm', REQ_VIEW_MAIN },
+ { 'd', REQ_VIEW_DIFF },
+ { 'l', REQ_VIEW_LOG },
+ { 'p', REQ_VIEW_PAGER },
+ { 'h', REQ_VIEW_HELP },
+
+ /**
+ * View manipulation
+ * ~~~~~~~~~~~~~~~~~
+ * 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.
+ * Up::
+ * This key is "context sensitive" and will move the cursor one
+ * line up. However, uf you opened a diff view from the main view
+ * (split- or full-screen) it will change the cursor to point to
+ * the previous commit in the main view and update the diff view
+ * to display it.
+ * Down::
+ * Similar to 'Up' but will move down.
+ **/
+ { 'q', REQ_VIEW_CLOSE },
+ { KEY_TAB, REQ_VIEW_NEXT },
+ { KEY_RETURN, REQ_ENTER },
+ { KEY_UP, REQ_PREVIOUS },
+ { KEY_DOWN, REQ_NEXT },
+
+ /**
+ * Cursor navigation
+ * ~~~~~~~~~~~~~~~~~
+ * j::
+ * Move cursor one line up.
+ * k::
+ * Move cursor one line down.
+ * PgUp::
+ * b::
+ * -::
+ * Move cursor one page up.
+ * PgDown::
+ * Space::
+ * Move cursor one page down.
+ * Home::
+ * Jump to first line.
+ * End::
+ * Jump to last line.
+ **/
+ { 'k', REQ_MOVE_UP },
+ { 'j', REQ_MOVE_DOWN },
+ { 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
+ * ~~~~~~~~~
+ * Insert::
+ * Scroll view one line up.
+ * Delete::
+ * Scroll view one line down.
+ * w::
+ * Scroll view one page up.
+ * s::
+ * Scroll view one page down.
+ **/
+ { KEY_IC, REQ_SCROLL_LINE_UP },
+ { KEY_DC, REQ_SCROLL_LINE_DOWN },
+ { 'w', REQ_SCROLL_PAGE_UP },
+ { 's', REQ_SCROLL_PAGE_DOWN },
+
+ /**
+ * Misc
+ * ~~~~
+ * Q::
+ * Quit.
+ * r::
+ * Redraw screen.
+ * z::
+ * Stop all background loading. This can be useful if you use
+ * tig(1) in a repository with a long history without limiting
+ * the revision log.
+ * v::
+ * Show version.
+ * n::
+ * Toggle line numbers on/off.
+ * ':'::
+ * Open prompt. This allows you to specify what git command
+ * to run. Example:
+ *
+ * :log -p
+ **/
+ { 'Q', REQ_QUIT },
+ { 'z', REQ_STOP_LOADING },
+ { 'v', REQ_SHOW_VERSION },
+ { 'r', REQ_SCREEN_REDRAW },
+ { 'n', REQ_TOGGLE_LINE_NUMBERS },
+ { ':', REQ_PROMPT },
+
+ /* wgetch() with nodelay() enabled returns ERR when there's no input. */
+ { ERR, REQ_SCREEN_UPDATE },
+
+ /* Use the ncurses SIGWINCH handler. */
+ { KEY_RESIZE, REQ_SCREEN_RESIZE },
+};
+
+static enum request
+get_request(int key)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(keymap); i++)
+ if (keymap[i].alias == key)
+ return keymap[i].request;
+
+ return (enum request) key;
+}
+
+
+/*
+ * 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;
+}
+
+