Code

Fix regression in handling of data for non-UTF-8 locales
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index fdf1dc6c56b01b96118058a7dda67facea50ddbc..b16fe244d0fd2d9b26d4208fd99c9ffaecf4b338 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
+/* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
 #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 <time.h>
+#include <fcntl.h>
 
 #include <regex.h>
 
@@ -143,7 +146,6 @@ enum format_flags {
        FORMAT_NONE             /* No replacement should be performed. */
 };
 
-static bool format_command(char dst[], const char *src[], enum format_flags flags);
 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
 
 struct int_map {
@@ -269,48 +271,6 @@ suffixcmp(const char *str, int slen, const char *suffix)
        return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
 }
 
-/* Shell quoting
- *
- * NOTE: The following is a slightly modified copy of the git project's shell
- * quoting routines found in the quote.c file.
- *
- * Help to copy the thing properly quoted for the shell safety.  any single
- * quote is replaced with '\'', any exclamation point is replaced with '\!',
- * and the whole thing is enclosed in a
- *
- * E.g.
- *  original     sq_quote     result
- *  name     ==> name      ==> 'name'
- *  a b      ==> a b       ==> 'a b'
- *  a'b      ==> a'\''b    ==> 'a'\''b'
- *  a!b      ==> a'\!'b    ==> 'a'\!'b'
- */
-
-static size_t
-sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
-{
-       char c;
-
-#define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
-
-       BUFPUT('\'');
-       while ((c = *src++)) {
-               if (c == '\'' || c == '!') {
-                       BUFPUT('\'');
-                       BUFPUT('\\');
-                       BUFPUT(c);
-                       BUFPUT('\'');
-               } else {
-                       BUFPUT(c);
-               }
-       }
-       BUFPUT('\'');
-
-       if (bufsize < SIZEOF_STR)
-               buf[bufsize] = 0;
-
-       return bufsize;
-}
 
 static bool
 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
@@ -358,20 +318,26 @@ enum io_type {
 struct io {
        enum io_type type;      /* The requested type of pipe. */
        const char *dir;        /* Directory from which to execute. */
-       FILE *pipe;             /* Pipe for reading or writing. */
+       pid_t pid;              /* Pipe for reading or writing. */
+       int pipe;               /* Pipe end for reading or writing. */
        int error;              /* Error status. */
-       char sh[SIZEOF_STR];    /* Shell command buffer. */
-       char *buf;              /* Read/write buffer. */
+       const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
+       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
 reset_io(struct io *io)
 {
-       io->pipe = NULL;
-       io->buf = NULL;
-       io->bufalloc = 0;
+       io->pipe = -1;
+       io->pid = 0;
+       io->buf = io->bufpos = NULL;
+       io->bufalloc = io->bufsize = 0;
        io->error = 0;
+       io->eof = 0;
 }
 
 static void
@@ -387,57 +353,107 @@ init_io_rd(struct io *io, const char *argv[], const char *dir,
                enum format_flags flags)
 {
        init_io(io, dir, IO_RD);
-       return format_command(io->sh, argv, flags);
+       return format_argv(io->argv, argv, flags);
 }
 
 static bool
-init_io_fd(struct io *io, FILE *pipe)
+io_open(struct io *io, const char *name)
 {
        init_io(io, NULL, IO_FD);
-       io->pipe = pipe;
-       return io->pipe != NULL;
+       io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
+       return io->pipe != -1;
+}
+
+static bool
+kill_io(struct io *io)
+{
+       return kill(io->pid, SIGKILL) != -1;
 }
 
 static bool
 done_io(struct io *io)
 {
+       pid_t pid = io->pid;
+
+       if (io->pipe != -1)
+               close(io->pipe);
        free(io->buf);
-       if (io->type == IO_FD)
-               fclose(io->pipe);
-       else if (io->type == IO_RD || io->type == IO_WR)
-               pclose(io->pipe);
        reset_io(io);
+
+       while (pid > 0) {
+               int status;
+               pid_t waiting = waitpid(pid, &status, 0);
+
+               if (waiting < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       report("waitpid failed (%s)", strerror(errno));
+                       return FALSE;
+               }
+
+               return waiting == pid &&
+                      !WIFSIGNALED(status) &&
+                      WIFEXITED(status) &&
+                      !WEXITSTATUS(status);
+       }
+
        return TRUE;
 }
 
 static bool
 start_io(struct io *io)
 {
-       char buf[SIZEOF_STR * 2];
-       size_t bufpos = 0;
+       int pipefds[2] = { -1, -1 };
 
        if (io->type == IO_FD)
                return TRUE;
 
-       if (io->dir && *io->dir &&
-           !string_format_from(buf, &bufpos, "cd %s;", io->dir))
+       if ((io->type == IO_RD || io->type == IO_WR) &&
+           pipe(pipefds) < 0)
                return FALSE;
 
-       if (!string_format_from(buf, &bufpos, "%s", io->sh))
-               return FALSE;
+       if ((io->pid = fork())) {
+               if (pipefds[!(io->type == IO_WR)] != -1)
+                       close(pipefds[!(io->type == IO_WR)]);
+               if (io->pid != -1) {
+                       io->pipe = pipefds[!!(io->type == IO_WR)];
+                       return TRUE;
+               }
+
+       } else {
+               if (io->type != IO_FG) {
+                       int devnull = open("/dev/null", O_RDWR);
+                       int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
+                       int writefd = io->type == IO_RD ? 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 (io->dir && *io->dir && chdir(io->dir) == -1)
+                       die("Failed to change directory: %s", strerror(errno));
 
-       if (io->type == IO_FG || io->type == IO_BG)
-               return system(buf) == 0;
+               execvp(io->argv[0], (char *const*) io->argv);
+               die("Failed to execute program: %s", strerror(errno));
+       }
 
-       io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
-       return io->pipe != NULL;
+       if (pipefds[!!(io->type == IO_WR)] != -1)
+               close(pipefds[!!(io->type == IO_WR)]);
+       return FALSE;
 }
 
 static bool
 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
 {
        init_io(io, dir, type);
-       if (!format_command(io->sh, argv, FORMAT_NONE))
+       if (!format_argv(io->argv, argv, FORMAT_NONE))
                return FALSE;
        return start_io(io);
 }
@@ -454,7 +470,7 @@ run_io_bg(const char **argv)
        struct io io = {};
 
        init_io(&io, NULL, IO_BG);
-       if (!format_command(io.sh, argv, FORMAT_NONE))
+       if (!format_argv(io.argv, argv, FORMAT_NONE))
                return FALSE;
        return run_io_do(&io);
 }
@@ -465,7 +481,7 @@ run_io_fg(const char **argv, const char *dir)
        struct io io = {};
 
        init_io(&io, dir, IO_FG);
-       if (!format_command(io.sh, argv, FORMAT_NONE))
+       if (!format_argv(io.argv, argv, FORMAT_NONE))
                return FALSE;
        return run_io_do(&io);
 }
@@ -479,7 +495,7 @@ run_io_rd(struct io *io, const char **argv, enum format_flags flags)
 static bool
 io_eof(struct io *io)
 {
-       return feof(io->pipe);
+       return io->eof;
 }
 
 static int
@@ -494,34 +510,82 @@ io_strerror(struct io *io)
        return strerror(io->error);
 }
 
-static size_t
-io_read(struct io *io, void *buf, size_t bufsize)
+static bool
+io_can_read(struct io *io)
 {
-       size_t readsize = fread(buf, 1, bufsize, io->pipe);
+       struct timeval tv = { 0, 500 };
+       fd_set fds;
 
-       if (ferror(io->pipe))
-               io->error = errno;
+       FD_ZERO(&fds);
+       FD_SET(io->pipe, &fds);
 
-       return readsize;
+       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);
 }
 
 static char *
-io_gets(struct io *io)
+io_get(struct io *io, int c, bool can_read)
 {
+       char *eol;
+       ssize_t readsize;
+
        if (!io->buf) {
-               io->buf = malloc(BUFSIZ);
+               io->buf = io->bufpos = malloc(BUFSIZ);
                if (!io->buf)
                        return NULL;
                io->bufalloc = BUFSIZ;
+               io->bufsize = 0;
        }
 
-       if (!fgets(io->buf, io->bufalloc, io->pipe)) {
-               if (ferror(io->pipe))
-                       io->error = errno;
-               return NULL;
-       }
+       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;
+               }
 
-       return io->buf;
+               if (!can_read)
+                       return NULL;
+
+               if (io->bufsize > 0 && io->bufpos > io->buf)
+                       memmove(io->buf, io->bufpos, io->bufsize);
+
+               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
@@ -530,9 +594,15 @@ io_write(struct io *io, const void *buf, size_t bufsize)
        size_t written = 0;
 
        while (!io_error(io) && written < bufsize) {
-               written += fwrite(buf + written, 1, bufsize - written, io->pipe);
-               if (ferror(io->pipe))
+               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;
@@ -547,9 +617,9 @@ run_io_buf(const char **argv, char buf[], size_t bufsize)
        if (!run_io_rd(&io, argv, FORMAT_NONE))
                return FALSE;
 
-       io.buf = buf;
+       io.buf = io.bufpos = buf;
        io.bufalloc = bufsize;
-       error = !io_gets(&io) && io_error(&io);
+       error = !io_get(&io, '\n', TRUE) && io_error(&io);
        io.buf = NULL;
 
        return done_io(&io) || error;
@@ -727,7 +797,7 @@ parse_options(int argc, const char *argv[], const char ***run_argv)
        bool seen_dashdash = FALSE;
        /* XXX: This is vulnerable to the user overriding options
         * required for the main view parser. */
-       const char *custom_argv[SIZEOF_ARG] = {
+       static const char *custom_argv[SIZEOF_ARG] = {
                "git", "log", "--no-color", "--pretty=raw", "--parents",
                        "--topo-order", NULL
        };
@@ -951,6 +1021,7 @@ struct line {
        /* State flags */
        unsigned int selected:1;
        unsigned int dirty:1;
+       unsigned int cleareol:1;
 
        void *data;             /* User data */
 };
@@ -1558,7 +1629,7 @@ load_option_file(const char *path)
        struct io io = {};
 
        /* It's ok that the file doesn't exist. */
-       if (!init_io_fd(&io, fopen(path, "r")))
+       if (!io_open(&io, path))
                return;
 
        config_lineno = 0;
@@ -1654,7 +1725,6 @@ struct view {
        size_t lines;           /* Total number of lines */
        struct line *line;      /* Line index */
        size_t line_alloc;      /* Total number of allocated lines */
-       size_t line_size;       /* Total number of used lines */
        unsigned int digits;    /* Number of digits in the lines member. */
 
        /* Drawing */
@@ -1666,6 +1736,7 @@ struct view {
        struct io io;
        struct io *pipe;
        time_t start_time;
+       time_t update_secs;
 };
 
 struct view_ops {
@@ -1913,17 +1984,18 @@ draw_view_line(struct view *view, unsigned int lineno)
        line = &view->line[view->offset + lineno];
 
        wmove(view->win, lineno, 0);
+       if (line->cleareol)
+               wclrtoeol(view->win);
        view->col = 0;
        view->curline = line;
        view->curtype = LINE_NONE;
        line->selected = FALSE;
+       line->dirty = line->cleareol = 0;
 
        if (selected) {
                set_view_attr(view, LINE_CURSOR);
                line->selected = TRUE;
                view->ops->select(view, line);
-       } else if (line->selected) {
-               wclrtoeol(view->win);
        }
 
        scrollok(view->win, FALSE);
@@ -1940,11 +2012,10 @@ redraw_view_dirty(struct view *view)
        int lineno;
 
        for (lineno = 0; lineno < view->height; lineno++) {
-               struct line *line = &view->line[view->offset + lineno];
-
-               if (!line->dirty)
+               if (view->offset + lineno >= view->lines)
+                       break;
+               if (!view->line[view->offset + lineno].dirty)
                        continue;
-               line->dirty = 0;
                dirty = TRUE;
                if (!draw_view_line(view, lineno))
                        break;
@@ -1993,25 +2064,26 @@ update_view_title(struct view *view)
 
        assert(view_is_displayed(view));
 
-       if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
+       if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
                unsigned int view_lines = view->offset + view->height;
                unsigned int lines = view->lines
                                   ? MIN(view_lines, view->lines) * 100 / view->lines
                                   : 0;
 
-               string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
+               string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
                                   view->ops->type,
                                   view->lineno + 1,
                                   view->lines,
                                   lines);
 
-               if (view->pipe) {
-                       time_t secs = time(NULL) - view->start_time;
+       }
 
-                       /* Three git seconds are a long time ... */
-                       if (secs > 2)
-                               string_format_from(state, &statelen, " %lds", secs);
-               }
+       if (view->pipe) {
+               time_t secs = time(NULL) - view->start_time;
+
+               /* Three git seconds are a long time ... */
+               if (secs > 2)
+                       string_format_from(state, &statelen, " loading %lds", secs);
        }
 
        string_format_from(buf, &bufpos, "[%s]", view->name);
@@ -2025,7 +2097,7 @@ update_view_title(struct view *view)
        }
 
        if (statelen && bufpos < view->width) {
-               string_format_from(buf, &bufpos, " %s", state);
+               string_format_from(buf, &bufpos, "%s", state);
        }
 
        if (view == display[current_view])
@@ -2428,9 +2500,9 @@ reset_view(struct view *view)
        view->offset = 0;
        view->lines  = 0;
        view->lineno = 0;
-       view->line_size = 0;
        view->line_alloc = 0;
        view->vid[0] = 0;
+       view->update_secs = 0;
 }
 
 static void
@@ -2505,31 +2577,6 @@ format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags fl
        return src_argv[argc] == NULL;
 }
 
-static bool
-format_command(char dst[], const char *src_argv[], enum format_flags flags)
-{
-       const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
-       int bufsize = 0;
-       int argc;
-
-       if (!format_argv(dst_argv, src_argv, flags)) {
-               free_argv(dst_argv);
-               return FALSE;
-       }
-
-       for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
-               if (bufsize > 0)
-                       dst[bufsize++] = ' ';
-               bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
-       }
-
-       if (bufsize < SIZEOF_STR)
-               dst[bufsize] = 0;
-       free_argv(dst_argv);
-
-       return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
-}
-
 static void
 end_update(struct view *view, bool force)
 {
@@ -2539,6 +2586,8 @@ end_update(struct view *view, bool force)
                if (!force)
                        return;
        set_nonblocking_input(FALSE);
+       if (force)
+               kill_io(view->pipe);
        done_io(view->pipe);
        view->pipe = NULL;
 }
@@ -2567,7 +2616,7 @@ prepare_update_file(struct view *view, const char *name)
 {
        if (view->pipe)
                end_update(view, TRUE);
-       return init_io_fd(&view->io, fopen(name, "r"));
+       return io_open(&view->io, name);
 }
 
 static bool
@@ -2623,7 +2672,6 @@ realloc_lines(struct view *view, size_t line_size)
 
        view->line = tmp;
        view->line_alloc = alloc;
-       view->line_size = line_size;
        return view->line;
 }
 
@@ -2632,32 +2680,34 @@ update_view(struct view *view)
 {
        char out_buffer[BUFSIZ * 2];
        char *line;
-       /* The number of lines to read. If too low it will cause too much
-        * redrawing (and possible flickering), if too high responsiveness
-        * will suffer. */
-       unsigned long lines = view->height;
-       int redraw_from = -1;
+       /* Clear the view and redraw everything since the tree sorting
+        * might have rearranged things. */
+       bool redraw = view->lines == 0;
+       bool can_read = TRUE;
 
        if (!view->pipe)
                return TRUE;
 
-       /* Only redraw if lines are visible. */
-       if (view->offset + view->height >= view->lines)
-               redraw_from = view->lines - view->offset;
+       if (!io_can_read(view->pipe)) {
+               if (view->lines == 0) {
+                       time_t secs = time(NULL) - view->start_time;
 
-       /* FIXME: This is probably not perfect for backgrounded views. */
-       if (!realloc_lines(view, view->lines + lines))
-               goto alloc_error;
+                       if (secs > view->update_secs) {
+                               if (view->update_secs == 0)
+                                       redraw_view(view);
+                               update_view_title(view);
+                               view->update_secs = secs;
+                       }
+               }
+               return TRUE;
+       }
 
-       while ((line = io_gets(view->pipe))) {
+       for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
                size_t linelen = strlen(line);
 
-               if (linelen)
-                       line[linelen - 1] = 0;
-
                if (opt_iconv != ICONV_NONE) {
                        ICONV_CONST char *inbuf = line;
-                       size_t inlen = linelen;
+                       size_t inlen = linelen + 1;
 
                        char *outbuf = out_buffer;
                        size_t outlen = sizeof(out_buffer);
@@ -2673,22 +2723,20 @@ update_view(struct view *view)
 
                if (!view->ops->read(view, line))
                        goto alloc_error;
-
-               if (lines-- == 1)
-                       break;
        }
 
        {
+               unsigned long lines = view->lines;
                int digits;
 
-               lines = view->lines;
                for (digits = 0; lines; digits++)
                        lines /= 10;
 
                /* Keep the displayed view in sync with line number scaling. */
                if (digits != view->digits) {
                        view->digits = digits;
-                       redraw_from = 0;
+                       if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
+                               redraw = TRUE;
                }
        }
 
@@ -2704,29 +2752,9 @@ update_view(struct view *view)
        if (!view_is_displayed(view))
                return TRUE;
 
-       if (view == VIEW(REQ_VIEW_TREE)) {
-               /* Clear the view and redraw everything since the tree sorting
-                * might have rearranged things. */
-               redraw_view(view);
-
-       } else if (redraw_from >= 0) {
-               /* If this is an incremental update, redraw the previous line
-                * since for commits some members could have changed when
-                * loading the main view. */
-               if (redraw_from > 0)
-                       redraw_from--;
-
-               /* Since revision graph visualization requires knowledge
-                * about the parent commit, it causes a further one-off
-                * needed to be redrawn for incremental updates. */
-               if (redraw_from > 0 && opt_rev_graph)
-                       redraw_from--;
-
-               /* Incrementally draw avoids flickering. */
-               redraw_view_from(view, redraw_from);
-       }
-
-       if (view == VIEW(REQ_VIEW_BLAME))
+       if (redraw)
+               redraw_view_from(view, 0);
+       else
                redraw_view_dirty(view);
 
        /* Update the title _after_ the redraw so that if the redraw picks up a
@@ -2743,11 +2771,16 @@ alloc_error:
 static struct line *
 add_line_data(struct view *view, void *data, enum line_type type)
 {
-       struct line *line = &view->line[view->lines++];
+       struct line *line;
 
+       if (!realloc_lines(view, view->lines + 1))
+               return NULL;
+
+       line = &view->line[view->lines++];
        memset(line, 0, sizeof(*line));
        line->type = type;
        line->data = data;
+       line->dirty = 1;
 
        return line;
 }
@@ -2760,6 +2793,19 @@ add_line_text(struct view *view, const char *text, enum line_type type)
        return data ? add_line_data(view, data, type) : NULL;
 }
 
+static struct line *
+add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
+{
+       char buf[SIZEOF_STR];
+       va_list args;
+
+       va_start(args, fmt);
+       if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
+               buf[0] = 0;
+       va_end(args);
+
+       return buf[0] ? add_line_text(view, buf, type) : NULL;
+}
 
 /*
  * View opening
@@ -2879,7 +2925,7 @@ open_mergetool(const char *file)
 {
        const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
 
-       open_external_viewer(mergetool_argv, NULL);
+       open_external_viewer(mergetool_argv, opt_cdup);
 }
 
 static void
@@ -3240,9 +3286,6 @@ try_add_describe_ref:
        if (bufpos == 0)
                return;
 
-       if (!realloc_lines(view, view->line_size + 1))
-               return;
-
        add_line_text(view, buf, LINE_PP_REFS);
 }
 
@@ -3384,7 +3427,6 @@ static struct view_ops diff_ops = {
 static bool
 help_open(struct view *view)
 {
-       char buf[BUFSIZ];
        int lines = ARRAY_SIZE(req_info) + 2;
        int i;
 
@@ -3419,10 +3461,8 @@ help_open(struct view *view)
                if (!*key)
                        key = "(no key defined)";
 
-               if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
-                       continue;
-
-               add_line_text(view, buf, LINE_DEFAULT);
+               add_line_format(view, LINE_DEFAULT, "    %-25s %s",
+                               key, req_info[i].help);
        }
 
        if (run_requests) {
@@ -3449,11 +3489,8 @@ help_open(struct view *view)
                                                argc ? " " : "", req->argv[argc]))
                                return REQ_NONE;
 
-               if (!string_format(buf, "    %-10s %-14s `%s`",
-                                  keymap_table[req->keymap].name, key, cmd))
-                       continue;
-
-               add_line_text(view, buf, LINE_DEFAULT);
+               add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
+                               keymap_table[req->keymap].name, key, cmd);
        }
 
        return TRUE;
@@ -3532,19 +3569,6 @@ push_tree_stack_entry(const char *name, unsigned long lineno)
 
 #define TREE_UP_FORMAT "040000 tree %s\t.."
 
-static int
-tree_compare_entry(enum line_type type1, const char *name1,
-                  enum line_type type2, const char *name2)
-{
-       if (type1 != type2) {
-               if (type1 == LINE_TREE_DIR)
-                       return -1;
-               return 1;
-       }
-
-       return strcmp(name1, name2);
-}
-
 static const char *
 tree_path(struct line *line)
 {
@@ -3553,14 +3577,20 @@ tree_path(struct line *line)
        return path + SIZEOF_TREE_ATTR;
 }
 
+static int
+tree_compare_entry(struct line *line1, struct line *line2)
+{
+       if (line1->type != line2->type)
+               return line1->type == LINE_TREE_DIR ? -1 : 1;
+       return strcmp(tree_path(line1), tree_path(line2));
+}
+
 static bool
 tree_read(struct view *view, char *text)
 {
        size_t textlen = text ? strlen(text) : 0;
-       char buf[SIZEOF_STR];
-       unsigned long pos;
+       struct line *entry, *line;
        enum line_type type;
-       bool first_read = view->lines == 0;
 
        if (!text)
                return TRUE;
@@ -3570,21 +3600,9 @@ tree_read(struct view *view, char *text)
        type = text[STRING_SIZE("100644 ")] == 't'
             ? LINE_TREE_DIR : LINE_TREE_FILE;
 
-       if (first_read) {
-               /* Add path info line */
-               if (!string_format(buf, "Directory path /%s", opt_path) ||
-                   !realloc_lines(view, view->line_size + 1) ||
-                   !add_line_text(view, buf, LINE_DEFAULT))
-                       return FALSE;
-
-               /* Insert "link" to parent directory. */
-               if (*opt_path) {
-                       if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
-                           !realloc_lines(view, view->line_size + 1) ||
-                           !add_line_text(view, buf, LINE_TREE_DIR))
-                               return FALSE;
-               }
-       }
+       if (view->lines == 0 &&
+           !add_line_format(view, LINE_DEFAULT, "Directory path /%s", opt_path))
+               return FALSE;
 
        /* Strip the path part ... */
        if (*opt_path) {
@@ -3595,36 +3613,32 @@ tree_read(struct view *view, char *text)
                if (pathlen > striplen)
                        memmove(path, path + striplen,
                                pathlen - striplen + 1);
+
+               /* Insert "link" to parent directory. */
+               if (view->lines == 1 &&
+                   !add_line_format(view, LINE_TREE_DIR, TREE_UP_FORMAT, view->ref))
+                       return FALSE;
        }
 
-       /* Skip "Directory ..." and ".." line. */
-       for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
-               struct line *line = &view->line[pos];
-               const char *path1 = tree_path(line);
-               char *path2 = text + SIZEOF_TREE_ATTR;
-               int cmp = tree_compare_entry(line->type, path1, type, path2);
+       entry = add_line_text(view, text, type);
+       if (!entry)
+               return FALSE;
+       text = entry->data;
 
-               if (cmp <= 0)
+       /* Skip "Directory ..." and ".." line. */
+       for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
+               if (tree_compare_entry(line, entry) <= 0)
                        continue;
 
-               text = strdup(text);
-               if (!text)
-                       return FALSE;
-
-               if (view->lines > pos)
-                       memmove(&view->line[pos + 1], &view->line[pos],
-                               (view->lines - pos) * sizeof(*line));
+               memmove(line + 1, line, (entry - line) * sizeof(*entry));
 
-               line = &view->line[pos];
                line->data = text;
                line->type = type;
-               view->lines++;
+               for (; line <= entry; line++)
+                       line->dirty = line->cleareol = 1;
                return TRUE;
        }
 
-       if (!add_line_text(view, text, type))
-               return FALSE;
-
        if (tree_lineno > view->lineno) {
                view->lineno = tree_lineno;
                tree_lineno = 0;
@@ -3807,7 +3821,7 @@ struct blame {
 static bool
 blame_open(struct view *view)
 {
-       if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
+       if (*opt_ref || !io_open(&view->io, opt_file)) {
                if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
                        return FALSE;
        }
@@ -4024,6 +4038,18 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno)
        return TRUE;
 }
 
+static bool
+check_blame_commit(struct blame *blame)
+{
+       if (!blame->commit)
+               report("Commit data not loaded yet");
+       else if (!strcmp(blame->commit->id, NULL_ID))
+               report("No commit exist for the selected line");
+       else
+               return TRUE;
+       return FALSE;
+}
+
 static enum request
 blame_request(struct view *view, enum request request, struct line *line)
 {
@@ -4032,13 +4058,11 @@ blame_request(struct view *view, enum request request, struct line *line)
 
        switch (request) {
        case REQ_VIEW_BLAME:
-               if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
-                       report("Commit ID unknown");
-                       break;
+               if (check_blame_commit(blame)) {
+                       string_copy(opt_ref, blame->commit->id);
+                       open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
                }
-               string_copy(opt_ref, blame->commit->id);
-               open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
-               return request;
+               break;
 
        case REQ_ENTER:
                if (!blame->commit) {
@@ -4172,7 +4196,7 @@ status_get_diff(struct status *file, const char *buf, size_t bufsize)
        const char *new_rev  = buf + 56;
        const char *status   = buf + 97;
 
-       if (bufsize < 99 ||
+       if (bufsize < 98 ||
            old_mode[-1] != ':' ||
            new_mode[-1] != ' ' ||
            old_rev[-1]  != ' ' ||
@@ -4198,8 +4222,7 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
 {
        struct status *file = NULL;
        struct status *unmerged = NULL;
-       char buf[SIZEOF_STR * 4];
-       size_t bufsize = 0;
+       char *buf;
        struct io io = {};
 
        if (!run_io(&io, argv, NULL, IO_RD))
@@ -4207,90 +4230,61 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
 
        add_line_data(view, NULL, type);
 
-       while (!io_eof(&io)) {
-               char *sep;
-               size_t readsize;
+       while ((buf = io_get(&io, 0, TRUE))) {
+               if (!file) {
+                       file = calloc(1, sizeof(*file));
+                       if (!file || !add_line_data(view, file, type))
+                               goto error_out;
+               }
 
-               readsize = io_read(&io, buf + bufsize, sizeof(buf) - bufsize);
-               if (io_error(&io))
-                       break;
-               bufsize += readsize;
+               /* Parse diff info part. */
+               if (status) {
+                       file->status = status;
+                       if (status == 'A')
+                               string_copy(file->old.rev, NULL_ID);
 
-               /* Process while we have NUL chars. */
-               while ((sep = memchr(buf, 0, bufsize))) {
-                       size_t sepsize = sep - buf + 1;
+               } else if (!file->status) {
+                       if (!status_get_diff(file, buf, strlen(buf)))
+                               goto error_out;
 
-                       if (!file) {
-                               if (!realloc_lines(view, view->line_size + 1))
-                                       goto error_out;
+                       buf = io_get(&io, 0, TRUE);
+                       if (!buf)
+                               break;
 
-                               file = calloc(1, sizeof(*file));
-                               if (!file)
-                                       goto error_out;
+                       /* Collapse all 'M'odified entries that follow a
+                        * associated 'U'nmerged entry. */
+                       if (file->status == 'U') {
+                               unmerged = file;
 
-                               add_line_data(view, file, type);
-                       }
+                       } else if (unmerged) {
+                               int collapse = !strcmp(buf, unmerged->new.name);
 
-                       /* Parse diff info part. */
-                       if (status) {
-                               file->status = status;
-                               if (status == 'A')
-                                       string_copy(file->old.rev, NULL_ID);
-
-                       } else if (!file->status) {
-                               if (!status_get_diff(file, buf, sepsize))
-                                       goto error_out;
-
-                               bufsize -= sepsize;
-                               memmove(buf, sep + 1, bufsize);
-
-                               sep = memchr(buf, 0, bufsize);
-                               if (!sep)
-                                       break;
-                               sepsize = sep - buf + 1;
-
-                               /* Collapse all 'M'odified entries that
-                                * follow a associated 'U'nmerged entry.
-                                */
-                               if (file->status == 'U') {
-                                       unmerged = file;
-
-                               } else if (unmerged) {
-                                       int collapse = !strcmp(buf, unmerged->new.name);
-
-                                       unmerged = NULL;
-                                       if (collapse) {
-                                               free(file);
-                                               view->lines--;
-                                               continue;
-                                       }
+                               unmerged = NULL;
+                               if (collapse) {
+                                       free(file);
+                                       view->lines--;
+                                       continue;
                                }
                        }
+               }
 
-                       /* Grab the old name for rename/copy. */
-                       if (!*file->old.name &&
-                           (file->status == 'R' || file->status == 'C')) {
-                               sepsize = sep - buf + 1;
-                               string_ncopy(file->old.name, buf, sepsize);
-                               bufsize -= sepsize;
-                               memmove(buf, sep + 1, bufsize);
-
-                               sep = memchr(buf, 0, bufsize);
-                               if (!sep)
-                                       break;
-                               sepsize = sep - buf + 1;
-                       }
+               /* Grab the old name for rename/copy. */
+               if (!*file->old.name &&
+                   (file->status == 'R' || file->status == 'C')) {
+                       string_ncopy(file->old.name, buf, strlen(buf));
 
-                       /* git-ls-files just delivers a NUL separated
-                        * list of file names similar to the second half
-                        * of the git-diff-* output. */
-                       string_ncopy(file->new.name, buf, sepsize);
-                       if (!*file->old.name)
-                               string_copy(file->old.name, file->new.name);
-                       bufsize -= sepsize;
-                       memmove(buf, sep + 1, bufsize);
-                       file = NULL;
+                       buf = io_get(&io, 0, TRUE);
+                       if (!buf)
+                               break;
                }
+
+               /* git-ls-files just delivers a NUL separated list of
+                * file names similar to the second half of the
+                * git-diff-* output. */
+               string_ncopy(file->new.name, buf, strlen(buf));
+               if (!*file->old.name)
+                       string_copy(file->old.name, file->new.name);
+               file = NULL;
        }
 
        if (io_error(&io)) {
@@ -4338,9 +4332,6 @@ status_open(struct view *view)
 
        reset_view(view);
 
-       if (!realloc_lines(view, view->line_size + 7))
-               return FALSE;
-
        add_line_data(view, NULL, LINE_STAT_HEAD);
        if (is_initial_commit())
                string_copy(status_onbranch, "Initial commit");
@@ -5301,7 +5292,7 @@ prepare_rev_graph(struct rev_graph *graph)
 }
 
 static void
-update_rev_graph(struct rev_graph *graph)
+update_rev_graph(struct view *view, struct rev_graph *graph)
 {
        /* If this is the finalizing update ... */
        if (graph->commit)
@@ -5312,6 +5303,10 @@ update_rev_graph(struct rev_graph *graph)
        if (!graph->prev->commit)
                return;
 
+       if (view->lines > 2)
+               view->line[view->lines - 3].dirty = 1;
+       if (view->lines > 1)
+               view->line[view->lines - 2].dirty = 1;
        draw_rev_graph(graph->prev);
        done_rev_graph(graph->prev->prev);
 }
@@ -5399,7 +5394,7 @@ main_read(struct view *view, char *line)
                                graph->commit = NULL;
                        }
                }
-               update_rev_graph(graph);
+               update_rev_graph(view, graph);
 
                for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
                        clear_rev_graph(&graph_stacks[i]);
@@ -5454,7 +5449,7 @@ main_read(struct view *view, char *line)
                if (!nameend || !emailend)
                        break;
 
-               update_rev_graph(graph);
+               update_rev_graph(view, graph);
                graph = graph->next;
 
                *nameend = *emailend = 0;
@@ -5466,6 +5461,7 @@ main_read(struct view *view, char *line)
                }
 
                string_ncopy(commit->author, ident, strlen(ident));
+               view->line[view->lines - 1].dirty = 1;
 
                /* Parse epoch and timezone */
                if (emailend[1] == ' ') {
@@ -5512,6 +5508,7 @@ main_read(struct view *view, char *line)
                 * shortened titles, etc. */
 
                string_ncopy(commit->title, line, strlen(line));
+               view->line[view->lines - 1].dirty = 1;
        }
 
        return TRUE;
@@ -6287,7 +6284,7 @@ read_properties(struct io *io, const char *separators,
        if (!start_io(io))
                return ERR;
 
-       while (state == OK && (name = io_gets(io))) {
+       while (state == OK && (name = io_get(io, '\n', TRUE))) {
                char *value;
                size_t namelen;
                size_t valuelen;
@@ -6409,7 +6406,7 @@ main(int argc, const char *argv[])
 
        if (request == REQ_VIEW_PAGER || run_argv) {
                if (request == REQ_VIEW_PAGER)
-                       init_io_fd(&VIEW(request)->io, stdin);
+                       io_open(&VIEW(request)->io, "");
                else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
                        die("Failed to format arguments");
                open_view(NULL, request, OPEN_PREPARED);