+/*
+ * Line-oriented content detection.
+ */
+
+#define LINE_INFO \
+LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
+LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
+LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
+LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
+LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
+LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
+LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
+LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
+LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
+LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
+LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
+LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
+LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
+LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
+LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
+LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
+LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
+LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
+LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
+LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
+LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
+LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
+LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
+LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
+LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL)
+
+enum line_type {
+#define LINE(type, line, fg, bg, attr) \
+ LINE_##type
+ LINE_INFO
+#undef LINE
+};
+
+struct line_info {
+ const char *name; /* Option name. */
+ int namelen; /* Size of option name. */
+ 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. */
+};
+
+static struct line_info line_info[] = {
+#define LINE(type, line, fg, bg, attr) \
+ { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
+ LINE_INFO
+#undef LINE
+};
+
+static enum line_type
+get_line_type(char *line)
+{
+ int linelen = strlen(line);
+ enum line_type type;
+
+ for (type = 0; type < ARRAY_SIZE(line_info); type++)
+ /* Case insensitive search matches Signed-off-by lines better. */
+ if (linelen >= line_info[type].linelen &&
+ !strncasecmp(line_info[type].line, line, line_info[type].linelen))
+ return type;
+
+ return LINE_DEFAULT;
+}
+
+static inline int
+get_line_attr(enum line_type type)
+{
+ assert(type < ARRAY_SIZE(line_info));
+ return COLOR_PAIR(type) | line_info[type].attr;
+}
+
+static struct line_info *
+get_line_info(char *name, int namelen)
+{
+ enum line_type type;
+
+ for (type = 0; type < ARRAY_SIZE(line_info); type++)
+ if (namelen == line_info[type].namelen &&
+ !string_enum_compare(line_info[type].name, name, namelen))
+ return &line_info[type];
+
+ return NULL;
+}
+
+static void
+init_colors(void)
+{
+ int default_bg = COLOR_BLACK;
+ int default_fg = COLOR_WHITE;
+ enum line_type type;
+
+ start_color();
+
+ if (use_default_colors() != ERR) {
+ default_bg = -1;
+ default_fg = -1;
+ }
+
+ for (type = 0; type < ARRAY_SIZE(line_info); type++) {
+ struct line_info *info = &line_info[type];
+ int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
+ int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
+
+ init_pair(type, fg, bg);
+ }
+}
+
+struct line {
+ enum line_type type;
+
+ /* State flags */
+ unsigned int selected:1;
+
+ void *data; /* User data */
+};
+
+
+/*
+ * Keys
+ */
+
+struct keybinding {
+ int alias;
+ enum request request;
+ struct keybinding *next;
+};
+
+static struct keybinding default_keybindings[] = {
+ /* View switching */
+ { 'm', REQ_VIEW_MAIN },
+ { 'd', REQ_VIEW_DIFF },
+ { 'l', REQ_VIEW_LOG },
+ { 't', REQ_VIEW_TREE },
+ { 'f', REQ_VIEW_BLOB },
+ { 'p', REQ_VIEW_PAGER },
+ { 'h', REQ_VIEW_HELP },
+
+ /* View manipulation */
+ { 'q', REQ_VIEW_CLOSE },
+ { KEY_TAB, REQ_VIEW_NEXT },
+ { KEY_RETURN, REQ_ENTER },
+ { KEY_UP, REQ_PREVIOUS },
+ { KEY_DOWN, REQ_NEXT },
+
+ /* Cursor navigation */
+ { '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 */
+ { KEY_IC, REQ_SCROLL_LINE_UP },
+ { KEY_DC, REQ_SCROLL_LINE_DOWN },
+ { 'w', REQ_SCROLL_PAGE_UP },
+ { 's', REQ_SCROLL_PAGE_DOWN },
+
+ /* Searching */
+ { '/', REQ_SEARCH },
+ { '?', REQ_SEARCH_BACK },
+ { 'n', REQ_FIND_NEXT },
+ { 'N', REQ_FIND_PREV },
+
+ /* Misc */
+ { 'Q', REQ_QUIT },
+ { 'z', REQ_STOP_LOADING },
+ { 'v', REQ_SHOW_VERSION },
+ { 'r', REQ_SCREEN_REDRAW },
+ { '.', REQ_TOGGLE_LINENO },
+ { 'g', REQ_TOGGLE_REV_GRAPH },
+ { ':', REQ_PROMPT },
+
+ /* wgetch() with nodelay() enabled returns ERR when there's no input. */
+ { ERR, REQ_NONE },
+
+ /* Using the ncurses SIGWINCH handler. */
+ { KEY_RESIZE, REQ_SCREEN_RESIZE },
+};
+
+#define KEYMAP_INFO \
+ KEYMAP_(GENERIC), \
+ KEYMAP_(MAIN), \
+ KEYMAP_(DIFF), \
+ KEYMAP_(LOG), \
+ KEYMAP_(TREE), \
+ KEYMAP_(BLOB), \
+ KEYMAP_(PAGER), \
+ KEYMAP_(HELP) \
+
+enum keymap {
+#define KEYMAP_(name) KEYMAP_##name
+ KEYMAP_INFO
+#undef KEYMAP_
+};
+
+static struct int_map keymap_table[] = {
+#define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
+ KEYMAP_INFO
+#undef KEYMAP_
+};
+
+#define set_keymap(map, name) \
+ set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
+
+static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
+
+static void
+add_keybinding(enum keymap keymap, enum request request, int key)
+{
+ struct keybinding *keybinding;
+
+ keybinding = calloc(1, sizeof(*keybinding));
+ if (!keybinding)
+ die("Failed to allocate keybinding");
+
+ keybinding->alias = key;
+ keybinding->request = request;
+ keybinding->next = keybindings[keymap];
+ keybindings[keymap] = keybinding;
+}
+
+/* Looks for a key binding first in the given map, then in the generic map, and
+ * lastly in the default keybindings. */
+static enum request
+get_keybinding(enum keymap keymap, int key)
+{
+ struct keybinding *kbd;
+ int i;
+
+ for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
+ if (kbd->alias == key)
+ return kbd->request;
+
+ for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
+ if (kbd->alias == key)
+ return kbd->request;
+
+ for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
+ if (default_keybindings[i].alias == key)
+ return default_keybindings[i].request;
+
+ return (enum request) key;
+}
+
+
+struct key {
+ char *name;
+ int value;
+};
+
+static struct key key_table[] = {
+ { "Enter", KEY_RETURN },
+ { "Space", ' ' },
+ { "Backspace", KEY_BACKSPACE },
+ { "Tab", KEY_TAB },
+ { "Escape", KEY_ESC },
+ { "Left", KEY_LEFT },
+ { "Right", KEY_RIGHT },
+ { "Up", KEY_UP },
+ { "Down", KEY_DOWN },
+ { "Insert", KEY_IC },
+ { "Delete", KEY_DC },
+ { "Hash", '#' },
+ { "Home", KEY_HOME },
+ { "End", KEY_END },
+ { "PageUp", KEY_PPAGE },
+ { "PageDown", KEY_NPAGE },
+ { "F1", KEY_F(1) },
+ { "F2", KEY_F(2) },
+ { "F3", KEY_F(3) },
+ { "F4", KEY_F(4) },
+ { "F5", KEY_F(5) },
+ { "F6", KEY_F(6) },
+ { "F7", KEY_F(7) },
+ { "F8", KEY_F(8) },
+ { "F9", KEY_F(9) },
+ { "F10", KEY_F(10) },
+ { "F11", KEY_F(11) },
+ { "F12", KEY_F(12) },
+};
+
+static int
+get_key_value(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(key_table); i++)
+ if (!strcasecmp(key_table[i].name, name))
+ return key_table[i].value;
+
+ if (strlen(name) == 1 && isprint(*name))
+ return (int) *name;
+
+ return ERR;
+}
+
+static char *
+get_key(enum request request)
+{
+ static char buf[BUFSIZ];
+ static char key_char[] = "'X'";
+ size_t pos = 0;
+ char *sep = " ";
+ int i;
+
+ buf[pos] = 0;
+
+ for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
+ struct keybinding *keybinding = &default_keybindings[i];
+ char *seq = NULL;
+ int key;
+
+ if (keybinding->request != request)
+ continue;
+
+ for (key = 0; key < ARRAY_SIZE(key_table); key++)
+ if (key_table[key].value == keybinding->alias)
+ seq = key_table[key].name;
+
+ if (seq == NULL &&
+ keybinding->alias < 127 &&
+ isprint(keybinding->alias)) {
+ key_char[1] = (char) keybinding->alias;
+ seq = key_char;
+ }
+
+ if (!seq)
+ seq = "'?'";
+
+ if (!string_format_from(buf, &pos, "%s%s", sep, seq))
+ return "Too many keybindings!";
+ sep = ", ";
+ }
+
+ return buf;
+}
+
+
+/*
+ * User config file handling.
+ */
+
+static struct int_map color_map[] = {
+#define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
+ COLOR_MAP(DEFAULT),
+ COLOR_MAP(BLACK),
+ COLOR_MAP(BLUE),
+ COLOR_MAP(CYAN),
+ COLOR_MAP(GREEN),
+ COLOR_MAP(MAGENTA),
+ COLOR_MAP(RED),
+ COLOR_MAP(WHITE),
+ COLOR_MAP(YELLOW),
+};
+
+#define set_color(color, name) \
+ set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
+
+static struct int_map attr_map[] = {
+#define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
+ ATTR_MAP(NORMAL),
+ ATTR_MAP(BLINK),
+ ATTR_MAP(BOLD),
+ ATTR_MAP(DIM),
+ ATTR_MAP(REVERSE),
+ ATTR_MAP(STANDOUT),
+ ATTR_MAP(UNDERLINE),
+};
+
+#define set_attribute(attr, name) \
+ set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
+
+static int config_lineno;
+static bool config_errors;
+static char *config_msg;
+
+/* Wants: object fgcolor bgcolor [attr] */
+static int
+option_color_command(int argc, char *argv[])
+{
+ struct line_info *info;
+
+ if (argc != 3 && argc != 4) {
+ config_msg = "Wrong number of arguments given to color command";
+ return ERR;
+ }
+
+ info = get_line_info(argv[0], strlen(argv[0]));
+ if (!info) {
+ config_msg = "Unknown color name";
+ return ERR;
+ }
+
+ if (set_color(&info->fg, argv[1]) == ERR ||
+ set_color(&info->bg, argv[2]) == ERR) {
+ config_msg = "Unknown color";
+ return ERR;
+ }
+
+ if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
+ config_msg = "Unknown attribute";
+ return ERR;
+ }
+
+ return OK;
+}
+
+/* Wants: name = value */
+static int
+option_set_command(int argc, char *argv[])
+{
+ if (argc != 3) {
+ config_msg = "Wrong number of arguments given to set command";
+ return ERR;
+ }
+
+ if (strcmp(argv[1], "=")) {
+ config_msg = "No value assigned";
+ return ERR;
+ }
+
+ if (!strcmp(argv[0], "show-rev-graph")) {
+ opt_rev_graph = (!strcmp(argv[2], "1") ||
+ !strcmp(argv[2], "true") ||
+ !strcmp(argv[2], "yes"));
+ return OK;
+ }
+
+ if (!strcmp(argv[0], "line-number-interval")) {
+ opt_num_interval = atoi(argv[2]);
+ return OK;
+ }
+
+ if (!strcmp(argv[0], "tab-size")) {
+ opt_tab_size = atoi(argv[2]);
+ return OK;
+ }
+
+ if (!strcmp(argv[0], "commit-encoding")) {
+ char *arg = argv[2];
+ int delimiter = *arg;
+ int i;
+
+ switch (delimiter) {
+ case '"':
+ case '\'':
+ for (arg++, i = 0; arg[i]; i++)
+ if (arg[i] == delimiter) {
+ arg[i] = 0;
+ break;
+ }
+ default:
+ string_copy(opt_encoding, arg);
+ return OK;
+ }
+ }
+
+ config_msg = "Unknown variable name";
+ return ERR;
+}
+
+/* Wants: mode request key */
+static int
+option_bind_command(int argc, char *argv[])
+{
+ enum request request;
+ int keymap;
+ int key;
+
+ if (argc != 3) {
+ config_msg = "Wrong number of arguments given to bind command";
+ return ERR;
+ }
+
+ if (set_keymap(&keymap, argv[0]) == ERR) {
+ config_msg = "Unknown key map";
+ return ERR;
+ }
+
+ key = get_key_value(argv[1]);
+ if (key == ERR) {
+ config_msg = "Unknown key";
+ return ERR;
+ }
+
+ request = get_request(argv[2]);
+ if (request == REQ_UNKNOWN) {
+ config_msg = "Unknown request name";
+ return ERR;
+ }