Code

Move IO API to src/io.[ch]
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index 6012fc814bea5ce1485893fd787172e2616d3a63..6ad9c57953182e4d54d4ad3fdca66d61e8dfc4b3 100644 (file)
--- a/tig.c
+++ b/tig.c
  * GNU General Public License for more details.
  */
 
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#ifndef TIG_VERSION
-#define TIG_VERSION "unknown-version"
-#endif
-
-#ifndef DEBUG
-#define NDEBUG
-#endif
-
-#include <assert.h>
-#include <errno.h>
-#include <ctype.h>
-#include <signal.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <sys/stat.h>
-#include <sys/select.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include <time.h>
-#include <fcntl.h>
-
-#include <regex.h>
-
-#include <locale.h>
-#include <langinfo.h>
-#include <iconv.h>
-
-/* ncurses(3): Must be defined to have extended wide-character functions. */
-#define _XOPEN_SOURCE_EXTENDED
-
-#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__))
-#else
-#define __NORETURN
-#endif
+#include "tig.h"
+#include "io.h"
 
 static void __NORETURN die(const char *err, ...);
 static void warn(const char *msg, ...);
 static void report(const char *msg, ...);
 
-#define ABS(x)         ((x) >= 0  ? (x) : -(x))
-#define MIN(x, y)      ((x) < (y) ? (x) :  (y))
-#define MAX(x, y)      ((x) > (y) ? (x) :  (y))
-
-#define ARRAY_SIZE(x)  (sizeof(x) / sizeof(x[0]))
-#define STRING_SIZE(x) (sizeof(x) - 1)
-
-#define SIZEOF_STR     1024    /* Default string size. */
-#define SIZEOF_REF     256     /* Size of symbolic or SHA1 ID. */
-#define SIZEOF_REV     41      /* Holds a SHA-1 and an ending NUL. */
-#define SIZEOF_ARG     32      /* Default argument array size. */
-
-/* Revision graph */
-
-#define REVGRAPH_INIT  'I'
-#define REVGRAPH_MERGE 'M'
-#define REVGRAPH_BRANCH        '+'
-#define REVGRAPH_COMMIT        '*'
-#define REVGRAPH_BOUND '^'
-
-#define SIZEOF_REVGRAPH        19      /* Size of revision ancestry graphics. */
-
-/* This color name can be used to refer to the default term colors. */
-#define COLOR_DEFAULT  (-1)
-
-#define ICONV_NONE     ((iconv_t) -1)
-#ifndef ICONV_CONST
-#define ICONV_CONST    /* nothing */
-#endif
-
-/* 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 DATE_SHORT_COLS        STRING_SIZE("2006-04-29 ")
-
-#define ID_COLS                8
-#define AUTHOR_COLS    19
-
-#define MIN_VIEW_HEIGHT 4
-
-#define NULL_ID                "0000000000000000000000000000000000000000"
-
-#define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
-
-/* Some ASCII-shorthands fitted into the ncurses namespace. */
-#define KEY_CTL(x)     ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
-#define KEY_TAB                '\t'
-#define KEY_RETURN     '\r'
-#define KEY_ESC                27
-
 
 struct ref {
        char id[SIZEOF_REV];    /* Commit SHA1 ID */
@@ -161,356 +60,6 @@ struct menu_item {
 
 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
 
-/*
- * Allocation helpers ... Entering macro hell to never be seen again.
- */
-
-#define DEFINE_ALLOCATOR(name, type, chunk_size)                               \
-static type *                                                                  \
-name(type **mem, size_t size, size_t increase)                                 \
-{                                                                              \
-       size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
-       size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
-       type *tmp = *mem;                                                       \
-                                                                               \
-       if (mem == NULL || num_chunks != num_chunks_new) {                      \
-               tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
-               if (tmp)                                                        \
-                       *mem = tmp;                                             \
-       }                                                                       \
-                                                                               \
-       return tmp;                                                             \
-}
-
-/*
- * String helpers
- */
-
-static inline void
-string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
-{
-       if (srclen > dstlen - 1)
-               srclen = dstlen - 1;
-
-       strncpy(dst, src, srclen);
-       dst[srclen] = 0;
-}
-
-/* Shorthands for safely copying into a fixed buffer. */
-
-#define string_copy(dst, src) \
-       string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
-
-#define string_ncopy(dst, src, srclen) \
-       string_ncopy_do(dst, sizeof(dst), src, srclen)
-
-#define string_copy_rev(dst, src) \
-       string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
-
-#define string_add(dst, from, src) \
-       string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
-
-static size_t
-string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
-{
-       size_t size, pos;
-
-       for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
-               if (src[pos] == '\t') {
-                       size_t expanded = tabsize - (size % tabsize);
-
-                       if (expanded + size >= dstlen - 1)
-                               expanded = dstlen - size - 1;
-                       memcpy(dst + size, "        ", expanded);
-                       size += expanded;
-               } else {
-                       dst[size++] = src[pos];
-               }
-       }
-
-       dst[size] = 0;
-       return pos;
-}
-
-static char *
-chomp_string(char *name)
-{
-       int namelen;
-
-       while (isspace(*name))
-               name++;
-
-       namelen = strlen(name) - 1;
-       while (namelen > 0 && isspace(name[namelen]))
-               name[namelen--] = 0;
-
-       return name;
-}
-
-static bool
-string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
-{
-       va_list args;
-       size_t pos = bufpos ? *bufpos : 0;
-
-       va_start(args, fmt);
-       pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
-       va_end(args);
-
-       if (bufpos)
-               *bufpos = pos;
-
-       return pos >= bufsize ? FALSE : TRUE;
-}
-
-#define string_format(buf, fmt, args...) \
-       string_nformat(buf, sizeof(buf), NULL, fmt, args)
-
-#define string_format_from(buf, from, fmt, args...) \
-       string_nformat(buf, sizeof(buf), from, fmt, args)
-
-static int
-string_enum_compare(const char *str1, const char *str2, int len)
-{
-       size_t i;
-
-#define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
-
-       /* Diff-Header == DIFF_HEADER */
-       for (i = 0; i < len; i++) {
-               if (toupper(str1[i]) == toupper(str2[i]))
-                       continue;
-
-               if (string_enum_sep(str1[i]) &&
-                   string_enum_sep(str2[i]))
-                       continue;
-
-               return str1[i] - str2[i];
-       }
-
-       return 0;
-}
-
-#define enum_equals(entry, str, len) \
-       ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
-
-struct enum_map {
-       const char *name;
-       int namelen;
-       int value;
-};
-
-#define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
-
-static char *
-enum_map_name(const char *name, size_t namelen)
-{
-       static char buf[SIZEOF_STR];
-       int bufpos;
-
-       for (bufpos = 0; bufpos <= namelen; bufpos++) {
-               buf[bufpos] = tolower(name[bufpos]);
-               if (buf[bufpos] == '_')
-                       buf[bufpos] = '-';
-       }
-
-       buf[bufpos] = 0;
-       return buf;
-}
-
-#define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
-
-static bool
-map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
-{
-       size_t namelen = strlen(name);
-       int i;
-
-       for (i = 0; i < map_size; i++)
-               if (enum_equals(map[i], name, namelen)) {
-                       *value = map[i].value;
-                       return TRUE;
-               }
-
-       return FALSE;
-}
-
-#define map_enum(attr, map, name) \
-       map_enum_do(map, ARRAY_SIZE(map), attr, name)
-
-#define prefixcmp(str1, str2) \
-       strncmp(str1, str2, STRING_SIZE(str2))
-
-static inline int
-suffixcmp(const char *str, int slen, const char *suffix)
-{
-       size_t len = slen >= 0 ? slen : strlen(str);
-       size_t suffixlen = strlen(suffix);
-
-       return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
-}
-
-
-/*
- * 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 UTF-8 branch commit elinks-0.11.0-g31f2c28.
- */
-
-static inline int
-unicode_width(unsigned long c, int tab_size)
-{
-       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;
-
-       if (c == '\t')
-               return tab_size;
-
-       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,
-};
-
-static inline unsigned char
-utf8_char_length(const char *string, const char *end)
-{
-       int c = *(unsigned char *) string;
-
-       return utf8_bytes[c];
-}
-
-/* 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:
-               return 0;
-       }
-
-       /* 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. If the reserve flag is TRUE, it will reserve at least one
- * trailing character, which can be useful when drawing a delimiter.
- *
- * Returns the number of bytes to output from string to satisfy max_width. */
-static size_t
-utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
-{
-       const char *string = *start;
-       const char *end = strchr(string, '\0');
-       unsigned char last_bytes = 0;
-       size_t last_ucwidth = 0;
-
-       *width = 0;
-       *trimmed = 0;
-
-       while (string < end) {
-               unsigned char bytes = utf8_char_length(string, end);
-               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, tab_size);
-               if (skip > 0) {
-                       skip -= ucwidth <= skip ? ucwidth : skip;
-                       *start += bytes;
-               }
-               *width  += ucwidth;
-               if (*width > max_width) {
-                       *trimmed = 1;
-                       *width -= ucwidth;
-                       if (reserve && *width == max_width) {
-                               string -= last_bytes;
-                               *width -= last_ucwidth;
-                       }
-                       break;
-               }
-
-               string  += bytes;
-               last_bytes = ucwidth ? bytes : 0;
-               last_ucwidth = ucwidth;
-       }
-
-       return string - *start;
-}
-
-
 #define DATE_INFO \
        DATE_(NO), \
        DATE_(DEFAULT), \
@@ -644,455 +193,6 @@ get_author_initials(const char *author)
 }
 
 
-static bool
-argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
-{
-       int valuelen;
-
-       while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
-               bool advance = cmd[valuelen] != 0;
-
-               cmd[valuelen] = 0;
-               argv[(*argc)++] = chomp_string(cmd);
-               cmd = chomp_string(cmd + valuelen + advance);
-       }
-
-       if (*argc < SIZEOF_ARG)
-               argv[*argc] = NULL;
-       return *argc < SIZEOF_ARG;
-}
-
-static bool
-argv_from_env(const char **argv, const char *name)
-{
-       char *env = argv ? getenv(name) : NULL;
-       int argc = 0;
-
-       if (env && *env)
-               env = strdup(env);
-       return !env || argv_from_string(argv, &argc, env);
-}
-
-static void
-argv_free(const char *argv[])
-{
-       int argc;
-
-       if (!argv)
-               return;
-       for (argc = 0; argv[argc]; argc++)
-               free((void *) argv[argc]);
-       argv[0] = NULL;
-}
-
-static size_t
-argv_size(const char **argv)
-{
-       int argc = 0;
-
-       while (argv && argv[argc])
-               argc++;
-
-       return argc;
-}
-
-DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
-
-static bool
-argv_append(const char ***argv, const char *arg)
-{
-       size_t argc = argv_size(*argv);
-
-       if (!argv_realloc(argv, argc, 2))
-               return FALSE;
-
-       (*argv)[argc++] = strdup(arg);
-       (*argv)[argc] = NULL;
-       return TRUE;
-}
-
-static bool
-argv_append_array(const char ***dst_argv, const char *src_argv[])
-{
-       int i;
-
-       for (i = 0; src_argv && src_argv[i]; i++)
-               if (!argv_append(dst_argv, src_argv[i]))
-                       return FALSE;
-       return TRUE;
-}
-
-static bool
-argv_copy(const char ***dst, const char *src[])
-{
-       int argc;
-
-       for (argc = 0; src[argc]; argc++)
-               if (!argv_append(dst, src[argc]))
-                       return FALSE;
-       return TRUE;
-}
-
-
-/*
- * Executing external commands.
- */
-
-enum io_type {
-       IO_FD,                  /* File descriptor based IO. */
-       IO_BG,                  /* Execute command in the background. */
-       IO_FG,                  /* Execute command with same std{in,out,err}. */
-       IO_RD,                  /* Read only fork+exec IO. */
-       IO_WR,                  /* Write only fork+exec IO. */
-       IO_AP,                  /* Append fork+exec output to file. */
-};
-
-struct io {
-       int pipe;               /* Pipe end for reading or writing. */
-       pid_t pid;              /* PID of spawned process. */
-       int error;              /* Error status. */
-       char *buf;              /* Read buffer. */
-       size_t bufalloc;        /* Allocated buffer size. */
-       size_t bufsize;         /* Buffer content size. */
-       char *bufpos;           /* Current buffer position. */
-       unsigned int eof:1;     /* Has end of file been reached. */
-};
-
-static void
-io_init(struct io *io)
-{
-       memset(io, 0, sizeof(*io));
-       io->pipe = -1;
-}
-
-static bool
-io_open(struct io *io, const char *fmt, ...)
-{
-       char name[SIZEOF_STR] = "";
-       bool fits;
-       va_list args;
-
-       io_init(io);
-
-       va_start(args, fmt);
-       fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
-       va_end(args);
-
-       if (!fits) {
-               io->error = ENAMETOOLONG;
-               return FALSE;
-       }
-       io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
-       if (io->pipe == -1)
-               io->error = errno;
-       return io->pipe != -1;
-}
-
-static bool
-io_kill(struct io *io)
-{
-       return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
-}
-
-static bool
-io_done(struct io *io)
-{
-       pid_t pid = io->pid;
-
-       if (io->pipe != -1)
-               close(io->pipe);
-       free(io->buf);
-       io_init(io);
-
-       while (pid > 0) {
-               int status;
-               pid_t waiting = waitpid(pid, &status, 0);
-
-               if (waiting < 0) {
-                       if (errno == EINTR)
-                               continue;
-                       io->error = errno;
-                       return FALSE;
-               }
-
-               return waiting == pid &&
-                      !WIFSIGNALED(status) &&
-                      WIFEXITED(status) &&
-                      !WEXITSTATUS(status);
-       }
-
-       return TRUE;
-}
-
-static bool
-io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
-{
-       int pipefds[2] = { -1, -1 };
-       va_list args;
-
-       io_init(io);
-
-       if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
-               io->error = errno;
-               return FALSE;
-       } else if (type == IO_AP) {
-               va_start(args, argv);
-               pipefds[1] = va_arg(args, int);
-               va_end(args);
-       }
-
-       if ((io->pid = fork())) {
-               if (io->pid == -1)
-                       io->error = errno;
-               if (pipefds[!(type == IO_WR)] != -1)
-                       close(pipefds[!(type == IO_WR)]);
-               if (io->pid != -1) {
-                       io->pipe = pipefds[!!(type == IO_WR)];
-                       return TRUE;
-               }
-
-       } else {
-               if (type != IO_FG) {
-                       int devnull = open("/dev/null", O_RDWR);
-                       int readfd  = type == IO_WR ? pipefds[0] : devnull;
-                       int writefd = (type == IO_RD || type == IO_AP)
-                                                       ? pipefds[1] : devnull;
-
-                       dup2(readfd,  STDIN_FILENO);
-                       dup2(writefd, STDOUT_FILENO);
-                       dup2(devnull, STDERR_FILENO);
-
-                       close(devnull);
-                       if (pipefds[0] != -1)
-                               close(pipefds[0]);
-                       if (pipefds[1] != -1)
-                               close(pipefds[1]);
-               }
-
-               if (dir && *dir && chdir(dir) == -1)
-                       exit(errno);
-
-               execvp(argv[0], (char *const*) argv);
-               exit(errno);
-       }
-
-       if (pipefds[!!(type == IO_WR)] != -1)
-               close(pipefds[!!(type == IO_WR)]);
-       return FALSE;
-}
-
-static bool
-io_complete(enum io_type type, const char **argv, const char *dir, int fd)
-{
-       struct io io;
-
-       return io_run(&io, type, dir, argv, fd) && io_done(&io);
-}
-
-static bool
-io_run_bg(const char **argv)
-{
-       return io_complete(IO_BG, argv, NULL, -1);
-}
-
-static bool
-io_run_fg(const char **argv, const char *dir)
-{
-       return io_complete(IO_FG, argv, dir, -1);
-}
-
-static bool
-io_run_append(const char **argv, int fd)
-{
-       return io_complete(IO_AP, argv, NULL, fd);
-}
-
-static bool
-io_eof(struct io *io)
-{
-       return io->eof;
-}
-
-static int
-io_error(struct io *io)
-{
-       return io->error;
-}
-
-static char *
-io_strerror(struct io *io)
-{
-       return strerror(io->error);
-}
-
-static bool
-io_can_read(struct io *io)
-{
-       struct timeval tv = { 0, 500 };
-       fd_set fds;
-
-       FD_ZERO(&fds);
-       FD_SET(io->pipe, &fds);
-
-       return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
-}
-
-static ssize_t
-io_read(struct io *io, void *buf, size_t bufsize)
-{
-       do {
-               ssize_t readsize = read(io->pipe, buf, bufsize);
-
-               if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
-                       continue;
-               else if (readsize == -1)
-                       io->error = errno;
-               else if (readsize == 0)
-                       io->eof = 1;
-               return readsize;
-       } while (1);
-}
-
-DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
-
-static char *
-io_get(struct io *io, int c, bool can_read)
-{
-       char *eol;
-       ssize_t readsize;
-
-       while (TRUE) {
-               if (io->bufsize > 0) {
-                       eol = memchr(io->bufpos, c, io->bufsize);
-                       if (eol) {
-                               char *line = io->bufpos;
-
-                               *eol = 0;
-                               io->bufpos = eol + 1;
-                               io->bufsize -= io->bufpos - line;
-                               return line;
-                       }
-               }
-
-               if (io_eof(io)) {
-                       if (io->bufsize) {
-                               io->bufpos[io->bufsize] = 0;
-                               io->bufsize = 0;
-                               return io->bufpos;
-                       }
-                       return NULL;
-               }
-
-               if (!can_read)
-                       return NULL;
-
-               if (io->bufsize > 0 && io->bufpos > io->buf)
-                       memmove(io->buf, io->bufpos, io->bufsize);
-
-               if (io->bufalloc == io->bufsize) {
-                       if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
-                               return NULL;
-                       io->bufalloc += BUFSIZ;
-               }
-
-               io->bufpos = io->buf;
-               readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
-               if (io_error(io))
-                       return NULL;
-               io->bufsize += readsize;
-       }
-}
-
-static bool
-io_write(struct io *io, const void *buf, size_t bufsize)
-{
-       size_t written = 0;
-
-       while (!io_error(io) && written < bufsize) {
-               ssize_t size;
-
-               size = write(io->pipe, buf + written, bufsize - written);
-               if (size < 0 && (errno == EAGAIN || errno == EINTR))
-                       continue;
-               else if (size == -1)
-                       io->error = errno;
-               else
-                       written += size;
-       }
-
-       return written == bufsize;
-}
-
-static bool
-io_read_buf(struct io *io, char buf[], size_t bufsize)
-{
-       char *result = io_get(io, '\n', TRUE);
-
-       if (result) {
-               result = chomp_string(result);
-               string_ncopy_do(buf, bufsize, result, strlen(result));
-       }
-
-       return io_done(io) && result;
-}
-
-static bool
-io_run_buf(const char **argv, char buf[], size_t bufsize)
-{
-       struct io io;
-
-       return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
-}
-
-typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data);
-
-static int
-io_load(struct io *io, const char *separators,
-       io_read_fn read_property, void *data)
-{
-       char *name;
-       int state = OK;
-
-       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, data);
-       }
-
-       if (state != ERR && io_error(io))
-               state = ERR;
-       io_done(io);
-
-       return state;
-}
-
-static int
-io_run_load(const char **argv, const char *separators,
-           io_read_fn read_property, void *data)
-{
-       struct io io;
-
-       if (!io_run(&io, IO_RD, NULL, argv))
-               return ERR;
-       return io_load(&io, separators, read_property, data);
-}
-
-
 /*
  * User requests
  */
@@ -1920,24 +1020,21 @@ option_color_command(int argc, const char *argv[])
                };
                int index;
 
-               if (!map_enum(&index, obsolete, argv[0])) {
+               if (!map_enum(&index, obsolete, argv[0]))
                        return OPT_ERR_UNKNOWN_COLOR_NAME;
-               }
                info = &line_info[index];
        }
 
        if (!set_color(&info->fg, argv[1]) ||
-           !set_color(&info->bg, argv[2])) {
+           !set_color(&info->bg, argv[2]))
                return OPT_ERR_UNKNOWN_COLOR;
-       }
 
        info->attr = 0;
        while (argc-- > 3) {
                int attr;
 
-               if (!set_attribute(&attr, argv[argc])) {
+               if (!set_attribute(&attr, argv[argc]))
                        return OPT_ERR_UNKNOWN_ATTRIBUTE;
-               }
                info->attr |= attr;
        }
 
@@ -2208,6 +1305,8 @@ struct view_ops;
 
 /* The display array of active views and the index of the current view. */
 static struct view *display[2];
+static WINDOW *display_win[2];
+static WINDOW *display_title[2];
 static unsigned int current_view;
 
 #define foreach_displayed_view(view, i) \
@@ -2251,7 +1350,6 @@ struct view {
 
        int height, width;      /* The width and height of the main window */
        WINDOW *win;            /* The main window */
-       WINDOW *title;          /* The title window living below the main window */
 
        /* Navigation */
        unsigned long offset;   /* Offset of the window top */
@@ -2439,14 +1537,14 @@ draw_space(struct view *view, enum line_type type, int max, int spaces)
 }
 
 static bool
-draw_text(struct view *view, enum line_type type, const char *string, bool trim)
+draw_text(struct view *view, enum line_type type, const char *string)
 {
        char text[SIZEOF_STR];
 
        do {
                size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
 
-               view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
+               view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, TRUE);
                string += pos;
        } while (*string && view->width + view->yoffset > view->col);
 
@@ -2638,6 +1736,7 @@ update_view_title(struct view *view)
        char buf[SIZEOF_STR];
        char state[SIZEOF_STR];
        size_t bufpos = 0, statelen = 0;
+       WINDOW *window = display[0] == view ? display_title[0] : display_title[1];
 
        assert(view_is_displayed(view));
 
@@ -2678,13 +1777,13 @@ update_view_title(struct view *view)
        }
 
        if (view == display[current_view])
-               wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
+               wbkgdset(window, get_line_attr(LINE_TITLE_FOCUS));
        else
-               wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
+               wbkgdset(window, get_line_attr(LINE_TITLE_BLUR));
 
-       mvwaddnstr(view->title, 0, 0, buf, bufpos);
-       wclrtoeol(view->title);
-       wnoutrefresh(view->title);
+       mvwaddnstr(window, 0, 0, buf, bufpos);
+       wclrtoeol(window);
+       wnoutrefresh(window);
 }
 
 static int
@@ -2728,23 +1827,25 @@ resize_display(void)
        offset = 0;
 
        foreach_displayed_view (view, i) {
-               if (!view->win) {
-                       view->win = newwin(view->height, 0, offset, 0);
-                       if (!view->win)
+               if (!display_win[i]) {
+                       display_win[i] = newwin(view->height, view->width, offset, 0);
+                       if (!display_win[i])
                                die("Failed to create %s view", view->name);
 
-                       scrollok(view->win, FALSE);
+                       scrollok(display_win[i], FALSE);
 
-                       view->title = newwin(1, 0, offset + view->height, 0);
-                       if (!view->title)
+                       display_title[i] = newwin(1, view->width, offset + view->height, 0);
+                       if (!display_title[i])
                                die("Failed to create title window");
 
                } else {
-                       wresize(view->win, view->height, view->width);
-                       mvwin(view->win,   offset, 0);
-                       mvwin(view->title, offset + view->height, 0);
+                       wresize(display_win[i], view->height, view->width);
+                       mvwin(display_win[i],   offset, 0);
+                       mvwin(display_title[i], offset + view->height, 0);
                }
 
+               view->win = display_win[i];
+
                offset += view->height + 1;
        }
 }
@@ -2768,49 +1869,56 @@ redraw_display(bool clear)
  * Option management
  */
 
-static void
-toggle_enum_option_do(unsigned int *opt, const char *help,
-                     const struct enum_map *map, size_t size)
-{
-       *opt = (*opt + 1) % size;
-       redraw_display(FALSE);
-       report("Displaying %s %s", enum_name(map[*opt]), help);
-}
-
-#define toggle_enum_option(opt, help, map) \
-       toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
-
-#define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
-#define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
-
-static void
-toggle_view_option(bool *option, const char *help)
-{
-       *option = !*option;
-       redraw_display(FALSE);
-       report("%sabling %s", *option ? "En" : "Dis", help);
-}
+#define TOGGLE_MENU \
+       TOGGLE_(LINENO,    '.', "line numbers",      &opt_line_number, NULL) \
+       TOGGLE_(DATE,      'D', "dates",             &opt_date,   date_map) \
+       TOGGLE_(AUTHOR,    'A', "author names",      &opt_author, author_map) \
+       TOGGLE_(REV_GRAPH, 'g', "revision graph",    &opt_rev_graph, NULL) \
+       TOGGLE_(REFS,      'F', "reference display", &opt_show_refs, NULL)
 
 static void
-open_option_menu(void)
-{
+toggle_option(enum request request)
+{
+       const struct {
+               enum request request;
+               const struct enum_map *map;
+               size_t map_size;
+       } data[] = {            
+#define TOGGLE_(id, key, help, value, map) { REQ_TOGGLE_ ## id, map, ARRAY_SIZE(map) },
+               TOGGLE_MENU
+#undef TOGGLE_
+       };
        const struct menu_item menu[] = {
-               { '.', "line numbers", &opt_line_number },
-               { 'D', "date display", &opt_date },
-               { 'A', "author display", &opt_author },
-               { 'g', "revision graph display", &opt_rev_graph },
-               { 'F', "reference display", &opt_show_refs },
+#define TOGGLE_(id, key, help, value, map) { key, help, value },
+               TOGGLE_MENU
+#undef TOGGLE_
                { 0 }
        };
-       int selected = 0;
+       int i = 0;
 
-       if (prompt_menu("Toggle option", menu, &selected)) {
-               if (menu[selected].data == &opt_date)
-                       toggle_date();
-               else if (menu[selected].data == &opt_author)
-                       toggle_author();
-               else
-                       toggle_view_option(menu[selected].data, menu[selected].text);
+       if (request == REQ_OPTIONS) {
+               if (!prompt_menu("Toggle option", menu, &i))
+                       return;
+       } else {
+               while (i < ARRAY_SIZE(data) && data[i].request != request)
+                       i++;
+               if (i >= ARRAY_SIZE(data))
+                       die("Invalid request (%d)", request);
+       }
+
+       if (data[i].map != NULL) {
+               unsigned int *opt = menu[i].data;
+
+               *opt = (*opt + 1) % data[i].map_size;
+               redraw_display(FALSE);
+               report("Displaying %s %s", enum_name(data[i].map[*opt]), menu[i].text);
+
+       } else {
+               bool *option = menu[i].data;
+
+               *option = !*option;
+               redraw_display(FALSE);
+               report("%sabling %s", *option ? "En" : "Dis", menu[i].text);
        }
 }
 
@@ -3825,27 +2933,12 @@ view_driver(struct view *view, enum request request)
                break;
 
        case REQ_OPTIONS:
-               open_option_menu();
-               break;
-
        case REQ_TOGGLE_LINENO:
-               toggle_view_option(&opt_line_number, "line numbers");
-               break;
-
        case REQ_TOGGLE_DATE:
-               toggle_date();
-               break;
-
        case REQ_TOGGLE_AUTHOR:
-               toggle_author();
-               break;
-
        case REQ_TOGGLE_REV_GRAPH:
-               toggle_view_option(&opt_rev_graph, "revision graph display");
-               break;
-
        case REQ_TOGGLE_REFS:
-               toggle_view_option(&opt_show_refs, "reference display");
+               toggle_option(request);
                break;
 
        case REQ_TOGGLE_SORT_FIELD:
@@ -4054,7 +3147,7 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno)
        if (opt_line_number && draw_lineno(view, lineno))
                return TRUE;
 
-       draw_text(view, line->type, line->data, TRUE);
+       draw_text(view, line->type, line->data);
        return TRUE;
 }
 
@@ -4678,7 +3771,7 @@ tree_draw(struct view *view, struct line *line, unsigned int lineno)
        struct tree_entry *entry = line->data;
 
        if (line->type == LINE_TREE_HEAD) {
-               if (draw_text(view, line->type, "Directory path /", TRUE))
+               if (draw_text(view, line->type, "Directory path /"))
                        return TRUE;
        } else {
                if (draw_mode(view, entry->mode))
@@ -4690,8 +3783,8 @@ tree_draw(struct view *view, struct line *line, unsigned int lineno)
                if (opt_date && draw_date(view, &entry->time))
                        return TRUE;
        }
-       if (draw_text(view, line->type, entry->name, TRUE))
-               return TRUE;
+
+       draw_text(view, line->type, entry->name);
        return TRUE;
 }
 
@@ -5180,7 +4273,7 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno)
        if (draw_lineno(view, lineno))
                return TRUE;
 
-       draw_text(view, LINE_DEFAULT, blame->text, TRUE);
+       draw_text(view, LINE_DEFAULT, blame->text);
        return TRUE;
 }
 
@@ -5398,7 +4491,7 @@ branch_draw(struct view *view, struct line *line, unsigned int lineno)
        if (opt_author && draw_author(view, branch->author))
                return TRUE;
 
-       draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
+       draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name);
        return TRUE;
 }
 
@@ -5867,13 +4960,13 @@ status_draw(struct view *view, struct line *line, unsigned int lineno)
                static char buf[] = { '?', ' ', ' ', ' ', 0 };
 
                buf[0] = status->status;
-               if (draw_text(view, line->type, buf, TRUE))
+               if (draw_text(view, line->type, buf))
                        return TRUE;
                type = LINE_DEFAULT;
                text = status->new.name;
        }
 
-       draw_text(view, type, text, TRUE);
+       draw_text(view, type, text);
        return TRUE;
 }
 
@@ -6825,17 +5918,17 @@ main_draw(struct view *view, struct line *line, unsigned int lineno)
                        else
                                type = LINE_MAIN_REF;
 
-                       if (draw_text(view, type, "[", TRUE) ||
-                           draw_text(view, type, ref->name, TRUE) ||
-                           draw_text(view, type, "]", TRUE))
+                       if (draw_text(view, type, "[") ||
+                           draw_text(view, type, ref->name) ||
+                           draw_text(view, type, "]"))
                                return TRUE;
 
-                       if (draw_text(view, LINE_DEFAULT, " ", TRUE))
+                       if (draw_text(view, LINE_DEFAULT, " "))
                                return TRUE;
                }
        }
 
-       draw_text(view, LINE_DEFAULT, commit->title, TRUE);
+       draw_text(view, LINE_DEFAULT, commit->title);
        return TRUE;
 }
 
@@ -7107,7 +6200,7 @@ init_display(void)
                init_colors();
 
        getmaxyx(stdscr, y, x);
-       status_win = newwin(1, 0, y - 1, 0);
+       status_win = newwin(1, x, y - 1, 0);
        if (!status_win)
                die("Failed to create status window");