Add doc example for disabling automatic use of topo-order for the graph
[tig.git] / src / graph.c
blob6317352c41ac970dd9df1a086f3345f0ef96d141
1 /* Copyright (c) 2006-2014 Jonas Fonseca <jonas.fonseca@gmail.com>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #include "tig/tig.h"
15 #include "tig/util.h"
16 #include "tig/graph.h"
18 DEFINE_ALLOCATOR(realloc_graph_columns, struct graph_column, 32)
19 DEFINE_ALLOCATOR(realloc_graph_symbols, struct graph_symbol, 1)
21 struct id_color {
22 char *id;
23 size_t color;
26 struct id_color *
27 id_color_new(const char *id, size_t color)
29 struct id_color *node = malloc(sizeof(struct id_color));
31 node->id = (char *) malloc(strlen(id) + 1);
32 strcpy(node->id, id);
33 node->color = color;
35 return node;
38 static void
39 id_color_delete(struct id_color *node)
41 free(node->id);
42 free(node);
45 static int
46 id_color_eq(const void *entry, const void *element)
48 return strcmp(((const struct id_color *) entry)->id, ((const struct id_color *) element)->id) == 0;
51 static void
52 key_del(void *key)
54 id_color_delete((struct id_color *) key);
57 static hashval_t
58 id_color_hash(const void *node)
60 return htab_hash_string(((const struct id_color*) node)->id);
63 static void
64 colors_add_id(struct colors *colors, const char *id, const size_t color)
66 struct id_color *node = id_color_new(id, color);
67 void **slot = htab_find_slot(colors->id_map, node, INSERT);
69 if (slot != NULL && *slot == NULL) {
70 *slot = node;
71 colors->count[color]++;
72 } else {
73 id_color_delete(node);
77 static void
78 colors_remove_id(struct colors *colors, const char *id)
80 struct id_color *node = id_color_new(id, 0);
81 void **slot = htab_find_slot(colors->id_map, node, NO_INSERT);
83 if (slot != NULL && *slot != NULL) {
84 colors->count[((struct id_color *) *slot)->color]--;
85 htab_clear_slot(colors->id_map, slot);
88 id_color_delete(node);
91 static size_t
92 colors_get_color(struct colors *colors, const char *id)
94 struct id_color *key = id_color_new(id, 0);
95 struct id_color *node = (struct id_color *) htab_find(colors->id_map, key);
97 id_color_delete(key);
99 if (node == NULL) {
100 return (size_t) -1; // Max value of size_t. ID not found.
102 return node->color;
105 static size_t
106 colors_get_free_color(struct colors *colors)
108 size_t free_color = 0;
109 size_t lowest = (size_t) -1; // Max value of size_t
110 int i;
112 for (i = 0; i < ARRAY_SIZE(colors->count); i++) {
113 if (colors->count[i] < lowest) {
114 lowest = colors->count[i];
115 free_color = i;
118 return free_color;
121 static void
122 colors_init(struct colors *colors)
124 if (colors->id_map == NULL) {
125 uint size = 500;
127 colors->id_map = htab_create_alloc(size, id_color_hash, id_color_eq, key_del, calloc, free);
131 static size_t
132 get_color(struct graph *graph, char *new_id)
134 size_t color;
136 colors_init(&graph->colors);
137 color = colors_get_color(&graph->colors, new_id);
139 if (color < (size_t) -1) {
140 return color;
143 color = colors_get_free_color(&graph->colors);
144 colors_add_id(&graph->colors, new_id, color);
146 return color;
149 struct graph *
150 init_graph(void)
152 return calloc(1, sizeof(struct graph));
155 void
156 done_graph(struct graph *graph)
158 free(graph->prev_row.columns);
159 free(graph->row.columns);
160 free(graph->next_row.columns);
161 free(graph->parents.columns);
162 free(graph);
165 #define graph_column_has_commit(col) ((col)->id[0])
167 static size_t
168 graph_find_column_by_id(struct graph_row *row, const char *id)
170 size_t free_column = row->size;
171 size_t i;
173 for (i = 0; i < row->size; i++) {
174 if (!graph_column_has_commit(&row->columns[i]) && free_column == row->size)
175 free_column = i;
176 else if (!strcmp(row->columns[i].id, id))
177 return i;
180 return free_column;
183 static size_t
184 graph_find_free_column(struct graph_row *row)
186 size_t i;
188 for (i = 0; i < row->size; i++) {
189 if (!graph_column_has_commit(&row->columns[i]))
190 return i;
193 return row->size;
196 static struct graph_column *
197 graph_insert_column(struct graph *graph, struct graph_row *row, size_t pos, const char *id)
199 struct graph_column *column;
201 if (!realloc_graph_columns(&row->columns, row->size, 1))
202 return NULL;
204 column = &row->columns[pos];
205 if (pos < row->size) {
206 memmove(column + 1, column, sizeof(*column) * (row->size - pos));
209 row->size++;
210 memset(column, 0, sizeof(*column));
211 string_copy_rev(column->id, id);
212 column->symbol.boundary = !!graph->is_boundary;
214 return column;
217 struct graph_column *
218 graph_add_parent(struct graph *graph, const char *parent)
220 return graph_insert_column(graph, &graph->parents, graph->parents.size, parent);
223 static bool
224 graph_needs_expansion(struct graph *graph)
226 return graph->position + graph->parents.size > graph->row.size;
229 static bool
230 graph_expand(struct graph *graph)
232 while (graph_needs_expansion(graph)) {
233 if (!graph_insert_column(graph, &graph->prev_row, graph->prev_row.size, ""))
234 return FALSE;
236 if (!graph_insert_column(graph, &graph->row, graph->row.size, ""))
237 return FALSE;
239 if (!graph_insert_column(graph, &graph->next_row, graph->next_row.size, ""))
240 return FALSE;
243 return TRUE;
246 static bool
247 graph_needs_collapsing(struct graph *graph)
249 return graph->row.size > 1
250 && !graph_column_has_commit(&graph->row.columns[graph->row.size - 1]);
253 static bool
254 graph_collapse(struct graph *graph)
256 while (graph_needs_collapsing(graph)) {
257 graph->prev_row.size--;
258 graph->row.size--;
259 graph->next_row.size--;
262 return TRUE;
265 static void
266 graph_canvas_append_symbol(struct graph *graph, struct graph_canvas *canvas, struct graph_symbol *symbol)
268 if (realloc_graph_symbols(&canvas->symbols, canvas->size, 1))
269 canvas->symbols[canvas->size++] = *symbol;
272 static void
273 graph_row_clear_commit(struct graph_row *row, const char *id)
275 int i;
277 for (i = 0; i < row->size; i++) {
278 if (strcmp(row->columns[i].id, id) == 0) {
279 row->columns[i].id[0] = 0;
284 static void
285 graph_insert_parents(struct graph *graph)
287 struct graph_row *prev_row = &graph->prev_row;
288 struct graph_row *row = &graph->row;
289 struct graph_row *next_row = &graph->next_row;
290 struct graph_row *parents = &graph->parents;
291 int i;
293 for (i = 0; i < parents->size; i++) {
294 struct graph_column *new = &parents->columns[i];
296 if (graph_column_has_commit(new)) {
297 size_t match = graph_find_free_column(next_row);
299 if (match == next_row->size && next_row->columns[next_row->size - 1].id) {
300 graph_insert_column(graph, next_row, next_row->size, new->id);
301 graph_insert_column(graph, row, row->size, "");
302 graph_insert_column(graph, prev_row, prev_row->size, "");
303 } else {
304 next_row->columns[match] = *new;
310 static bool
311 commit_is_in_row(const char *id, struct graph_row *row)
313 int i;
315 for (i = 0; i < row->size; i++) {
316 if (!graph_column_has_commit(&row->columns[i]))
317 continue;
319 if (strcmp(id, row->columns[i].id) == 0)
320 return true;
322 return false;
325 static void
326 graph_remove_collapsed_columns(struct graph *graph)
328 struct graph_row *row = &graph->next_row;
329 int i;
331 for (i = row->size - 1; i > 0; i--) {
332 if (i == graph->position)
333 continue;
335 if (i == graph->position + 1)
336 continue;
338 if (strcmp(row->columns[i].id, graph->id) == 0)
339 continue;
341 if (strcmp(row->columns[i].id, row->columns[i - 1].id) != 0)
342 continue;
344 if (commit_is_in_row(row->columns[i].id, &graph->parents) && !graph_column_has_commit(&graph->prev_row.columns[i]))
345 continue;
347 if (strcmp(row->columns[i - 1].id, graph->prev_row.columns[i - 1].id) != 0 || graph->prev_row.columns[i - 1].symbol.shift_left)
348 row->columns[i] = row->columns[i + 1];
352 static void
353 graph_fill_empty_columns(struct graph *graph)
355 struct graph_row *row = &graph->next_row;
356 int i;
358 for (i = row->size - 2; i >= 0; i--) {
359 if (!graph_column_has_commit(&row->columns[i])) {
360 row->columns[i] = row->columns[i + 1];
365 static void
366 graph_generate_next_row(struct graph *graph)
368 graph_row_clear_commit(&graph->next_row, graph->id);
369 graph_insert_parents(graph);
370 graph_remove_collapsed_columns(graph);
371 graph_fill_empty_columns(graph);
374 static int
375 commits_in_row(struct graph_row *row)
377 int count = 0;
378 int i;
380 for (i = 0; i < row->size;i++) {
381 if (graph_column_has_commit(&row->columns[i]))
382 count++;
385 return count;
388 static void
389 graph_commit_next_row(struct graph *graph)
391 int i;
393 for (i = 0; i < graph->row.size; i++) {
394 graph->prev_row.columns[i] = graph->row.columns[i];
396 if (i == graph->position && commits_in_row(&graph->parents) > 0)
397 graph->prev_row.columns[i] = graph->next_row.columns[i];
399 if (!graph_column_has_commit(&graph->prev_row.columns[i]))
400 graph->prev_row.columns[i] = graph->next_row.columns[i];
402 graph->row.columns[i] = graph->next_row.columns[i];
405 graph->prev_position = graph->position;
408 static bool
409 continued_down(struct graph_row *row, struct graph_row *next_row, int pos)
411 if (strcmp(row->columns[pos].id, next_row->columns[pos].id) != 0)
412 return false;
414 if (row->columns[pos].symbol.shift_left)
415 return false;
417 return true;
420 static bool
421 shift_left(struct graph_row *row, struct graph_row *prev_row, int pos)
423 int i;
425 if (!graph_column_has_commit(&row->columns[pos]))
426 return false;
428 for (i = pos - 1; i >= 0; i--) {
429 if (!graph_column_has_commit(&row->columns[i]))
430 continue;
432 if (strcmp(row->columns[i].id, row->columns[pos].id) != 0)
433 continue;
435 if (!continued_down(prev_row, row, i))
436 return true;
438 break;
441 return false;
444 static bool
445 new_column(struct graph_row *row, struct graph_row *prev_row, int pos)
447 int i;
449 if (!graph_column_has_commit(&prev_row->columns[pos]))
450 return true;
452 for (i = pos; i < row->size; i++) {
453 if (strcmp(row->columns[pos].id, prev_row->columns[i].id) == 0)
454 return false;
457 return true;
460 static bool
461 continued_right(struct graph_row *row, int pos, int commit_pos)
463 int i, end;
465 if (pos < commit_pos)
466 end = commit_pos;
467 else
468 end = row->size;
470 for (i = pos + 1; i < end; i++) {
471 if (strcmp(row->columns[pos].id, row->columns[i].id) == 0)
472 return true;
475 return false;
478 static bool
479 continued_left(struct graph_row *row, int pos, int commit_pos)
481 int i, start;
483 if (pos < commit_pos)
484 start = 0;
485 else
486 start = commit_pos;
488 for (i = start; i < pos; i++) {
489 if (!graph_column_has_commit(&row->columns[i]))
490 continue;
492 if (strcmp(row->columns[pos].id, row->columns[i].id) == 0)
493 return true;
496 return false;
499 static bool
500 parent_down(struct graph_row *parents, struct graph_row *next_row, int pos)
502 int parent;
504 for (parent = 0; parent < parents->size; parent++) {
505 if (!graph_column_has_commit(&parents->columns[parent]))
506 continue;
508 if (strcmp(parents->columns[parent].id, next_row->columns[pos].id) == 0)
509 return true;
512 return false;
515 static bool
516 parent_right(struct graph_row *parents, struct graph_row *row, struct graph_row *next_row, int pos)
518 int parent, i;
520 for (parent = 0; parent < parents->size; parent++) {
521 if (!graph_column_has_commit(&parents->columns[parent]))
522 continue;
524 for (i = pos + 1; i < next_row->size; i++) {
525 if (strcmp(parents->columns[parent].id, next_row->columns[i].id) != 0)
526 continue;
528 if (strcmp(parents->columns[parent].id, row->columns[i].id) != 0)
529 return true;
533 return false;
536 static bool
537 flanked(struct graph_row *row, int pos, int commit_pos, const char *commit_id)
539 int i, start, end;
541 if (pos < commit_pos) {
542 start = 0;
543 end = pos;
544 } else {
545 start = pos + 1;
546 end = row->size;
549 for (i = start; i < end; i++) {
550 if (strcmp(row->columns[i].id, commit_id) == 0)
551 return true;
554 return false;
557 static bool
558 below_commit(int pos, struct graph *graph)
560 if (!pos == graph->prev_position)
561 return false;
563 if (!strcmp(graph->row.columns[pos].id, graph->prev_row.columns[pos].id) == 0)
564 return false;
566 return true;
569 static void
570 graph_generate_symbols(struct graph *graph, struct graph_canvas *canvas)
572 struct graph_row *prev_row = &graph->prev_row;
573 struct graph_row *row = &graph->row;
574 struct graph_row *next_row = &graph->next_row;
575 struct graph_row *parents = &graph->parents;
576 int pos;
578 for (pos = 0; pos < row->size; pos++) {
579 struct graph_column *column = &row->columns[pos];
580 struct graph_symbol *symbol = &column->symbol;
581 char *id = next_row->columns[pos].id;
583 symbol->commit = (pos == graph->position);
584 symbol->boundary = (pos == graph->position && next_row->columns[pos].symbol.boundary);
585 symbol->initial = (commits_in_row(parents) < 1);
586 symbol->merge = (commits_in_row(parents) > 1);
588 symbol->continued_down = continued_down(row, next_row, pos);
589 symbol->continued_up = continued_down(prev_row, row, pos);
590 symbol->continued_right = continued_right(row, pos, graph->position);
591 symbol->continued_left = continued_left(row, pos, graph->position);
592 symbol->continued_up_left = continued_left(prev_row, pos, prev_row->size);
594 symbol->parent_down = parent_down(parents, next_row, pos);
595 symbol->parent_right = (pos > graph->position && parent_right(parents, row, next_row, pos));
597 symbol->below_commit = below_commit(pos, graph);
598 symbol->flanked = flanked(row, pos, graph->position, graph->id);
599 symbol->next_right = continued_right(next_row, pos, 0);
600 symbol->matches_commit = (strcmp(column->id, graph->id) == 0);
602 symbol->shift_left = shift_left(row, prev_row, pos);
603 symbol->continue_shift = shift_left(row, prev_row, pos + 1);
604 symbol->below_shift = prev_row->columns[pos].symbol.shift_left;
606 symbol->new_column = new_column(row, prev_row, pos);
607 symbol->empty = (!graph_column_has_commit(&row->columns[pos]));
609 if (graph_column_has_commit(column)) {
610 id = column->id;
612 symbol->color = get_color(graph, id);
614 graph_canvas_append_symbol(graph, canvas, symbol);
617 colors_remove_id(&graph->colors, graph->id);
620 bool
621 graph_render_parents(struct graph *graph, struct graph_canvas *canvas)
623 if (graph->parents.size == 0 &&
624 !graph_add_parent(graph, ""))
625 return FALSE;
627 if (!graph_expand(graph))
628 return FALSE;
630 graph_generate_next_row(graph);
631 graph_generate_symbols(graph, canvas);
632 graph_commit_next_row(graph);
634 graph->parents.size = graph->position = 0;
636 if (!graph_collapse(graph))
637 return FALSE;
639 return TRUE;
642 bool
643 graph_add_commit(struct graph *graph, struct graph_canvas *canvas,
644 const char *id, const char *parents, bool is_boundary)
646 graph->position = graph_find_column_by_id(&graph->row, id);
647 string_copy_rev(graph->id, id);
648 graph->is_boundary = is_boundary;
650 while ((parents = strchr(parents, ' '))) {
651 parents++;
652 if (!graph_add_parent(graph, parents))
653 return FALSE;
654 graph->has_parents = TRUE;
657 return TRUE;
660 const bool
661 graph_symbol_forks(struct graph_symbol *symbol)
663 if (!symbol->continued_down)
664 return false;
666 if (!symbol->continued_right)
667 return false;
669 if (!symbol->continued_up)
670 return false;
672 return true;
675 const bool
676 graph_symbol_cross_over(struct graph_symbol *symbol)
678 if (symbol->empty)
679 return false;
681 if (!symbol->continued_down)
682 return false;
684 if (!symbol->continued_up && !symbol->new_column && !symbol->below_commit)
685 return false;
687 if (symbol->shift_left)
688 return false;
690 if (symbol->parent_right && symbol->merge)
691 return true;
693 if (symbol->flanked)
694 return true;
696 return false;
699 const bool
700 graph_symbol_turn_left(struct graph_symbol *symbol)
702 if (symbol->matches_commit && symbol->continued_right && !symbol->continued_down)
703 return false;
705 if (symbol->continue_shift)
706 return false;
708 if (symbol->continued_up || symbol->new_column || symbol->below_commit) {
709 if (symbol->matches_commit)
710 return true;
712 if (symbol->shift_left)
713 return true;
716 return false;
719 const bool
720 graph_symbol_turn_down_cross_over(struct graph_symbol *symbol)
722 if (!symbol->continued_down)
723 return false;
725 if (!symbol->continued_right)
726 return false;
728 if (symbol->flanked)
729 return true;
731 if (symbol->merge)
732 return true;
734 return false;
737 const bool
738 graph_symbol_turn_down(struct graph_symbol *symbol)
740 if (!symbol->continued_down)
741 return false;
743 if (!symbol->continued_right)
744 return false;
746 return true;
749 const bool
750 graph_symbol_merge(struct graph_symbol *symbol)
752 if (symbol->continued_down)
753 return false;
755 if (!symbol->parent_down)
756 return false;
758 if (symbol->parent_right)
759 return false;
761 return true;
764 const bool
765 graph_symbol_multi_merge(struct graph_symbol *symbol)
767 if (!symbol->parent_down)
768 return false;
770 if (!symbol->parent_right)
771 return false;
773 return true;
776 const bool
777 graph_symbol_vertical_bar(struct graph_symbol *symbol)
779 if (symbol->empty)
780 return false;
782 if (symbol->shift_left)
783 return false;
785 if (!symbol->continued_down)
786 return false;
788 if (symbol->continued_up)
789 return true;
791 if (symbol->parent_right)
792 return false;
794 if (symbol->flanked)
795 return false;
797 if (symbol->continued_right)
798 return false;
800 return true;
803 const bool
804 graph_symbol_horizontal_bar(struct graph_symbol *symbol)
806 if (symbol->shift_left)
807 return true;
809 if (symbol->continued_down)
810 return false;
812 if (!symbol->parent_right && !symbol->continued_right)
813 return false;
815 if ((symbol->continued_up && !symbol->continued_up_left))
816 return false;
818 if (!symbol->below_commit)
819 return true;
821 return false;
824 const bool
825 graph_symbol_multi_branch(struct graph_symbol *symbol)
827 if (symbol->continued_down)
828 return false;
830 if (!symbol->continued_right)
831 return false;
833 if (symbol->below_shift)
834 return false;
836 if (symbol->continued_up || symbol->new_column || symbol->below_commit) {
837 if (symbol->matches_commit)
838 return true;
840 if (symbol->shift_left)
841 return true;
844 return false;
847 const char *
848 graph_symbol_to_utf8(struct graph_symbol *symbol)
850 if (symbol->commit) {
851 if (symbol->boundary)
852 return " ◯";
853 else if (symbol->initial)
854 return " ◎";
855 else if (symbol->merge)
856 return " ●";
857 return " ●";
860 if (graph_symbol_cross_over(symbol))
861 return "─│";
863 if (graph_symbol_vertical_bar(symbol))
864 return " │";
866 if (graph_symbol_turn_left(symbol))
867 return "─╯";
869 if (graph_symbol_multi_branch(symbol))
870 return "─┴";
872 if (graph_symbol_horizontal_bar(symbol))
873 return "──";
875 if (graph_symbol_forks(symbol))
876 return " ├";
878 if (graph_symbol_turn_down_cross_over(symbol))
879 return "─╭";
881 if (graph_symbol_turn_down(symbol))
882 return " ╭";
884 if (graph_symbol_merge(symbol))
885 return "─╮";
887 if (graph_symbol_multi_merge(symbol))
888 return "─┬";
890 return " ";
893 const chtype *
894 graph_symbol_to_chtype(struct graph_symbol *symbol)
896 static chtype graphics[2];
898 if (symbol->commit) {
899 graphics[0] = ' ';
900 if (symbol->boundary)
901 graphics[1] = 'o';
902 else if (symbol->initial)
903 graphics[1] = 'I';
904 else if (symbol->merge)
905 graphics[1] = 'M';
906 else
907 graphics[1] = 'o'; //ACS_DIAMOND; //'*';
908 return graphics;
910 } else if (graph_symbol_cross_over(symbol)) {
911 graphics[0] = ACS_HLINE;
912 graphics[1] = ACS_VLINE;
914 } else if (graph_symbol_vertical_bar(symbol)) {
915 graphics[0] = ' ';
916 graphics[1] = ACS_VLINE;
918 } else if (graph_symbol_turn_left(symbol)) {
919 graphics[0] = ACS_HLINE;
920 graphics[1] = ACS_LRCORNER;
922 } else if (graph_symbol_multi_branch(symbol)) {
923 graphics[0] = ACS_HLINE;
924 graphics[1] = ACS_BTEE;
926 } else if (graph_symbol_horizontal_bar(symbol)) {
927 graphics[0] = graphics[1] = ACS_HLINE;
929 } else if (graph_symbol_forks(symbol)) {
930 graphics[0] = ' ';
931 graphics[1] = ACS_LTEE;
933 } else if (graph_symbol_turn_down_cross_over(symbol)) {
934 graphics[0] = ACS_HLINE;
935 graphics[1] = ACS_ULCORNER;
937 } else if (graph_symbol_turn_down(symbol)) {
938 graphics[0] = ' ';
939 graphics[1] = ACS_ULCORNER;
941 } else if (graph_symbol_merge(symbol)) {
942 graphics[0] = ACS_HLINE;
943 graphics[1] = ACS_URCORNER;
945 } else if (graph_symbol_multi_merge(symbol)) {
946 graphics[0] = ACS_HLINE;
947 graphics[1] = ACS_TTEE;
949 } else {
950 graphics[0] = graphics[1] = ' ';
953 return graphics;
956 const char *
957 graph_symbol_to_ascii(struct graph_symbol *symbol)
959 if (symbol->commit) {
960 if (symbol->boundary)
961 return " o";
962 else if (symbol->initial)
963 return " I";
964 else if (symbol->merge)
965 return " M";
966 return " *";
969 if (graph_symbol_cross_over(symbol))
970 return "-|";
972 if (graph_symbol_vertical_bar(symbol))
973 return " |";
975 if (graph_symbol_turn_left(symbol))
976 return "-'";
978 if (graph_symbol_multi_branch(symbol))
979 return "-+";
981 if (graph_symbol_horizontal_bar(symbol))
982 return "--";
984 if (graph_symbol_forks(symbol))
985 return " +";
987 if (graph_symbol_turn_down_cross_over(symbol))
988 return "-.";
990 if (graph_symbol_turn_down(symbol))
991 return " .";
993 if (graph_symbol_merge(symbol))
994 return "-.";
996 if (graph_symbol_multi_merge(symbol))
997 return "-+";
999 return " ";
1002 /* vim: set ts=8 sw=8 noexpandtab: */