summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 389378a)
raw | patch | inline | side by side (parent: 389378a)
author | Jonas Fonseca <jonas.fonseca@savoirfairelinux.com> | |
Fri, 11 Dec 2009 22:05:23 +0000 (17:05 -0500) | ||
committer | Jonas Fonseca <fonseca@diku.dk> | |
Mon, 7 Mar 2011 01:45:34 +0000 (20:45 -0500) |
The new renderer is more 'square' but comes with three modes: UTF-8,
ncurses (using chtype graphical characters), and ASCII. The three modes
can be toggled. Enable revision graph rendering by default.
ncurses (using chtype graphical characters), and ASCII. The three modes
can be toggled. Enable revision graph rendering by default.
12 files changed:
.gitignore | patch | blob | history | |
Makefile | patch | blob | history | |
NEWS | patch | blob | history | |
graph.c | [new file with mode: 0644] | patch | blob |
graph.h | [new file with mode: 0644] | patch | blob |
io.c | patch | blob | history | |
io.h | patch | blob | history | |
manual.txt | patch | blob | history | |
test-graph.c | [new file with mode: 0644] | patch | blob |
tig.c | patch | blob | history | |
tig.h | patch | blob | history | |
tigrc.5.txt | patch | blob | history |
diff --git a/.gitignore b/.gitignore
index bd0c3a9d41063499663728b0174a960868def51a..d8dcc0cad25dfe0dea0c89cec275cfce02d35e5f 100644 (file)
--- a/.gitignore
+++ b/.gitignore
tig.spec
tigrc.5
tigmanual.7
+test-graph
*.html
*.o
*.xml
diff --git a/Makefile b/Makefile
index 78131f515223e224935d942ab638886c5654ee54..044348294ab8be34cf37f7ac25a85f3b83d10889 100644 (file)
--- a/Makefile
+++ b/Makefile
LDLIBS ?= -lcurses
CFLAGS ?= -Wall -O2
DFLAGS = -g -DDEBUG -Werror -O0
-PROGS = tig
-SOURCE = tig.c tig.h io.c io.h
+PROGS = tig test-graph
+SOURCE = tig.c tig.h io.c io.h graph.c graph.h
TXTDOC = tig.1.txt tigrc.5.txt manual.txt NEWS README INSTALL BUGS TODO
MANDOC = tig.1 tigrc.5 tigmanual.7
HTMLDOC = tig.1.html tigrc.5.html manual.html README.html NEWS.html
install-doc-man install-doc-html clean spell-check dist rpm
io.o: io.c io.h tig.h
+graph.o: tig.h
tig.o: tig.c tig.h io.h
-tig: tig.o io.o
+tig: tig.o io.o graph.o
+test-graph.o: test-graph.c io.h tig.h graph.h
+test-graph: io.o graph.o
tig.spec: contrib/tig.spec.in
sed -e 's/@@VERSION@@/$(RPM_VERSION)/g' \
index 0926f669d53c7f3a03823f07a2ce01f04bf33597..9a4439dc8b49afa8f90de2a8f898b0eaeaac7e4f 100644 (file)
--- a/NEWS
+++ b/NEWS
tig master
----------
+Improvements:
+
+ - Start rewrite of the revision graph renderer. Three modes are
+ supported UTF-8, ncurses line graphics, and ASCII. Also, enable
+ revision graph rendering by default.
+
Bug fixes:
- Fix opening of diffs when browsing branches.
diff --git a/graph.c b/graph.c
--- /dev/null
+++ b/graph.c
@@ -0,0 +1,410 @@
+/* Copyright (c) 2006-2010 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
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "tig.h"
+#include "graph.h"
+
+DEFINE_ALLOCATOR(realloc_graph_columns, struct graph_column, 32)
+DEFINE_ALLOCATOR(realloc_graph_symbols, struct graph_symbol, 1)
+
+static size_t get_free_graph_color(struct graph *graph)
+{
+ size_t i, free_color;
+
+ for (free_color = i = 0; i < ARRAY_SIZE(graph->colors); i++) {
+ if (graph->colors[i] < graph->colors[free_color])
+ free_color = i;
+ }
+
+ graph->colors[free_color]++;
+ return free_color;
+}
+
+void
+done_graph(struct graph *graph)
+{
+ free(graph->row.columns);
+ free(graph->parents.columns);
+ memset(graph, 0, sizeof(*graph));
+}
+
+#define graph_column_has_commit(col) ((col)->id[0])
+
+static size_t
+graph_find_column_by_id(struct graph_row *row, const char *id)
+{
+ size_t free_column = row->size;
+ size_t i;
+
+ for (i = 0; i < row->size; i++) {
+ if (!graph_column_has_commit(&row->columns[i]))
+ free_column = i;
+ else if (!strcmp(row->columns[i].id, id))
+ return i;
+ }
+
+ return free_column;
+}
+
+static struct graph_column *
+graph_insert_column(struct graph *graph, struct graph_row *row, size_t pos, const char *id)
+{
+ struct graph_column *column;
+
+ if (!realloc_graph_columns(&row->columns, row->size, 1))
+ return NULL;
+
+ column = &row->columns[pos];
+ if (pos < row->size) {
+ memmove(column + 1, column, sizeof(*column) * (row->size - pos));
+ }
+
+ row->size++;
+ memset(column, 0, sizeof(*column));
+ string_copy_rev(column->id, id);
+ column->symbol.boundary = !!graph->is_boundary;
+
+ return column;
+}
+
+struct graph_column *
+graph_add_parent(struct graph *graph, const char *parent)
+{
+ return graph_insert_column(graph, &graph->parents, graph->parents.size, parent);
+}
+
+static bool
+graph_needs_expansion(struct graph *graph)
+{
+ if (graph->position + graph->parents.size > graph->row.size)
+ return TRUE;
+ return graph->parents.size > 1
+ && graph->position < 0
+ && graph->expanded < graph->parents.size;
+}
+
+static bool
+graph_expand(struct graph *graph)
+{
+ while (graph_needs_expansion(graph)) {
+ if (!graph_insert_column(graph, &graph->row, graph->position + graph->expanded, ""))
+ return FALSE;
+ graph->expanded++;
+ }
+
+ return TRUE;
+}
+
+static bool
+graph_needs_collapsing(struct graph *graph)
+{
+ return graph->row.size > 1
+ && !graph_column_has_commit(&graph->row.columns[graph->row.size - 1]);
+}
+
+static bool
+graph_collapse(struct graph *graph)
+{
+ while (graph_needs_collapsing(graph)) {
+ graph->row.size--;
+ }
+
+ return TRUE;
+}
+
+static void
+graph_reorder_parents(struct graph *graph)
+{
+ struct graph_row *row = &graph->row;
+ struct graph_row *parents = &graph->parents;
+ int i;
+
+ if (parents->size == 1)
+ return;
+
+ for (i = 0; i < parents->size; i++) {
+ struct graph_column *column = &parents->columns[i];
+ size_t match = graph_find_column_by_id(row, column->id);
+
+ if (match < graph->position && graph_column_has_commit(&row->columns[match])) {
+ //die("Reorder: %s -> %s", graph->commit->id, column->id);
+// row->columns[match].symbol.initial = 1;
+ }
+ }
+}
+
+static void
+graph_canvas_append_symbol(struct graph *graph, struct graph_symbol *symbol)
+{
+ struct graph_canvas *canvas = graph->canvas;
+
+ if (realloc_graph_symbols(&canvas->symbols, canvas->size, 1))
+ canvas->symbols[canvas->size++] = *symbol;
+}
+
+static bool
+graph_insert_parents(struct graph *graph)
+{
+ struct graph_row *row = &graph->row;
+ struct graph_row *parents = &graph->parents;
+ size_t orig_size = row->size;
+ bool branched = FALSE;
+ bool merge = parents->size > 1;
+ int pos;
+
+ assert(!graph_needs_expansion(graph));
+
+ for (pos = 0; pos < graph->position; pos++) {
+ struct graph_column *column = &row->columns[pos];
+ struct graph_symbol symbol = column->symbol;
+
+ if (graph_column_has_commit(column)) {
+ size_t match = graph_find_column_by_id(parents, column->id);
+
+ if (match < parents->size) {
+ column->symbol.initial = 1;
+ }
+
+ symbol.branch = 1;
+ }
+ symbol.vbranch = !!branched;
+ if (!strcmp(column->id, graph->id)) {
+ branched = TRUE;
+ column->id[0] = 0;
+ }
+
+ graph_canvas_append_symbol(graph, &symbol);
+ }
+
+ for (; pos < graph->position + parents->size; pos++) {
+ struct graph_column *old = &row->columns[pos];
+ struct graph_column *new = &parents->columns[pos - graph->position];
+ struct graph_symbol symbol = old->symbol;
+
+ symbol.merge = !!merge;
+
+ if (pos == graph->position) {
+ symbol.commit = 1;
+ /*
+ if (new->symbol->boundary) {
+ symbol.boundary = 1;
+ } else*/
+ if (!graph_column_has_commit(new)) {
+ symbol.initial = 1;
+ }
+
+ } else if (!strcmp(old->id, new->id) && orig_size == row->size) {
+ symbol.vbranch = 1;
+ symbol.branch = 1;
+ //symbol.merge = 1;
+
+ } else if (parents->size > 1) {
+ symbol.merge = 1;
+ symbol.vbranch = !(pos == graph->position + parents->size - 1);
+
+ } else if (graph_column_has_commit(old)) {
+ symbol.branch = 1;
+ }
+
+ graph_canvas_append_symbol(graph, &symbol);
+ if (!graph_column_has_commit(old))
+ new->symbol.color = get_free_graph_color(graph);
+ *old = *new;
+ }
+
+ for (; pos < row->size; pos++) {
+ bool too = !strcmp(row->columns[row->size - 1].id, graph->id);
+ struct graph_symbol symbol = row->columns[pos].symbol;
+
+ symbol.vbranch = !!too;
+ if (row->columns[pos].id[0]) {
+ symbol.branch = 1;
+ if (!strcmp(row->columns[pos].id, graph->id)) {
+ symbol.branched = 1;
+ if (too && pos != row->size - 1) {
+ symbol.vbranch = 1;
+ } else {
+ symbol.vbranch = 0;
+ }
+ row->columns[pos].id[0] = 0;
+ }
+ }
+ graph_canvas_append_symbol(graph, &symbol);
+ }
+
+ graph->parents.size = graph->expanded = graph->position = 0;
+
+ return TRUE;
+}
+
+bool
+graph_render_parents(struct graph *graph)
+{
+ if (!graph_expand(graph))
+ return FALSE;
+ graph_reorder_parents(graph);
+ graph_insert_parents(graph);
+ if (!graph_collapse(graph))
+ return FALSE;
+
+ return TRUE;
+}
+
+bool
+graph_add_commit(struct graph *graph, struct graph_canvas *canvas,
+ const char *id, const char *parents, bool is_boundary)
+{
+ graph->position = graph_find_column_by_id(&graph->row, id);
+ graph->id = id;
+ graph->canvas = canvas;
+ graph->is_boundary = is_boundary;
+
+ while ((parents = strchr(parents, ' '))) {
+ parents++;
+ if (!graph_add_parent(graph, parents))
+ return FALSE;
+ graph->has_parents = TRUE;
+ }
+
+ if (graph->parents.size == 0 &&
+ !graph_add_parent(graph, ""))
+ return FALSE;
+
+ return TRUE;
+}
+
+const char *
+graph_symbol_to_utf8(struct graph_symbol *symbol)
+{
+ if (symbol->commit) {
+ if (symbol->boundary)
+ return " ◯";
+ else if (symbol->initial)
+ return " ◎";
+ else if (symbol->merge)
+ return " ●";
+ return " ●";
+ }
+
+ if (symbol->merge) {
+ if (symbol->branch) {
+ return "━┪";
+ }
+ if (symbol->vbranch)
+ return "━┯";
+ return "━┑";
+ }
+
+ if (symbol->branch) {
+ if (symbol->branched) {
+ if (symbol->vbranch)
+ return "─┴";
+ return "─┘";
+ }
+ if (symbol->vbranch)
+ return "─│";
+ return " │";
+ }
+
+ if (symbol->vbranch)
+ return "──";
+
+ return " ";
+}
+
+const chtype *
+graph_symbol_to_chtype(struct graph_symbol *symbol)
+{
+ static chtype graphics[2];
+
+ if (symbol->commit) {
+ graphics[0] = ' ';
+ if (symbol->boundary)
+ graphics[1] = 'o';
+ else if (symbol->initial)
+ graphics[1] = 'I';
+ else if (symbol->merge)
+ graphics[1] = 'M';
+ else
+ graphics[1] = 'o'; //ACS_DIAMOND; //'*';
+ return graphics;
+ }
+
+ if (symbol->merge) {
+ graphics[0] = ACS_HLINE;
+ if (symbol->branch)
+ graphics[1] = ACS_RTEE;
+ else
+ graphics[1] = ACS_URCORNER;
+ return graphics;
+ }
+
+ if (symbol->branch) {
+ graphics[0] = ACS_HLINE;
+ if (symbol->branched) {
+ if (symbol->vbranch)
+ graphics[1] = ACS_BTEE;
+ else
+ graphics[1] = ACS_LRCORNER;
+ return graphics;
+ }
+
+ if (!symbol->vbranch)
+ graphics[0] = ' ';
+ graphics[1] = ACS_VLINE;
+ return graphics;
+ }
+
+ if (symbol->vbranch) {
+ graphics[0] = graphics[1] = ACS_HLINE;
+ } else
+ graphics[0] = graphics[1] = ' ';
+
+ return graphics;
+}
+
+const char *
+graph_symbol_to_ascii(struct graph_symbol *symbol)
+{
+ if (symbol->commit) {
+ if (symbol->boundary)
+ return " o";
+ else if (symbol->initial)
+ return " I";
+ else if (symbol->merge)
+ return " M";
+ return " *";
+ }
+
+ if (symbol->merge) {
+ if (symbol->branch)
+ return "-+";
+ return "-.";
+ }
+
+ if (symbol->branch) {
+ if (symbol->branched) {
+ if (symbol->vbranch)
+ return "-+";
+ return "-'";
+ }
+ if (symbol->vbranch)
+ return "-|";
+ return " |";
+ }
+
+ if (symbol->vbranch)
+ return "--";
+
+ return " ";
+}
diff --git a/graph.h b/graph.h
--- /dev/null
+++ b/graph.h
@@ -0,0 +1,73 @@
+
+/* Copyright (c) 2006-2010 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
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef TIG_GRAPH_H
+#define TIG_GRAPH_H
+
+#define GRAPH_COLORS 7
+
+struct graph_symbol {
+ unsigned int color:8;
+ unsigned int bold:1;
+
+ unsigned int commit:1;
+ unsigned int branch:1;
+
+ unsigned int boundary:1;
+ unsigned int initial:1;
+ unsigned int merge:1;
+
+ unsigned int vbranch:1;
+ unsigned int branched:1;
+};
+
+struct graph_canvas {
+ size_t size; /* The width of the graph array. */
+ struct graph_symbol *symbols; /* Symbols for this row. */
+};
+
+struct graph_column {
+ struct graph_symbol symbol;
+ char id[SIZEOF_REV]; /* Parent SHA1 ID. */
+};
+
+struct graph_row {
+ size_t size;
+ struct graph_column *columns;
+};
+
+struct graph {
+ struct graph_row row;
+ struct graph_row parents;
+ size_t position;
+ size_t expanded;
+ const char *id;
+ struct graph_canvas *canvas;
+ size_t colors[GRAPH_COLORS];
+ bool has_parents;
+ bool is_boundary;
+};
+
+void done_graph(struct graph *graph);
+
+bool graph_render_parents(struct graph *graph);
+bool graph_add_commit(struct graph *graph, struct graph_canvas *canvas,
+ const char *id, const char *parents, bool is_boundary);
+struct graph_column *graph_add_parent(struct graph *graph, const char *parent);
+
+const char *graph_symbol_to_ascii(struct graph_symbol *symbol);
+const char *graph_symbol_to_utf8(struct graph_symbol *symbol);
+const chtype *graph_symbol_to_chtype(struct graph_symbol *symbol);
+
+#endif
index 685ffb6cfeb77d7573156f4cec6332fc0cabdce7..edd6e85cfe110a45b1ee76a05ca476ccc8ef755b 100644 (file)
--- a/io.c
+++ b/io.c
}
bool
-io_can_read(struct io *io)
+io_can_read(struct io *io, bool can_block)
{
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;
+ return select(io->pipe + 1, &fds, NULL, NULL, can_block ? NULL : &tv) > 0;
}
ssize_t
index 9a68a5ec336bac8fbcbb96a5e84f9971a18a227d..8a766ca94c467e5f35012af11290eb2590978b35 100644 (file)
--- a/io.h
+++ b/io.h
bool io_eof(struct io *io);
int io_error(struct io *io);
char * io_strerror(struct io *io);
-bool io_can_read(struct io *io);
+bool io_can_read(struct io *io, bool can_block);
ssize_t io_read(struct io *io, void *buf, size_t bufsize);
char * io_get(struct io *io, int c, bool can_read);
bool io_write(struct io *io, const void *buf, size_t bufsize);
diff --git a/manual.txt b/manual.txt
index ffea349d2537fa66f80f914c4e57473db63f64b1..c9b18932fd98705bff238aa1af9b897094f6a16d 100644 (file)
--- a/manual.txt
+++ b/manual.txt
|D |Toggle date display on/off/short/relative/local.
|A |Toggle author display on/off/abbreviated.
|g |Toggle revision graph visualization on/off.
+|~ |Toggle (line) graphics mode
|F |Toggle reference display on/off (tag and branch names).
|: |Open prompt. This allows you to specify what git command
to run. Example `:log -p`. You can also use this to jump
diff --git a/test-graph.c b/test-graph.c
--- /dev/null
+++ b/test-graph.c
@@ -0,0 +1,106 @@
+/* Copyright (c) 2006-2010 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
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Example usage:
+ *
+ * # git log --pretty=raw --parents | ./test-graph
+ * # git log --pretty=raw --parents | ./test-graph --ascii
+ */
+
+#include "tig.h"
+#include "io.h"
+#include "graph.h"
+
+static void __NORETURN
+die(const char *err, ...)
+{
+ va_list args;
+
+ endwin();
+
+ va_start(args, err);
+ fputs("tig: ", stderr);
+ vfprintf(stderr, err, args);
+ fputs("\n", stderr);
+ va_end(args);
+
+ exit(1);
+}
+
+struct commit {
+ char id[SIZEOF_REV];
+ struct graph_canvas canvas;
+};
+
+DEFINE_ALLOCATOR(realloc_commits, struct commit *, 8)
+
+int
+main(int argc, const char *argv[])
+{
+ struct graph graph = { };
+ struct io io = { };
+ char *line;
+ struct commit **commits = NULL;
+ size_t ncommits = 0;
+ struct commit *commit = NULL;
+ bool is_boundary;
+ const char *(*graph_fn)(struct graph_symbol *) = graph_symbol_to_utf8;
+
+ if (argc > 1 && !strcmp(argv[1], "--ascii"))
+ graph_fn = graph_symbol_to_ascii;
+
+ if (!io_open(&io, ""))
+ die("IO");
+
+ while (!io_eof(&io)) {
+ bool can_read = io_can_read(&io, TRUE);
+
+ for (; (line = io_get(&io, '\n', can_read)); can_read = FALSE) {
+ if (!prefixcmp(line, "commit ")) {
+ line += STRING_SIZE("commit ");
+ is_boundary = *line == '-';
+
+ if (is_boundary)
+ line++;
+
+ if (!realloc_commits(&commits, ncommits, 1))
+ die("Commits");
+
+ commit = calloc(1, sizeof(*commit));
+ if (!commit)
+ die("Commit");
+ commits[ncommits++] = commit;
+ string_copy_rev(commit->id, line);
+ graph_add_commit(&graph, &commit->canvas, commit->id, line, is_boundary);
+ graph_render_parents(&graph);
+
+ } else if (!prefixcmp(line, " ")) {
+ int i;
+
+ if (!commit)
+ continue;
+
+ for (i = 0; i < commit->canvas.size; i++) {
+ struct graph_symbol *symbol = &commit->canvas.symbols[i];
+ const char *chars = graph_fn(symbol);
+
+ printf("%s", chars + (i == 0));
+ }
+ printf("%s\n", line + 3);
+
+ commit = NULL;
+ }
+ }
+ }
+
+ return 0;
+}
index 6ad9c57953182e4d54d4ad3fdca66d61e8dfc4b3..2cf4360ebff7ec72d1bf66233441dedbe68c819b 100644 (file)
--- a/tig.c
+++ b/tig.c
-/* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
+/* Copyrsght (c) 2006-2010 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 "tig.h"
#include "io.h"
+#include "graph.h"
static void __NORETURN die(const char *err, ...);
static void warn(const char *msg, ...);
static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
+enum graphic {
+ GRAPHIC_ASCII = 0,
+ GRAPHIC_DEFAULT,
+ GRAPHIC_UTF8
+};
+
+static const struct enum_map graphic_map[] = {
+#define GRAPHIC_(name) ENUM_MAP(#name, GRAPHIC_##name)
+ GRAPHIC_(ASCII),
+ GRAPHIC_(DEFAULT),
+ GRAPHIC_(UTF8)
+#undef GRAPHIC_
+};
+
#define DATE_INFO \
DATE_(NO), \
DATE_(DEFAULT), \
REQ_(TOGGLE_DATE, "Toggle date display"), \
REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
+ REQ_(TOGGLE_GRAPHIC, "Toggle (line) graphics mode"), \
REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
*/
/* Option and state variables. */
+static enum graphic opt_line_graphics = GRAPHIC_DEFAULT;
static enum date opt_date = DATE_DEFAULT;
static enum author opt_author = AUTHOR_DEFAULT;
+static bool opt_rev_graph = TRUE;
static bool opt_line_number = FALSE;
-static bool opt_line_graphics = TRUE;
-static bool opt_rev_graph = FALSE;
static bool opt_show_refs = TRUE;
static bool opt_untracked_dirs_content = TRUE;
static int opt_num_interval = 5;
LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
-LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
+LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_0, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_1, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_2, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_3, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_4, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_5, "", COLOR_WHITE, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_6, "", COLOR_RED, COLOR_DEFAULT, 0), \
+LINE(GRAPH_COMMIT, "", COLOR_BLUE, COLOR_DEFAULT, 0)
enum line_type {
#define LINE(type, line, fg, bg, attr) \
{ 'D', REQ_TOGGLE_DATE },
{ 'A', REQ_TOGGLE_AUTHOR },
{ 'g', REQ_TOGGLE_REV_GRAPH },
+ { '~', REQ_TOGGLE_GRAPHIC },
{ 'F', REQ_TOGGLE_REFS },
{ 'I', REQ_TOGGLE_SORT_ORDER },
{ 'i', REQ_TOGGLE_SORT_FIELD },
return parse_bool(&opt_line_number, argv[2]);
if (!strcmp(argv[0], "line-graphics"))
- return parse_bool(&opt_line_graphics, argv[2]);
+ return parse_enum(&opt_line_graphics, argv[2], graphic_map);
if (!strcmp(argv[0], "line-number-interval"))
return parse_int(&opt_num_interval, argv[2], 1, 1024);
}
static bool
-draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
+draw_graphic(struct view *view, enum line_type type, const chtype graphic[], size_t size, bool separator)
{
size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
int max = view->width + view->yoffset - view->col;
@@ -1568,9 +1593,11 @@ draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t si
waddch(view->win, graphic[i]);
view->col += size;
- if (size < max && skip <= size)
- waddch(view->win, ' ');
- view->col++;
+ if (separator) {
+ if (size < max && skip <= size)
+ waddch(view->win, ' ');
+ view->col++;
+ }
return view->width + view->yoffset <= view->col;
}
view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
else
view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
- return draw_graphic(view, LINE_DEFAULT, &separator, 1);
+ return draw_graphic(view, LINE_DEFAULT, &separator, 1, TRUE);
}
static bool
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_(GRAPHIC, '~', "graphics", &opt_line_graphics, graphic_map) \
TOGGLE_(REV_GRAPH, 'g', "revision graph", &opt_rev_graph, NULL) \
TOGGLE_(REFS, 'F', "reference display", &opt_show_refs, NULL)
if (!view->pipe)
return TRUE;
- if (!io_can_read(view->pipe)) {
+ if (!io_can_read(view->pipe, FALSE)) {
if (view->lines == 0 && view_is_displayed(view)) {
time_t secs = time(NULL) - view->start_time;
case REQ_TOGGLE_LINENO:
case REQ_TOGGLE_DATE:
case REQ_TOGGLE_AUTHOR:
+ case REQ_TOGGLE_GRAPHIC:
case REQ_TOGGLE_REV_GRAPH:
case REQ_TOGGLE_REFS:
toggle_option(request);
* Revision graph
*/
-struct commit {
- char id[SIZEOF_REV]; /* SHA1 ID. */
- char title[128]; /* First line of the commit message. */
- const char *author; /* Author of the commit. */
- struct time time; /* Date from the author ident. */
- struct ref_list *refs; /* Repository references. */
- chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
- size_t graph_size; /* The width of the graph array. */
- bool has_parents; /* Rewritten --parents seen. */
+static const enum line_type graph_colors[] = {
+ LINE_GRAPH_LINE_0,
+ LINE_GRAPH_LINE_1,
+ LINE_GRAPH_LINE_2,
+ LINE_GRAPH_LINE_3,
+ LINE_GRAPH_LINE_4,
+ LINE_GRAPH_LINE_5,
+ LINE_GRAPH_LINE_6,
};
-/* 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;
- unsigned int boundary:1;
-};
-
-/* 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)
+static enum line_type get_graph_color(struct graph_symbol *symbol)
{
- return graph->parents->size > 1;
+ if (symbol->commit)
+ return LINE_GRAPH_COMMIT;
+ assert(symbol->color < ARRAY_SIZE(graph_colors));
+ return graph_colors[symbol->color];
}
-static inline void
-append_to_rev_graph(struct rev_graph *graph, chtype symbol)
+static bool
+draw_graph_utf8(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
{
- struct commit *commit = graph->commit;
-
- if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
- commit->graph[commit->graph_size++] = symbol;
-}
+ const char *chars = graph_symbol_to_utf8(symbol);
-static void
-clear_rev_graph(struct rev_graph *graph)
-{
- graph->boundary = 0;
- graph->size = graph->pos = 0;
- graph->commit = NULL;
- memset(graph->parents, 0, sizeof(*graph->parents));
+ return draw_text(view, color, chars + !!first);
}
-static void
-done_rev_graph(struct rev_graph *graph)
+static bool
+draw_graph_ascii(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
{
- 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++;
- }
- }
+ const char *chars = graph_symbol_to_ascii(symbol);
- clear_rev_graph(graph);
+ return draw_text(view, color, chars + !!first);
}
-static void
-push_rev_graph(struct rev_graph *graph, const char *parent)
+static bool
+draw_graph_chtype(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
{
- int i;
+ const chtype *chars = graph_symbol_to_chtype(symbol);
- /* "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_copy_rev(graph->rev[graph->size++], parent);
- }
+ return draw_graphic(view, color, chars + !!first, 2 - !!first, FALSE);
}
-static chtype
-get_rev_graph_symbol(struct rev_graph *graph)
-{
- chtype symbol;
-
- if (graph->boundary)
- symbol = REVGRAPH_BOUND;
- else 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;
+typedef bool (*draw_graph_fn)(struct view *, struct graph_symbol *, enum line_type, bool);
- return symbol;
-}
-
-static void
-draw_rev_graph(struct rev_graph *graph)
+static bool draw_graph(struct view *view, struct graph_canvas *canvas)
{
- struct rev_filler {
- chtype separator, line;
+ static const draw_graph_fn fns[] = {
+ draw_graph_ascii,
+ draw_graph_chtype,
+ draw_graph_utf8
};
- enum { DEFAULT, RSHARP, RDIAG, LDIAG };
- static struct rev_filler fillers[] = {
- { ' ', '|' },
- { '`', '.' },
- { '\'', ' ' },
- { '/', ' ' },
- };
- chtype symbol = get_rev_graph_symbol(graph);
- struct rev_filler *filler;
- size_t i;
-
- fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
- 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);
- }
-}
+ draw_graph_fn fn = fns[opt_line_graphics];
+ int i;
-/* Prepare the next rev graph */
-static void
-prepare_rev_graph(struct rev_graph *graph)
-{
- size_t i;
+ for (i = 0; i < canvas->size; i++) {
+ struct graph_symbol *symbol = &canvas->symbols[i];
+ enum line_type color = get_graph_color(symbol);
- /* 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]);
+ if (fn(view, symbol, color, i == 0))
+ return TRUE;
}
- /* Interleave the new revision parent(s). */
- for (i = 0; !graph->boundary && 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]);
+ return draw_text(view, LINE_MAIN_REVGRAPH, " ");
}
-static void
-update_rev_graph(struct view *view, 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;
-
- 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);
-}
-
-
/*
* Main view backend
*/
+struct commit {
+ char id[SIZEOF_REV]; /* SHA1 ID. */
+ char title[128]; /* First line of the commit message. */
+ const char *author; /* Author of the commit. */
+ struct time time; /* Date from the author ident. */
+ struct ref_list *refs; /* Repository references. */
+ struct graph_canvas graph; /* Ancestry chain graphics. */
+};
+
static const char *main_argv[SIZEOF_ARG] = {
"git", "log", "--no-color", "--pretty=raw", "--parents",
"--topo-order", "%(diffargs)", "%(revargs)",
if (opt_author && draw_author(view, commit->author))
return TRUE;
- if (opt_rev_graph && commit->graph_size &&
- draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
+ if (opt_rev_graph && draw_graph(view, &commit->graph))
return TRUE;
if (opt_show_refs && commit->refs) {
static bool
main_read(struct view *view, char *line)
{
- static struct rev_graph *graph = graph_stacks;
+ static struct graph graph;
enum line_type type;
struct commit *commit;
if (!line) {
- int i;
-
if (!view->lines && !view->prev)
die("No revisions match the given arguments.");
if (view->lines > 0) {
if (!commit->author) {
view->lines--;
free(commit);
- graph->commit = NULL;
}
}
- update_rev_graph(view, graph);
- for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
- clear_rev_graph(&graph_stacks[i]);
+ done_graph(&graph);
return TRUE;
}
type = get_line_type(line);
if (type == LINE_COMMIT) {
+ bool is_boundary;
+
commit = calloc(1, sizeof(struct commit));
if (!commit)
return FALSE;
line += STRING_SIZE("commit ");
- if (*line == '-') {
- graph->boundary = 1;
+ is_boundary = *line == '-';
+ if (is_boundary)
line++;
- }
string_copy_rev(commit->id, line);
commit->refs = get_ref_list(commit->id);
- graph->commit = commit;
add_line_data(view, commit, LINE_MAIN_COMMIT);
-
- while ((line = strchr(line, ' '))) {
- line++;
- push_rev_graph(graph->parents, line);
- commit->has_parents = TRUE;
- }
+ graph_add_commit(&graph, &commit->graph, commit->id, line, is_boundary);
return TRUE;
}
switch (type) {
case LINE_PARENT:
- if (commit->has_parents)
- break;
- push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
+ if (!graph.has_parents)
+ graph_add_parent(&graph, line + STRING_SIZE("parent "));
break;
case LINE_AUTHOR:
parse_author_line(line + STRING_SIZE("author "),
&commit->author, &commit->time);
- update_rev_graph(view, graph);
- graph = graph->next;
+ graph_render_parents(&graph);
break;
default:
index e6cd9a19e5c8ffc312cbea2bc5493f7cef9a34d5..85b639bea72a0649869e27383bb056bb16b26936 100644 (file)
--- a/tig.h
+++ b/tig.h
#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)
diff --git a/tigrc.5.txt b/tigrc.5.txt
index 2d9afddd2dff47198d85112cfede22d164c78a46..b373ef2595f22819de4c24a3d71fb43f54d45f0c 100644 (file)
--- a/tigrc.5.txt
+++ b/tigrc.5.txt
The following variables can be set:
'show-rev-graph' (bool)::
+
+ Whether to show revision graph in the main view on start-up.
+ Can be toggled. See also line-graphics options.
+
'show-refs' (bool)::
- Whether to show revision graph, and references (branches, tags, and
- remotes) in the main view on start-up. Can all be toggled.
+ Whether to show references (branches, tags, and remotes) in the main
+ view on start-up. Can be toggled.
'show-author' (mixed) ["abbreviated" | "default" | bool]::
Width of the author column. When set to 5 or below, the author name
will be abbreviated to the author's initials.
-'line-graphics' (bool)::
+'line-graphics' (mixed) [ "ascii" | "default" | "utf-8" | bool]::
- Whether to use graphic characters for line drawing.
+ What type of character graphics for line drawing.
'line-number-interval' (int)::
|toggle-date |Toggle date display
|toggle-author |Toggle author display
|toggle-rev-graph |Toggle revision graph visualization
+|toggle-graphic |Toggle (line) graphics mode
|toggle-refs |Toggle reference display
|edit |Open in editor
|none |Do nothing