Code

Merge with master
authorJonas Fonseca <fonseca@diku.dk>
Mon, 30 Oct 2006 02:16:42 +0000 (03:16 +0100)
committerJonas Fonseca <fonseca@diku.dk>
Mon, 30 Oct 2006 02:16:42 +0000 (03:16 +0100)
1  2 
tig.c

diff --combined tig.c
index 1e033c607d041bbec20f272598dca9a6f4d6ef15,3b320e666911805feef2ad5a6ea2d359299f918c..1983375ca03a517a6d1408b2488c8814cc4d5a31
--- 1/tig.c
--- 2/tig.c
+++ b/tig.c
@@@ -12,7 -12,7 +12,7 @@@
   */
  
  #ifndef       VERSION
- #define VERSION       "tig-0.4.git"
+ #define VERSION       "tig-0.5.git"
  #endif
  
  #ifndef DEBUG
@@@ -60,15 -60,6 +60,15 @@@ static size_t utf8_length(const char *s
  #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 */
 +
 +/* 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. */
@@@ -460,6 -451,17 +460,17 @@@ parse_options(int argc, char *argv[]
        for (i = 1; i < argc; i++) {
                char *opt = argv[i];
  
+               if (!strcmp(opt, "log") ||
+                   !strcmp(opt, "diff") ||
+                   !strcmp(opt, "show")) {
+                       opt_request = opt[0] == 'l'
+                                   ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
+                       break;
+               }
+               if (opt[0] && opt[0] != '-')
+                       break;
                if (!strcmp(opt, "-l")) {
                        opt_request = REQ_VIEW_LOG;
                        continue;
                        break;
                }
  
-               if (!strcmp(opt, "log") ||
-                   !strcmp(opt, "diff") ||
-                   !strcmp(opt, "show")) {
-                       opt_request = opt[0] == 'l'
-                                   ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
-                       break;
-               }
-               if (opt[0] && opt[0] != '-')
-                       break;
                die("unknown option '%s'\n\n%s", opt, usage);
        }
  
@@@ -739,9 -730,6 +739,6 @@@ static struct keybinding default_keybin
        { 'g',          REQ_TOGGLE_REV_GRAPH },
        { ':',          REQ_PROMPT },
  
-       /* wgetch() with nodelay() enabled returns ERR when there's no input. */
-       { ERR,          REQ_NONE },
        /* Using the ncurses SIGWINCH handler. */
        { KEY_RESIZE,   REQ_SCREEN_RESIZE },
  };
@@@ -1173,6 -1161,9 +1170,9 @@@ struct view_ops
  static struct view *display[2];
  static unsigned int current_view;
  
+ /* Reading from the prompt? */
+ static bool input_mode = FALSE;
  #define foreach_displayed_view(view, i) \
        for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
  
@@@ -1310,7 -1301,10 +1310,10 @@@ redraw_view_from(struct view *view, in
        }
  
        redrawwin(view->win);
-       wrefresh(view->win);
+       if (input_mode)
+               wnoutrefresh(view->win);
+       else
+               wrefresh(view->win);
  }
  
  static void
@@@ -1370,10 -1364,14 +1373,14 @@@ update_view_title(struct view *view
        else
                wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
  
-       werase(view->title);
        mvwaddnstr(view->title, 0, 0, buf, bufpos);
+       wclrtoeol(view->title);
        wmove(view->title, 0, view->width - 1);
-       wrefresh(view->title);
+       if (input_mode)
+               wnoutrefresh(view->title);
+       else
+               wrefresh(view->title);
  }
  
  static void
@@@ -1876,7 -1874,7 +1883,7 @@@ update_view(struct view *view
  
                        size_t ret;
  
-                       ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
+                       ret = iconv(opt_iconv, (const char **) &inbuf, &inlen, &outbuf, &outlen);
                        if (ret != (size_t) -1) {
                                line = out_buffer;
                                linelen = strlen(out_buffer);
@@@ -1943,7 -1941,6 +1950,7 @@@ alloc_error
        report("Allocation failure");
  
  end:
 +      view->ops->read(view, NULL);
        end_update(view);
        return FALSE;
  }
@@@ -2392,9 -2389,6 +2399,9 @@@ pager_read(struct view *view, char *dat
  {
        struct line *line = &view->line[view->lines];
  
 +      if (!data)
 +              return TRUE;
 +
        line->data = strdup(data);
        if (!line->data)
                return FALSE;
@@@ -2505,7 -2499,7 +2512,7 @@@ tree_compare_entry(enum line_type type1
  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;
  static bool
  tree_enter(struct view *view, struct line *line)
  {
-       enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
+       enum open_flags flags;
        enum request request;
  
        switch (line->type) {
  
                /* Trees and subtrees share the same ID, so they are not not
                 * unique like blobs. */
-               flags |= OPEN_RELOAD;
+               flags = OPEN_RELOAD;
                request = REQ_VIEW_TREE;
                break;
  
        case LINE_TREE_FILE:
+               flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
                request = REQ_VIEW_BLOB;
                break;
  
@@@ -2684,12 -2679,12 +2692,12 @@@ static struct view_ops blob_ops = 
  
  
  /*
 - * Main view backend
 + * Revision graph
   */
  
  struct commit {
        char id[SIZEOF_REV];            /* SHA1 ID. */
-       char title[75];                 /* First line of the commit message. */
+       char title[128];                /* First line of the commit message. */
        char author[75];                /* Author of the commit. */
        struct tm time;                 /* Date from the author ident. */
        struct ref **refs;              /* Repository references. */
        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)
 +{
 +      int i;
 +
 +      /* "Collapse" duplicate parents lines.
 +       *
 +       * FIXME: This needs to also update update the drawn graph but
 +       * for now it just serves as a method for pruning graph lines. */
 +      for (i = 0; i < graph->size; i++)
 +              if (!strncmp(graph->rev[i], 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)
  {
                for (i = 0; i < commit->graph_size; i++)
                        waddch(view->win, commit->graph[i]);
  
 +              waddch(view->win, ' ');
                col += commit->graph_size + 1;
        }
  
  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));
                view->line[view->lines++].data = commit;
                string_copy(commit->id, line);
                commit->refs = get_refs(commit->id);
 -              commit->graph[commit->graph_size++] = ACS_LTEE;
 +              graph->commit = commit;
 +              break;
 +
 +      case LINE_PARENT:
 +              if (commit) {
 +                      line += STRING_SIZE("parent ");
 +                      push_rev_graph(graph->parents, line);
 +              }
                break;
  
        case LINE_AUTHOR:
                if (!commit)
                        break;
  
 +              update_rev_graph(graph);
 +              graph = graph->next;
 +
                if (end) {
                        char *email = end + 1;
  
  
                /* Require titles to start with a non-space character at the
                 * offset used by git log. */
-               /* FIXME: More gracefull handling of titles; append "..." to
-                * shortened titles, etc. */
-               if (strncmp(line, "    ", 4) ||
-                   isspace(line[4]))
+               if (strncmp(line, "    ", 4))
+                       break;
+               line += 4;
+               /* Well, if the title starts with a whitespace character,
+                * try to be forgiving.  Otherwise we end up with no title. */
+               while (isspace(*line))
+                       line++;
+               if (*line == '\0')
                        break;
+               /* FIXME: More graceful handling of titles; append "..." to
+                * shortened titles, etc. */
  
-               string_copy(commit->title, line + 4);
+               string_copy(commit->title, line);
        }
  
        return TRUE;
@@@ -3345,26 -3135,30 +3359,30 @@@ static bool cursed = FALSE
  /* The status window is used for polling keystrokes. */
  static WINDOW *status_win;
  
+ static bool status_empty = TRUE;
  /* Update status and title window. */
  static void
  report(const char *msg, ...)
  {
-       static bool empty = TRUE;
        struct view *view = display[current_view];
  
-       if (!empty || *msg) {
+       if (input_mode)
+               return;
+       if (!status_empty || *msg) {
                va_list args;
  
                va_start(args, msg);
  
-               werase(status_win);
                wmove(status_win, 0, 0);
                if (*msg) {
                        vwprintw(status_win, msg, args);
-                       empty = FALSE;
+                       status_empty = FALSE;
                } else {
-                       empty = TRUE;
+                       status_empty = TRUE;
                }
+               wclrtoeol(status_win);
                wrefresh(status_win);
  
                va_end(args);
@@@ -3434,10 -3228,16 +3452,16 @@@ read_prompt(const char *prompt
                struct view *view;
                int i, key;
  
+               input_mode = TRUE;
                foreach_view (view, i)
                        update_view(view);
  
-               report("%s%.*s", prompt, pos, buf);
+               input_mode = FALSE;
+               mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
+               wclrtoeol(status_win);
                /* Refresh, accept single keystroke of input */
                key = wgetch(status_win);
                switch (key) {
                }
        }
  
-       if (status == CANCEL) {
-               /* Clear the status window */
-               report("");
+       /* Clear the status window */
+       status_empty = FALSE;
+       report("");
+       if (status == CANCEL)
                return NULL;
-       }
  
        buf[pos++] = 0;
  
@@@ -3735,6 -3536,13 +3760,13 @@@ main(int argc, char *argv[]
                /* Refresh, accept single keystroke of input */
                key = wgetch(status_win);
  
+               /* wgetch() with nodelay() enabled returns ERR when there's no
+                * input. */
+               if (key == ERR) {
+                       request = REQ_NONE;
+                       continue;
+               }
                request = get_keybinding(display[current_view]->keymap, key);
  
                /* Some low-level request handling. This keeps access to