Partly restore old refresh behavior
[tig.git] / src / graph.c
blob4af5e4ff6fca7532e970eaa82421b58c4908067e
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 void
150 done_graph(struct graph *graph)
152 free(graph->prev_row.columns);
153 free(graph->row.columns);
154 free(graph->next_row.columns);
155 free(graph->parents.columns);
156 memset(graph, 0, sizeof(*graph));
159 #define graph_column_has_commit(col) ((col)->id[0])
161 static size_t
162 graph_find_column_by_id(struct graph_row *row, const char *id)
164 size_t free_column = row->size;
165 size_t i;
167 for (i = 0; i < row->size; i++) {
168 if (!graph_column_has_commit(&row->columns[i]) && free_column == row->size)
169 free_column = i;
170 else if (!strcmp(row->columns[i].id, id))
171 return i;
174 return free_column;
177 static size_t
178 graph_find_free_column(struct graph_row *row)
180 size_t i;
182 for (i = 0; i < row->size; i++) {
183 if (!graph_column_has_commit(&row->columns[i]))
184 return i;
187 return row->size;
190 static struct graph_column *
191 graph_insert_column(struct graph *graph, struct graph_row *row, size_t pos, const char *id)
193 struct graph_column *column;
195 if (!realloc_graph_columns(&row->columns, row->size, 1))
196 return NULL;
198 column = &row->columns[pos];
199 if (pos < row->size) {
200 memmove(column + 1, column, sizeof(*column) * (row->size - pos));
203 row->size++;
204 memset(column, 0, sizeof(*column));
205 string_copy_rev(column->id, id);
206 column->symbol.boundary = !!graph->is_boundary;
208 return column;
211 struct graph_column *
212 graph_add_parent(struct graph *graph, const char *parent)
214 return graph_insert_column(graph, &graph->parents, graph->parents.size, parent);
217 static bool
218 graph_needs_expansion(struct graph *graph)
220 return graph->position + graph->parents.size > graph->row.size;
223 static bool
224 graph_expand(struct graph *graph)
226 while (graph_needs_expansion(graph)) {
227 if (!graph_insert_column(graph, &graph->prev_row, graph->prev_row.size, ""))
228 return FALSE;
230 if (!graph_insert_column(graph, &graph->row, graph->row.size, ""))
231 return FALSE;
233 if (!graph_insert_column(graph, &graph->next_row, graph->next_row.size, ""))
234 return FALSE;
237 return TRUE;
240 static bool
241 graph_needs_collapsing(struct graph *graph)
243 return graph->row.size > 1
244 && !graph_column_has_commit(&graph->row.columns[graph->row.size - 1]);
247 static bool
248 graph_collapse(struct graph *graph)
250 while (graph_needs_collapsing(graph)) {
251 graph->prev_row.size--;
252 graph->row.size--;
253 graph->next_row.size--;
256 return TRUE;
259 static void
260 graph_canvas_append_symbol(struct graph *graph, struct graph_symbol *symbol)
262 struct graph_canvas *canvas = graph->canvas;
264 if (realloc_graph_symbols(&canvas->symbols, canvas->size, 1))
265 canvas->symbols[canvas->size++] = *symbol;
268 static void
269 graph_row_clear_commit(struct graph_row *row, const char *id)
271 int i;
273 for (i = 0; i < row->size; i++) {
274 if (strcmp(row->columns[i].id, id) == 0) {
275 row->columns[i].id[0] = 0;
280 static void
281 graph_insert_parents(struct graph *graph)
283 struct graph_row *prev_row = &graph->prev_row;
284 struct graph_row *row = &graph->row;
285 struct graph_row *next_row = &graph->next_row;
286 struct graph_row *parents = &graph->parents;
287 int i;
289 for (i = 0; i < parents->size; i++) {
290 struct graph_column *new = &parents->columns[i];
292 if (graph_column_has_commit(new)) {
293 size_t match = graph_find_free_column(next_row);
295 if (match == next_row->size && next_row->columns[next_row->size - 1].id) {
296 graph_insert_column(graph, next_row, next_row->size, new->id);
297 graph_insert_column(graph, row, row->size, "");
298 graph_insert_column(graph, prev_row, prev_row->size, "");
299 } else {
300 next_row->columns[match] = *new;
306 static bool
307 commit_is_in_row(const char *id, struct graph_row *row)
309 int i;
311 for (i = 0; i < row->size; i++) {
312 if (!graph_column_has_commit(&row->columns[i]))
313 continue;
315 if (strcmp(id, row->columns[i].id) == 0)
316 return true;
318 return false;
321 static void
322 graph_remove_collapsed_columns(struct graph *graph)
324 struct graph_row *row = &graph->next_row;
325 int i;
327 for (i = row->size - 1; i > 0; i--) {
328 if (i == graph->position)
329 continue;
331 if (i == graph->position + 1)
332 continue;
334 if (strcmp(row->columns[i].id, graph->id) == 0)
335 continue;
337 if (strcmp(row->columns[i].id, row->columns[i - 1].id) != 0)
338 continue;
340 if (commit_is_in_row(row->columns[i].id, &graph->parents) && !graph_column_has_commit(&graph->prev_row.columns[i]))
341 continue;
343 if (strcmp(row->columns[i - 1].id, graph->prev_row.columns[i - 1].id) != 0 || graph->prev_row.columns[i - 1].symbol.shift_left)
344 row->columns[i] = row->columns[i + 1];
348 static void
349 graph_fill_empty_columns(struct graph *graph)
351 struct graph_row *row = &graph->next_row;
352 int i;
354 for (i = row->size - 2; i >= 0; i--) {
355 if (!graph_column_has_commit(&row->columns[i])) {
356 row->columns[i] = row->columns[i + 1];
361 static void
362 graph_generate_next_row(struct graph *graph)
364 graph_row_clear_commit(&graph->next_row, graph->id);
365 graph_insert_parents(graph);
366 graph_remove_collapsed_columns(graph);
367 graph_fill_empty_columns(graph);
370 static int
371 commits_in_row(struct graph_row *row)
373 int count = 0;
374 int i;
376 for (i = 0; i < row->size;i++) {
377 if (graph_column_has_commit(&row->columns[i]))
378 count++;
381 return count;
384 static void
385 graph_commit_next_row(struct graph *graph)
387 int i;
389 for (i = 0; i < graph->row.size; i++) {
390 graph->prev_row.columns[i] = graph->row.columns[i];
392 if (i == graph->position && commits_in_row(&graph->parents) > 0)
393 graph->prev_row.columns[i] = graph->next_row.columns[i];
395 if (!graph_column_has_commit(&graph->prev_row.columns[i]))
396 graph->prev_row.columns[i] = graph->next_row.columns[i];
398 graph->row.columns[i] = graph->next_row.columns[i];
401 graph->prev_position = graph->position;
404 static bool
405 continued_down(struct graph_row *row, struct graph_row *next_row, int pos)
407 if (strcmp(row->columns[pos].id, next_row->columns[pos].id) != 0)
408 return false;
410 if (row->columns[pos].symbol.shift_left)
411 return false;
413 return true;
416 static bool
417 shift_left(struct graph_row *row, struct graph_row *prev_row, int pos)
419 int i;
421 if (!graph_column_has_commit(&row->columns[pos]))
422 return false;
424 for (i = pos - 1; i >= 0; i--) {
425 if (!graph_column_has_commit(&row->columns[i]))
426 continue;
428 if (strcmp(row->columns[i].id, row->columns[pos].id) != 0)
429 continue;
431 if (!continued_down(prev_row, row, i))
432 return true;
434 break;
437 return false;
440 static bool
441 new_column(struct graph_row *row, struct graph_row *prev_row, int pos)
443 int i;
445 if (!graph_column_has_commit(&prev_row->columns[pos]))
446 return true;
448 for (i = pos; i < row->size; i++) {
449 if (strcmp(row->columns[pos].id, prev_row->columns[i].id) == 0)
450 return false;
453 return true;
456 static bool
457 continued_right(struct graph_row *row, int pos, int commit_pos)
459 int i, end;
461 if (pos < commit_pos)
462 end = commit_pos;
463 else
464 end = row->size;
466 for (i = pos + 1; i < end; i++) {
467 if (strcmp(row->columns[pos].id, row->columns[i].id) == 0)
468 return true;
471 return false;
474 static bool
475 continued_left(struct graph_row *row, int pos, int commit_pos)
477 int i, start;
479 if (pos < commit_pos)
480 start = 0;
481 else
482 start = commit_pos;
484 for (i = start; i < pos; i++) {
485 if (!graph_column_has_commit(&row->columns[i]))
486 continue;
488 if (strcmp(row->columns[pos].id, row->columns[i].id) == 0)
489 return true;
492 return false;
495 static bool
496 parent_down(struct graph_row *parents, struct graph_row *next_row, int pos)
498 int parent;
500 for (parent = 0; parent < parents->size; parent++) {
501 if (!graph_column_has_commit(&parents->columns[parent]))
502 continue;
504 if (strcmp(parents->columns[parent].id, next_row->columns[pos].id) == 0)
505 return true;
508 return false;
511 static bool
512 parent_right(struct graph_row *parents, struct graph_row *row, struct graph_row *next_row, int pos)
514 int parent, i;
516 for (parent = 0; parent < parents->size; parent++) {
517 if (!graph_column_has_commit(&parents->columns[parent]))
518 continue;
520 for (i = pos + 1; i < next_row->size; i++) {
521 if (strcmp(parents->columns[parent].id, next_row->columns[i].id) != 0)
522 continue;
524 if (strcmp(parents->columns[parent].id, row->columns[i].id) != 0)
525 return true;
529 return false;
532 static bool
533 flanked(struct graph_row *row, int pos, int commit_pos, const char *commit_id)
535 int i, start, end;
537 if (pos < commit_pos) {
538 start = 0;
539 end = pos;
540 } else {
541 start = pos + 1;
542 end = row->size;
545 for (i = start; i < end; i++) {
546 if (strcmp(row->columns[i].id, commit_id) == 0)
547 return true;
550 return false;
553 static bool
554 below_commit(int pos, struct graph *graph)
556 if (!pos == graph->prev_position)
557 return false;
559 if (!strcmp(graph->row.columns[pos].id, graph->prev_row.columns[pos].id) == 0)
560 return false;
562 return true;
565 static void
566 graph_generate_symbols(struct graph *graph)
568 struct graph_row *prev_row = &graph->prev_row;
569 struct graph_row *row = &graph->row;
570 struct graph_row *next_row = &graph->next_row;
571 struct graph_row *parents = &graph->parents;
572 int pos;
574 for (pos = 0; pos < row->size; pos++) {
575 struct graph_column *column = &row->columns[pos];
576 struct graph_symbol *symbol = &column->symbol;
577 char *id = next_row->columns[pos].id;
579 symbol->commit = (pos == graph->position);
580 symbol->boundary = (pos == graph->position && next_row->columns[pos].symbol.boundary);
581 symbol->initial = (commits_in_row(parents) < 1);
582 symbol->merge = (commits_in_row(parents) > 1);
584 symbol->continued_down = continued_down(row, next_row, pos);
585 symbol->continued_up = continued_down(prev_row, row, pos);
586 symbol->continued_right = continued_right(row, pos, graph->position);
587 symbol->continued_left = continued_left(row, pos, graph->position);
588 symbol->continued_up_left = continued_left(prev_row, pos, prev_row->size);
590 symbol->parent_down = parent_down(parents, next_row, pos);
591 symbol->parent_right = (pos > graph->position && parent_right(parents, row, next_row, pos));
593 symbol->below_commit = below_commit(pos, graph);
594 symbol->flanked = flanked(row, pos, graph->position, graph->id);
595 symbol->next_right = continued_right(next_row, pos, 0);
596 symbol->matches_commit = (strcmp(column->id, graph->id) == 0);
598 symbol->shift_left = shift_left(row, prev_row, pos);
599 symbol->continue_shift = shift_left(row, prev_row, pos + 1);
600 symbol->below_shift = prev_row->columns[pos].symbol.shift_left;
602 symbol->new_column = new_column(row, prev_row, pos);
603 symbol->empty = (!graph_column_has_commit(&row->columns[pos]));
605 if (graph_column_has_commit(column)) {
606 id = column->id;
608 symbol->color = get_color(graph, id);
610 graph_canvas_append_symbol(graph, symbol);
613 colors_remove_id(&graph->colors, graph->id);
616 bool
617 graph_render_parents(struct graph *graph)
619 if (graph->parents.size == 0 &&
620 !graph_add_parent(graph, ""))
621 return FALSE;
623 if (!graph_expand(graph))
624 return FALSE;
626 graph_generate_next_row(graph);
627 graph_generate_symbols(graph);
628 graph_commit_next_row(graph);
630 graph->parents.size = graph->position = 0;
632 if (!graph_collapse(graph))
633 return FALSE;
635 return TRUE;
638 bool
639 graph_add_commit(struct graph *graph, struct graph_canvas *canvas,
640 const char *id, const char *parents, bool is_boundary)
642 graph->position = graph_find_column_by_id(&graph->row, id);
643 string_copy_rev(graph->id, id);
644 graph->canvas = canvas;
645 graph->is_boundary = is_boundary;
647 while ((parents = strchr(parents, ' '))) {
648 parents++;
649 if (!graph_add_parent(graph, parents))
650 return FALSE;
651 graph->has_parents = TRUE;
654 return TRUE;
657 const bool
658 graph_symbol_forks(struct graph_symbol *symbol)
660 if (!symbol->continued_down)
661 return false;
663 if (!symbol->continued_right)
664 return false;
666 if (!symbol->continued_up)
667 return false;
669 return true;
672 const bool
673 graph_symbol_cross_over(struct graph_symbol *symbol)
675 if (symbol->empty)
676 return false;
678 if (!symbol->continued_down)
679 return false;
681 if (!symbol->continued_up && !symbol->new_column && !symbol->below_commit)
682 return false;
684 if (symbol->shift_left)
685 return false;
687 if (symbol->parent_right && symbol->merge)
688 return true;
690 if (symbol->flanked)
691 return true;
693 return false;
696 const bool
697 graph_symbol_turn_left(struct graph_symbol *symbol)
699 if (symbol->matches_commit && symbol->continued_right && !symbol->continued_down)
700 return false;
702 if (symbol->continue_shift)
703 return false;
705 if (symbol->continued_up || symbol->new_column || symbol->below_commit) {
706 if (symbol->matches_commit)
707 return true;
709 if (symbol->shift_left)
710 return true;
713 return false;
716 const bool
717 graph_symbol_turn_down_cross_over(struct graph_symbol *symbol)
719 if (!symbol->continued_down)
720 return false;
722 if (!symbol->continued_right)
723 return false;
725 if (symbol->flanked)
726 return true;
728 if (symbol->merge)
729 return true;
731 return false;
734 const bool
735 graph_symbol_turn_down(struct graph_symbol *symbol)
737 if (!symbol->continued_down)
738 return false;
740 if (!symbol->continued_right)
741 return false;
743 return true;
746 const bool
747 graph_symbol_merge(struct graph_symbol *symbol)
749 if (symbol->continued_down)
750 return false;
752 if (!symbol->parent_down)
753 return false;
755 if (symbol->parent_right)
756 return false;
758 return true;
761 const bool
762 graph_symbol_multi_merge(struct graph_symbol *symbol)
764 if (!symbol->parent_down)
765 return false;
767 if (!symbol->parent_right)
768 return false;
770 return true;
773 const bool
774 graph_symbol_vertical_bar(struct graph_symbol *symbol)
776 if (symbol->empty)
777 return false;
779 if (symbol->shift_left)
780 return false;
782 if (!symbol->continued_down)
783 return false;
785 if (symbol->continued_up)
786 return true;
788 if (symbol->parent_right)
789 return false;
791 if (symbol->flanked)
792 return false;
794 if (symbol->continued_right)
795 return false;
797 return true;
800 const bool
801 graph_symbol_horizontal_bar(struct graph_symbol *symbol)
803 if (symbol->shift_left)
804 return true;
806 if (symbol->continued_down)
807 return false;
809 if (!symbol->parent_right && !symbol->continued_right)
810 return false;
812 if ((symbol->continued_up && !symbol->continued_up_left))
813 return false;
815 if (!symbol->below_commit)
816 return true;
818 return false;
821 const bool
822 graph_symbol_multi_branch(struct graph_symbol *symbol)
824 if (symbol->continued_down)
825 return false;
827 if (!symbol->continued_right)
828 return false;
830 if (symbol->below_shift)
831 return false;
833 if (symbol->continued_up || symbol->new_column || symbol->below_commit) {
834 if (symbol->matches_commit)
835 return true;
837 if (symbol->shift_left)
838 return true;
841 return false;
844 const char *
845 graph_symbol_to_utf8(struct graph_symbol *symbol)
847 if (symbol->commit) {
848 if (symbol->boundary)
849 return " ◯";
850 else if (symbol->initial)
851 return " ◎";
852 else if (symbol->merge)
853 return " ●";
854 return " ●";
857 if (graph_symbol_cross_over(symbol))
858 return "─│";
860 if (graph_symbol_vertical_bar(symbol))
861 return " │";
863 if (graph_symbol_turn_left(symbol))
864 return "─╯";
866 if (graph_symbol_multi_branch(symbol))
867 return "─┴";
869 if (graph_symbol_horizontal_bar(symbol))
870 return "──";
872 if (graph_symbol_forks(symbol))
873 return " ├";
875 if (graph_symbol_turn_down_cross_over(symbol))
876 return "─╭";
878 if (graph_symbol_turn_down(symbol))
879 return " ╭";
881 if (graph_symbol_merge(symbol))
882 return "─╮";
884 if (graph_symbol_multi_merge(symbol))
885 return "─┬";
887 return " ";
890 const chtype *
891 graph_symbol_to_chtype(struct graph_symbol *symbol)
893 static chtype graphics[2];
895 if (symbol->commit) {
896 graphics[0] = ' ';
897 if (symbol->boundary)
898 graphics[1] = 'o';
899 else if (symbol->initial)
900 graphics[1] = 'I';
901 else if (symbol->merge)
902 graphics[1] = 'M';
903 else
904 graphics[1] = 'o'; //ACS_DIAMOND; //'*';
905 return graphics;
907 } else if (graph_symbol_cross_over(symbol)) {
908 graphics[0] = ACS_HLINE;
909 graphics[1] = ACS_VLINE;
911 } else if (graph_symbol_vertical_bar(symbol)) {
912 graphics[0] = ' ';
913 graphics[1] = ACS_VLINE;
915 } else if (graph_symbol_turn_left(symbol)) {
916 graphics[0] = ACS_HLINE;
917 graphics[1] = ACS_LRCORNER;
919 } else if (graph_symbol_multi_branch(symbol)) {
920 graphics[0] = ACS_HLINE;
921 graphics[1] = ACS_BTEE;
923 } else if (graph_symbol_horizontal_bar(symbol)) {
924 graphics[0] = graphics[1] = ACS_HLINE;
926 } else if (graph_symbol_forks(symbol)) {
927 graphics[0] = ' ';
928 graphics[1] = ACS_LTEE;
930 } else if (graph_symbol_turn_down_cross_over(symbol)) {
931 graphics[0] = ACS_HLINE;
932 graphics[1] = ACS_ULCORNER;
934 } else if (graph_symbol_turn_down(symbol)) {
935 graphics[0] = ' ';
936 graphics[1] = ACS_ULCORNER;
938 } else if (graph_symbol_merge(symbol)) {
939 graphics[0] = ACS_HLINE;
940 graphics[1] = ACS_URCORNER;
942 } else if (graph_symbol_multi_merge(symbol)) {
943 graphics[0] = ACS_HLINE;
944 graphics[1] = ACS_TTEE;
946 } else {
947 graphics[0] = graphics[1] = ' ';
950 return graphics;
953 const char *
954 graph_symbol_to_ascii(struct graph_symbol *symbol)
956 if (symbol->commit) {
957 if (symbol->boundary)
958 return " o";
959 else if (symbol->initial)
960 return " I";
961 else if (symbol->merge)
962 return " M";
963 return " *";
966 if (graph_symbol_cross_over(symbol))
967 return "-|";
969 if (graph_symbol_vertical_bar(symbol))
970 return " |";
972 if (graph_symbol_turn_left(symbol))
973 return "-'";
975 if (graph_symbol_multi_branch(symbol))
976 return "-+";
978 if (graph_symbol_horizontal_bar(symbol))
979 return "--";
981 if (graph_symbol_forks(symbol))
982 return " +";
984 if (graph_symbol_turn_down_cross_over(symbol))
985 return "-.";
987 if (graph_symbol_turn_down(symbol))
988 return " .";
990 if (graph_symbol_merge(symbol))
991 return "-.";
993 if (graph_symbol_multi_merge(symbol))
994 return "-+";
996 return " ";
999 /* vim: set ts=8 sw=8 noexpandtab: */