Make the rev graph visualization have a one rev look-ahead
[tig] / tig.c
diff --git a/tig.c b/tig.c
index 2431f66..34a6358 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -60,10 +60,16 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 #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_REVGRAPH        19      /* Size of revision ancestry graphics. */
 
-/* Size of rev graph with no  "padding" columns */
-#define SIZEOF_REVITEMS        (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
+/* Revision graph */
+
+#define REVGRAPH_INIT  'I'
+#define REVGRAPH_MERGE 'M'
+#define REVGRAPH_BRANCH        '+'
+#define REVGRAPH_COMMIT        '*'
+#define REVGRAPH_LINE  '|'
+
+#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)
@@ -1922,6 +1928,7 @@ alloc_error:
        report("Allocation failure");
 
 end:
+       view->ops->read(view, NULL);
        end_update(view);
        return FALSE;
 }
@@ -2370,6 +2377,9 @@ pager_read(struct view *view, char *data)
 {
        struct line *line = &view->line[view->lines];
 
+       if (!data)
+               return TRUE;
+
        line->data = strdup(data);
        if (!line->data)
                return FALSE;
@@ -2480,7 +2490,7 @@ tree_compare_entry(enum line_type type1, char *name1,
 static bool
 tree_read(struct view *view, char *text)
 {
-       size_t textlen = strlen(text);
+       size_t textlen = text ? strlen(text) : 0;
        char buf[SIZEOF_STR];
        unsigned long pos;
        enum line_type type;
@@ -2659,7 +2669,7 @@ static struct view_ops blob_ops = {
 
 
 /*
- * Main view backend
+ * Revision graph
  */
 
 struct commit {
@@ -2672,6 +2682,193 @@ struct commit {
        size_t graph_size;              /* The width of the graph array. */
 };
 
+/* Size of rev graph with no  "padding" columns */
+#define SIZEOF_REVITEMS        (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
+
+struct rev_graph {
+       struct rev_graph *prev, *next, *parents;
+       char rev[SIZEOF_REVITEMS][SIZEOF_REV];
+       size_t size;
+       struct commit *commit;
+       size_t pos;
+};
+
+/* Parents of the commit being visualized. */
+static struct rev_graph graph_parents[4];
+
+/* The current stack of revisions on the graph. */
+static struct rev_graph graph_stacks[4] = {
+       { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
+       { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
+       { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
+       { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
+};
+
+static inline bool
+graph_parent_is_merge(struct rev_graph *graph)
+{
+       return graph->parents->size > 1;
+}
+
+static inline void
+append_to_rev_graph(struct rev_graph *graph, chtype symbol)
+{
+       struct commit *commit = graph->commit;
+
+       if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
+               commit->graph[commit->graph_size++] = symbol;
+}
+
+static void
+done_rev_graph(struct rev_graph *graph)
+{
+       if (graph_parent_is_merge(graph) &&
+           graph->pos < graph->size - 1 &&
+           graph->next->size == graph->size + graph->parents->size - 1) {
+               size_t i = graph->pos + graph->parents->size - 1;
+
+               graph->commit->graph_size = i * 2;
+               while (i < graph->next->size - 1) {
+                       append_to_rev_graph(graph, ' ');
+                       append_to_rev_graph(graph, '\\');
+                       i++;
+               }
+       }
+
+       graph->size = graph->pos = 0;
+       graph->commit = NULL;
+       memset(graph->parents, 0, sizeof(*graph->parents));
+}
+
+static void
+push_rev_graph(struct rev_graph *graph, char *parent)
+{
+       /* Combine duplicate parents lines. */
+       if (graph->size > 0 &&
+           !strncmp(graph->rev[graph->size - 1], parent, SIZEOF_REV))
+               return;
+
+       if (graph->size < SIZEOF_REVITEMS) {
+               string_ncopy(graph->rev[graph->size++], parent, SIZEOF_REV);
+       }
+}
+
+static chtype
+get_rev_graph_symbol(struct rev_graph *graph)
+{
+       chtype symbol;
+
+       if (graph->parents->size == 0)
+               symbol = REVGRAPH_INIT;
+       else if (graph_parent_is_merge(graph))
+               symbol = REVGRAPH_MERGE;
+       else if (graph->pos >= graph->size)
+               symbol = REVGRAPH_BRANCH;
+       else
+               symbol = REVGRAPH_COMMIT;
+
+       return symbol;
+}
+
+static void
+draw_rev_graph(struct rev_graph *graph)
+{
+       struct rev_filler {
+               chtype separator, line;
+       };
+       enum { DEFAULT, RSHARP, RDIAG, LDIAG };
+       static struct rev_filler fillers[] = {
+               { ' ',  REVGRAPH_LINE },
+               { '`',  '.' },
+               { '\'', ' ' },
+               { '/',  ' ' },
+       };
+       chtype symbol = get_rev_graph_symbol(graph);
+       struct rev_filler *filler;
+       size_t i;
+
+       filler = &fillers[DEFAULT];
+
+       for (i = 0; i < graph->pos; i++) {
+               append_to_rev_graph(graph, filler->line);
+               if (graph_parent_is_merge(graph->prev) &&
+                   graph->prev->pos == i)
+                       filler = &fillers[RSHARP];
+
+               append_to_rev_graph(graph, filler->separator);
+       }
+
+       /* Place the symbol for this revision. */
+       append_to_rev_graph(graph, symbol);
+
+       if (graph->prev->size > graph->size)
+               filler = &fillers[RDIAG];
+       else
+               filler = &fillers[DEFAULT];
+
+       i++;
+
+       for (; i < graph->size; i++) {
+               append_to_rev_graph(graph, filler->separator);
+               append_to_rev_graph(graph, filler->line);
+               if (graph_parent_is_merge(graph->prev) &&
+                   i < graph->prev->pos + graph->parents->size)
+                       filler = &fillers[RSHARP];
+               if (graph->prev->size > graph->size)
+                       filler = &fillers[LDIAG];
+       }
+
+       if (graph->prev->size > graph->size) {
+               append_to_rev_graph(graph, filler->separator);
+               if (filler->line != ' ')
+                       append_to_rev_graph(graph, filler->line);
+       }
+}
+
+/* Prepare the next rev graph */
+static void
+prepare_rev_graph(struct rev_graph *graph)
+{
+       size_t i;
+
+       /* First, traverse all lines of revisions up to the active one. */
+       for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
+               if (!strcmp(graph->rev[graph->pos], graph->commit->id))
+                       break;
+
+               push_rev_graph(graph->next, graph->rev[graph->pos]);
+       }
+
+       /* Interleave the new revision parent(s). */
+       for (i = 0; i < graph->parents->size; i++)
+               push_rev_graph(graph->next, graph->parents->rev[i]);
+
+       /* Lastly, put any remaining revisions. */
+       for (i = graph->pos + 1; i < graph->size; i++)
+               push_rev_graph(graph->next, graph->rev[i]);
+}
+
+static void
+update_rev_graph(struct rev_graph *graph)
+{
+       /* If this is the finalizing update ... */
+       if (graph->commit)
+               prepare_rev_graph(graph);
+
+       /* Graph visualization needs a one rev look-ahead,
+        * so the first update doesn't visualize anything. */
+       if (!graph->prev->commit)
+               return;
+
+       draw_rev_graph(graph->prev);
+       done_rev_graph(graph->prev->prev);
+}
+
+
+/*
+ * Main view backend
+ */
+
 static bool
 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
 {
@@ -2739,6 +2936,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select
                for (i = 0; i < commit->graph_size; i++)
                        waddch(view->win, commit->graph[i]);
 
+               waddch(view->win, ' ');
                col += commit->graph_size + 1;
        }
 
@@ -2779,97 +2977,22 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select
        return TRUE;
 }
 
-
-struct rev_stack {
-       char rev[SIZEOF_REVITEMS][SIZEOF_REV];
-       size_t size;
-};
-
-/* The current stack of revisions on the graph. */
-static struct rev_stack graph_stacks[2];
-static unsigned int graph_stack_no;
-
-/* Parents of the commit being visualized. */
-static struct rev_stack graph_parents;
-
-static void
-push_rev_stack(struct rev_stack *stack, char *parent)
-{
-       fprintf(stderr, " (%s)", parent);
-
-       /* Combine duplicate parents lines. */
-       if (stack->size > 0 &&
-           !strncmp(stack->rev[stack->size - 1], parent, SIZEOF_REV))
-               return;
-
-       if (stack->size < SIZEOF_REVITEMS) {
-               string_ncopy(stack->rev[stack->size++], parent, SIZEOF_REV);
-       }
-}
-
-void
-update_rev_graph(struct commit *commit)
-{
-       struct rev_stack *stack = &graph_stacks[graph_stack_no++ & 1];
-       struct rev_stack *graph = &graph_stacks[graph_stack_no & 1];
-       chtype symbol;
-       size_t stackpos = 0;
-       size_t i;
-
-       // FIXME: Initial commit ... assert(rev_graph_commit == commit);
-       fprintf(stderr, "\n%p <%s> ", graph, commit->id);
-
-       /* First traverse all lines of revisions up to the active one. */
-       for (stackpos = 0; stackpos < stack->size; stackpos++) {
-               if (!strcmp(stack->rev[stackpos], commit->id)) {
-                       while (stackpos + 1 < stack->size &&
-                              !strcmp(stack->rev[stackpos + 1], commit->id))
-                               stackpos++;
-                       break;
-               }
-
-               push_rev_stack(graph, stack->rev[stackpos]);
-               commit->graph[commit->graph_size++] = ACS_VLINE;
-               commit->graph[commit->graph_size++] = ' ';
-       }
-
-       assert(commit->graph_size < ARRAY_SIZE(commit->graph));
-
-       for (i = 0; i < graph_parents.size; i++)
-               push_rev_stack(graph, graph_parents.rev[i]);
-
-       /* Place the symbol for this commit. */
-       if (graph_parents.size == 0)
-               symbol = 'I';
-       else if (graph_parents.size > 1)
-               symbol = 'M';
-       else if (stackpos >= stack->size)
-               symbol = '+';
-       else
-               symbol = '*';
-
-       commit->graph[commit->graph_size++] = symbol;
-
-       stackpos++;
-
-       /* FIXME: Moving branches left and right when collapsing a branch. */
-       while (stackpos < stack->size) {
-               push_rev_stack(graph, stack->rev[stackpos++]);
-               commit->graph[commit->graph_size++] = ' ';
-               commit->graph[commit->graph_size++] = ACS_VLINE;
-       }
-
-       stack->size = graph_parents.size = 0;
-}
-
 /* Reads git log --pretty=raw output and parses it into the commit struct. */
 static bool
 main_read(struct view *view, char *line)
 {
-       enum line_type type = get_line_type(line);
+       static struct rev_graph *graph = graph_stacks;
+       enum line_type type;
        struct commit *commit = view->lines
                              ? view->line[view->lines - 1].data : NULL;
 
+       if (!line) {
+               update_rev_graph(graph);
+               return TRUE;
+       }
+
+       type = get_line_type(line);
+
        switch (type) {
        case LINE_COMMIT:
                commit = calloc(1, sizeof(struct commit));
@@ -2881,13 +3004,13 @@ main_read(struct view *view, char *line)
                view->line[view->lines++].data = commit;
                string_copy(commit->id, line);
                commit->refs = get_refs(commit->id);
-               fprintf(stderr, "\n%p [%s]", &graph_stacks[graph_stack_no], commit->id);
+               graph->commit = commit;
                break;
 
        case LINE_PARENT:
                if (commit) {
                        line += STRING_SIZE("parent ");
-                       push_rev_stack(&graph_parents, line);
+                       push_rev_graph(graph->parents, line);
                }
                break;
 
@@ -2899,7 +3022,8 @@ main_read(struct view *view, char *line)
                if (!commit)
                        break;
 
-               update_rev_graph(commit);
+               update_rev_graph(graph);
+               graph = graph->next;
 
                if (end) {
                        char *email = end + 1;