chiark / gitweb /
Improve documentation
[tig] / tig.c
diff --git a/tig.c b/tig.c
index aa62a8f6f3805a381c2a9c0482de088fb603ac1e..dea3a840278ee094c2c46bac8079e7523dcb50ed 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -38,6 +38,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 #include <time.h>
 
 #include <curses.h>
@@ -82,6 +83,7 @@ enum request {
        REQ_VIEW_DIFF,
        REQ_VIEW_LOG,
        REQ_VIEW_HELP,
+       REQ_VIEW_PAGER,
 
        REQ_ENTER,
        REQ_QUIT,
@@ -179,9 +181,7 @@ static int opt_line_number  = FALSE;
 static int opt_num_interval    = NUMBER_INTERVAL;
 static enum request opt_request = REQ_VIEW_MAIN;
 static char opt_cmd[SIZEOF_CMD]        = "";
-
-char ref_head[SIZEOF_REF]      = "HEAD";
-char ref_commit[SIZEOF_REF]    = "HEAD";
+static FILE *opt_pipe          = NULL;
 
 /* Returns the index of log or diff command or -1 to exit. */
 static int
@@ -203,7 +203,7 @@ parse_options(int argc, char *argv[])
                    !strcmp(opt, "diff")) {
                        opt_request = opt[0] == 'l'
                                    ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
-                       return i;
+                       break;
                }
 
                /**
@@ -259,26 +259,66 @@ parse_options(int argc, char *argv[])
 
                /**
                 * \--::
-                *      End of tig options. Useful when specifying commands
+                *      End of tig(1) options. Useful when specifying commands
                 *      for the main view. Example:
                 *
-                *              $ tig -- --pretty=raw tag-1.0..HEAD
+                *              $ tig -- --since=1.month
                 **/
-               if (!strcmp(opt, "--"))
-                       return i + 1;
+               if (!strcmp(opt, "--")) {
+                       i++;
+                       break;
+               }
 
                 /* Make stuff like:
-                 *
+                *
                 *      $ tig tag-1.0..HEAD
                 *
                 * work.
                 */
                if (opt[0] && opt[0] != '-')
-                       return i;
+                       break;
 
                die("unknown command '%s'", opt);
        }
 
+       /**
+        * Pager mode
+        * ~~~~~~~~~~
+        * If stdin is a pipe, any log or diff options will be ignored and the
+        * pager view will be opened loading data from stdin. The pager mode
+        * can be used for colorizing output from various git commands.
+        *
+        * Example on how to colorize the output of git-show(1):
+        *
+        *      $ git show | tig
+        **/
+       if (!isatty(STDIN_FILENO)) {
+               opt_request = REQ_VIEW_PAGER;
+               opt_pipe = stdin;
+
+       } else if (i < argc) {
+               size_t buf_size;
+
+               /* XXX: This is vulnerable to the user overriding options
+                * required for the main view parser. */
+               if (opt_request == REQ_VIEW_MAIN)
+                       string_copy(opt_cmd, "git log --stat --pretty=raw");
+               else
+                       string_copy(opt_cmd, "git");
+               buf_size = strlen(opt_cmd);
+
+               while (buf_size < sizeof(opt_cmd) && i < argc) {
+                       opt_cmd[buf_size++] = ' ';
+                       buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
+               }
+
+               if (buf_size >= sizeof(opt_cmd))
+                       die("command too long");
+
+               opt_cmd[buf_size] = 0;
+
+       }
+
        return i;
 }
 
@@ -424,9 +464,10 @@ LINE(DIFF_RENAME,  "rename ",              COLOR_YELLOW,   COLOR_DEFAULT,  0), \
 LINE(DIFF_SIM,    "similarity ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
 LINE(DIFF_DISSIM,  "dissimilarity ",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
 /* Pretty print commit header */ \
-LINE(AUTHOR,      "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
-LINE(MERGE,       "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
-LINE(DATE,        "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
+LINE(PP_AUTHOR,           "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
+LINE(PP_MERGE,    "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
+LINE(PP_DATE,     "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
+LINE(PP_COMMIT,           "Commit: ",          COLOR_GREEN,    COLOR_DEFAULT,  0), \
 /* Raw commit header */ \
 LINE(COMMIT,      "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
 LINE(PARENT,      "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
@@ -519,7 +560,7 @@ init_colors(void)
 
 struct view {
        const char *name;       /* View name */
-       const char *defcmd;     /* Default command line */
+       const char *cmdfmt;     /* Default command line format */
        char *id;               /* Points to either of ref_{head,commit} */
        size_t objsize;         /* Size of objects in the line index */
 
@@ -530,9 +571,8 @@ struct view {
        } *ops;
 
        char cmd[SIZEOF_CMD];   /* Command buffer */
-       char ref[SIZEOF_REF];   /* Hovered Commit reference */
-       /* The view reference that describes the content of this view. */
-       char vref[SIZEOF_REF];
+       char ref[SIZEOF_REF];   /* Hovered commit reference */
+       char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
 
        WINDOW *win;
        WINDOW *title;
@@ -567,11 +607,15 @@ static struct view_ops main_ops;
 #define HELP_CMD \
        "man tig 2> /dev/null"
 
+char ref_head[SIZEOF_REF]      = "HEAD";
+char ref_commit[SIZEOF_REF]    = "HEAD";
+
 static struct view views[] = {
-       { "main",  MAIN_CMD,   ref_head,    sizeof(struct commit), &main_ops },
-       { "diff",  DIFF_CMD,   ref_commit,  sizeof(char),          &pager_ops },
-       { "log",   LOG_CMD,    ref_head,    sizeof(char),          &pager_ops },
-       { "help",  HELP_CMD,   ref_head,    sizeof(char),          &pager_ops },
+       { "main",  MAIN_CMD,  ref_head,   sizeof(struct commit), &main_ops },
+       { "diff",  DIFF_CMD,  ref_commit, sizeof(char),          &pager_ops },
+       { "log",   LOG_CMD,   ref_head,   sizeof(char),          &pager_ops },
+       { "help",  HELP_CMD,  ref_head,   sizeof(char),          &pager_ops },
+       { "pager", "cat",     ref_head,   sizeof(char),          &pager_ops },
 };
 
 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
@@ -669,7 +713,12 @@ update_view_title(struct view *view)
        wmove(view->title, 0, 0);
 
        /* [main] ref: 334b506... - commit 6 of 4383 (0%) */
-       wprintw(view->title, "[%s] ref: %s", view->name, view->ref);
+
+       if (*view->ref)
+               wprintw(view->title, "[%s] ref: %s", view->name, view->ref);
+       else
+               wprintw(view->title, "[%s]", view->name);
+
        if (view->lines) {
                char *type = view == VIEW(REQ_VIEW_MAIN) ? "commit" : "line";
 
@@ -811,7 +860,7 @@ move_view(struct view *view, enum request request)
                report("Already on first line");
                return;
 
-       } else if (steps >= 0 && view->lineno + 1 == view->lines) {
+       } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
                report("Already on last line");
                return;
        }
@@ -870,12 +919,19 @@ begin_update(struct view *view)
                string_copy(view->cmd, opt_cmd);
                opt_cmd[0] = 0;
        } else {
-               if (snprintf(view->cmd, sizeof(view->cmd), view->defcmd,
+               if (snprintf(view->cmd, sizeof(view->cmd), view->cmdfmt,
                             id, id, id) >= sizeof(view->cmd))
                        return FALSE;
        }
 
-       view->pipe = popen(view->cmd, "r");
+       /* Special case for the pager view. */
+       if (opt_pipe) {
+               view->pipe = opt_pipe;
+               opt_pipe = NULL;
+       } else {
+               view->pipe = popen(view->cmd, "r");
+       }
+
        if (!view->pipe)
                return FALSE;
 
@@ -884,6 +940,7 @@ begin_update(struct view *view)
        view->offset = 0;
        view->lines  = 0;
        view->lineno = 0;
+       string_copy(view->vid, id);
 
        if (view->line) {
                int i;
@@ -991,10 +1048,19 @@ end:
        return FALSE;
 }
 
+enum open_flags {
+       OPEN_DEFAULT = 0,       /* Use default view switching. */
+       OPEN_SPLIT = 1,         /* Split current view. */
+       OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
+       OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
+};
+
 static void
-switch_view(struct view *prev, enum request request,
-           bool backgrounded, bool split)
+open_view(struct view *prev, enum request request, enum open_flags flags)
 {
+       bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
+       bool split = !!(flags & OPEN_SPLIT);
+       bool reload = !!(flags & OPEN_RELOAD);
        struct view *view = VIEW(request);
        struct view *displayed;
        int nviews;
@@ -1012,12 +1078,12 @@ switch_view(struct view *prev, enum request request,
                }
        }
 
-       if (view == prev && nviews == 1) {
+       if (view == prev && nviews == 1 && !reload) {
                report("Already in %s view", view->name);
                return;
        }
 
-       if (strcmp(view->vref, view->id) &&
+       if (strcmp(view->vid, view->id) &&
            !begin_update(view)) {
                report("Failed to load %s view", view->name);
                return;
@@ -1096,10 +1162,15 @@ view_driver(struct view *view, enum request request)
        case REQ_VIEW_DIFF:
        case REQ_VIEW_LOG:
        case REQ_VIEW_HELP:
-               switch_view(view, request, FALSE, FALSE);
+       case REQ_VIEW_PAGER:
+               open_view(view, request, OPEN_DEFAULT);
                break;
 
        case REQ_ENTER:
+               if (!view->lines) {
+                       report("Nothing to enter");
+                       break;
+               }
                return view->ops->enter(view);
 
        case REQ_VIEW_NEXT:
@@ -1124,7 +1195,7 @@ view_driver(struct view *view, enum request request)
                break;
 
        case REQ_PROMPT:
-               switch_view(view, opt_request, FALSE, FALSE);
+               open_view(view, opt_request, OPEN_RELOAD);
                break;
 
        case REQ_STOP_LOADING:
@@ -1179,11 +1250,17 @@ pager_draw(struct view *view, unsigned int lineno)
        type = get_line_type(line);
 
        if (view->offset + lineno == view->lineno) {
-               if (type == LINE_COMMIT) {
+               switch (type) {
+               case LINE_COMMIT:
                        string_copy(view->ref, line + 7);
                        string_copy(ref_commit, view->ref);
+                       break;
+               case LINE_PP_COMMIT:
+                       string_copy(view->ref, line + 8);
+                       string_copy(ref_commit, view->ref);
+               default:
+                       break;
                }
-
                type = LINE_CURSOR;
        }
 
@@ -1257,7 +1334,7 @@ pager_enter(struct view *view)
        char *line = view->line[view->lineno];
 
        if (get_line_type(line) == LINE_COMMIT) {
-               switch_view(view, REQ_VIEW_DIFF, FALSE, FALSE);
+               open_view(view, REQ_VIEW_DIFF, OPEN_DEFAULT);
        }
 
        return TRUE;
@@ -1288,6 +1365,7 @@ main_draw(struct view *view, unsigned int lineno)
 
        if (view->offset + lineno == view->lineno) {
                string_copy(view->ref, commit->id);
+               string_copy(ref_commit, view->ref);
                type = LINE_CURSOR;
        } else {
                type = LINE_MAIN_COMMIT;
@@ -1410,7 +1488,7 @@ main_read(struct view *view, char *line)
 static bool
 main_enter(struct view *view)
 {
-       switch_view(view, REQ_VIEW_DIFF, TRUE, TRUE);
+       open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT | OPEN_BACKGROUNDED);
        return TRUE;
 }
 
@@ -1469,7 +1547,16 @@ init_display(void)
 {
        int x, y;
 
-       initscr();      /* Initialize the curses library */
+       /* Initialize the curses library */
+       if (isatty(STDIN_FILENO)) {
+               initscr();
+       } else {
+               /* Leave stdin and stdout alone when acting as a pager. */
+               FILE *io = fopen("/dev/tty", "r+");
+
+               newterm(NULL, io, io);
+       }
+
        nonl();         /* Tell curses not to do NL->CR/NL on output */
        cbreak();       /* Take input chars one at a time, no wait for \n */
        noecho();       /* Don't echo input */
@@ -1531,28 +1618,6 @@ main(int argc, char *argv[])
        if (git_arg < 0)
                return 0;
 
-       if (git_arg < argc) {
-               size_t buf_size;
-
-               /* XXX: This is vulnerable to the user overriding options
-                * required for the main view parser. */
-               if (opt_request == REQ_VIEW_MAIN)
-                       string_copy(opt_cmd, "git log --stat --pretty=raw");
-               else
-                       string_copy(opt_cmd, "git");
-               buf_size = strlen(opt_cmd);
-
-               while (buf_size < sizeof(opt_cmd) && git_arg < argc) {
-                       opt_cmd[buf_size++] = ' ';
-                       buf_size = sq_quote(opt_cmd, buf_size, argv[git_arg++]);
-               }
-
-               if (buf_size >= sizeof(opt_cmd))
-                       die("command too long");
-
-               opt_cmd[buf_size] = 0;
-       }
-
        request = opt_request;
 
        init_display();
@@ -1570,13 +1635,21 @@ main(int argc, char *argv[])
                request = get_request(key);
 
                if (request == REQ_PROMPT) {
+                       report(":");
+                       /* Temporarily switch to line-oriented and echoed
+                        * input. */
                        nocbreak();
                        echo();
-                       report(":");
-                       if (wgetnstr(status_win, opt_cmd, sizeof(opt_cmd)) == OK)
-                               die("%s", opt_cmd);
-                       cbreak();       /* Take input chars one at a time, no wait for \n */
-                       noecho();       /* Don't echo input */
+
+                       if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
+                               memcpy(opt_cmd, "git ", 4);
+                               opt_request = REQ_VIEW_PAGER;
+                       } else {
+                               request = ERR;
+                       }
+
+                       noecho();
+                       cbreak();
                }
        }