graph: automatically track display width of graph lines

All the output functions called by `graph_next_line()` currently keep track of how many printable chars they've written to the buffer, before calling `graph_pad_horizontally()` to pad the line with spaces. Some functions do this by incrementing a counter whenever they write to the buffer, and others do it by encoding an assumption about how many chars are written, as in: graph_pad_horizontally(graph, sb, graph->num_columns * 2); This adds a fair amount of noise to the functions' logic and is easily broken if one forgets to increment the right counter or update the calculations used for padding. To make this easier to use, I'm introducing a new struct called `graph_line` that wraps a `strbuf` and keeps count of its display width implicitly. `graph_next_line()` wraps this around the `struct strbuf *` it's given and passes a `struct graph_line *` to the output functions, which use its interface. The `graph_line` interface wraps the `strbuf_addch()`, `strbuf_addchars()` and `strbuf_addstr()` functions, and adds the `graph_line_write_column()` function for adding a single character with color formatting. The `graph_pad_horizontally()` function can then use the `width` field from the struct rather than taking a character count as a parameter. Signed-off-by: James Coglan <jcoglan@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

James Coglan committed Oct 15, 2019 at 23:47 UTC fbccf255f9449c2f617d875ebf78b9f1730fae5d
1 file changed +99 -95
graph.c
+99 -95
@@ -112,14 +112,42 @@ static const char *column_get_color_code(unsigned short color)
112 return column_colors[color];
113 }
114
115 -static void strbuf_write_column(struct strbuf *sb, const struct column *c,
116 - char col_char)
115 +struct graph_line {
116 + struct strbuf *buf;
117 + size_t width;
118 +};
119 +
120 +static inline void graph_line_addch(struct graph_line *line, int c)
121 +{
122 + strbuf_addch(line->buf, c);
123 + line->width++;
124 +}
125 +
126 +static inline void graph_line_addchars(struct graph_line *line, int c, size_t n)
127 +{
128 + strbuf_addchars(line->buf, c, n);
129 + line->width += n;
130 +}
131 +
132 +static inline void graph_line_addstr(struct graph_line *line, const char *s)
133 +{
134 + strbuf_addstr(line->buf, s);
135 + line->width += strlen(s);
136 +}
137 +
138 +static inline void graph_line_addcolor(struct graph_line *line, unsigned short color)
139 +{
140 + strbuf_addstr(line->buf, column_get_color_code(color));
141 +}
142 +
143 +static void graph_line_write_column(struct graph_line *line, const struct column *c,
144 + char col_char)
145 {
146 if (c->color < column_colors_max)
119 - strbuf_addstr(sb, column_get_color_code(c->color));
120 - strbuf_addch(sb, col_char);
147 + graph_line_addcolor(line, c->color);
148 + graph_line_addch(line, col_char);
149 if (c->color < column_colors_max)
122 - strbuf_addstr(sb, column_get_color_code(column_colors_max));
150 + graph_line_addcolor(line, column_colors_max);
151 }
152
153 struct git_graph {
@@ -686,8 +714,7 @@ static int graph_is_mapping_correct(struct git_graph *graph)
714 return 1;
715 }
716
689 -static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,
690 - int chars_written)
717 +static void graph_pad_horizontally(struct git_graph *graph, struct graph_line *line)
718 {
719 /*
720 * Add additional spaces to the end of the strbuf, so that all
@@ -696,12 +723,12 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,
723 * This way, fields printed to the right of the graph will remain
724 * aligned for the entire commit.
725 */
699 - if (chars_written < graph->width)
700 - strbuf_addchars(sb, ' ', graph->width - chars_written);
726 + if (line->width < graph->width)
727 + graph_line_addchars(line, ' ', graph->width - line->width);
728 }
729
730 static void graph_output_padding_line(struct git_graph *graph,
704 - struct strbuf *sb)
731 + struct graph_line *line)
732 {
733 int i;
734
@@ -719,11 +746,11 @@ static void graph_output_padding_line(struct git_graph *graph,
746 * Output a padding row, that leaves all branch lines unchanged
747 */
748 for (i = 0; i < graph->num_new_columns; i++) {
722 - strbuf_write_column(sb, &graph->new_columns[i], '|');
723 - strbuf_addch(sb, ' ');
749 + graph_line_write_column(line, &graph->new_columns[i], '|');
750 + graph_line_addch(line, ' ');
751 }
752
726 - graph_pad_horizontally(graph, sb, graph->num_new_columns * 2);
753 + graph_pad_horizontally(graph, line);
754 }
755
756
@@ -733,14 +760,14 @@ int graph_width(struct git_graph *graph)
760 }
761
762
736 -static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
763 +static void graph_output_skip_line(struct git_graph *graph, struct graph_line *line)
764 {
765 /*
766 * Output an ellipsis to indicate that a portion
767 * of the graph is missing.
768 */
742 - strbuf_addstr(sb, "...");
743 - graph_pad_horizontally(graph, sb, 3);
769 + graph_line_addstr(line, "...");
770 + graph_pad_horizontally(graph, line);
771
772 if (graph->num_parents >= 3 &&
773 graph->commit_index < (graph->num_columns - 1))
@@ -750,11 +777,10 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
777 }
778
779 static void graph_output_pre_commit_line(struct git_graph *graph,
753 - struct strbuf *sb)
780 + struct graph_line *line)
781 {
782 int num_expansion_rows;
783 int i, seen_this;
757 - int chars_written;
784
785 /*
786 * This function formats a row that increases the space around a commit
@@ -777,14 +803,12 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
803 * Output the row
804 */
805 seen_this = 0;
780 - chars_written = 0;
806 for (i = 0; i < graph->num_columns; i++) {
807 struct column *col = &graph->columns[i];
808 if (col->commit == graph->commit) {
809 seen_this = 1;
785 - strbuf_write_column(sb, col, '|');
786 - strbuf_addchars(sb, ' ', graph->expansion_row);
787 - chars_written += 1 + graph->expansion_row;
810 + graph_line_write_column(line, col, '|');
811 + graph_line_addchars(line, ' ', graph->expansion_row);
812 } else if (seen_this && (graph->expansion_row == 0)) {
813 /*
814 * This is the first line of the pre-commit output.
@@ -797,22 +821,18 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
821 */
822 if (graph->prev_state == GRAPH_POST_MERGE &&
823 graph->prev_commit_index < i)
800 - strbuf_write_column(sb, col, '\\');
824 + graph_line_write_column(line, col, '\\');
825 else
802 - strbuf_write_column(sb, col, '|');
803 - chars_written++;
826 + graph_line_write_column(line, col, '|');
827 } else if (seen_this && (graph->expansion_row > 0)) {
805 - strbuf_write_column(sb, col, '\\');
806 - chars_written++;
828 + graph_line_write_column(line, col, '\\');
829 } else {
808 - strbuf_write_column(sb, col, '|');
809 - chars_written++;
830 + graph_line_write_column(line, col, '|');
831 }
811 - strbuf_addch(sb, ' ');
812 - chars_written++;
832 + graph_line_addch(line, ' ');
833 }
834
815 - graph_pad_horizontally(graph, sb, chars_written);
835 + graph_pad_horizontally(graph, line);
836
837 /*
838 * Increment graph->expansion_row,
@@ -823,7 +843,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
843 graph_update_state(graph, GRAPH_COMMIT);
844 }
845
826 -static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)
846 +static void graph_output_commit_char(struct git_graph *graph, struct graph_line *line)
847 {
848 /*
849 * For boundary commits, print 'o'
@@ -831,22 +851,20 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)
851 */
852 if (graph->commit->object.flags & BOUNDARY) {
853 assert(graph->revs->boundary);
834 - strbuf_addch(sb, 'o');
854 + graph_line_addch(line, 'o');
855 return;
856 }
857
858 /*
859 * get_revision_mark() handles all other cases without assert()
860 */
841 - strbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit));
861 + graph_line_addstr(line, get_revision_mark(graph->revs, graph->commit));
862 }
863
864 /*
845 - * Draw the horizontal dashes of an octopus merge and return the number of
846 - * characters written.
865 + * Draw the horizontal dashes of an octopus merge.
866 */
848 -static int graph_draw_octopus_merge(struct git_graph *graph,
849 - struct strbuf *sb)
867 +static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line *line)
868 {
869 /*
870 * Here dashless_parents represents the number of parents which don't
@@ -886,17 +904,16 @@ static int graph_draw_octopus_merge(struct git_graph *graph,
904
905 int i;
906 for (i = 0; i < dashful_parents; i++) {
889 - strbuf_write_column(sb, &graph->new_columns[i+first_col], '-');
890 - strbuf_write_column(sb, &graph->new_columns[i+first_col],
891 - i == dashful_parents-1 ? '.' : '-');
907 + graph_line_write_column(line, &graph->new_columns[i+first_col], '-');
908 + graph_line_write_column(line, &graph->new_columns[i+first_col],
909 + i == dashful_parents-1 ? '.' : '-');
910 }
893 - return 2 * dashful_parents;
911 }
912
896 -static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
913 +static void graph_output_commit_line(struct git_graph *graph, struct graph_line *line)
914 {
915 int seen_this = 0;
899 - int i, chars_written;
916 + int i;
917
918 /*
919 * Output the row containing this commit
@@ -906,7 +923,6 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
923 * children that we have already processed.)
924 */
925 seen_this = 0;
909 - chars_written = 0;
926 for (i = 0; i <= graph->num_columns; i++) {
927 struct column *col = &graph->columns[i];
928 struct commit *col_commit;
@@ -920,15 +936,12 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
936
937 if (col_commit == graph->commit) {
938 seen_this = 1;
923 - graph_output_commit_char(graph, sb);
924 - chars_written++;
939 + graph_output_commit_char(graph, line);
940
941 if (graph->num_parents > 2)
927 - chars_written += graph_draw_octopus_merge(graph,
928 - sb);
942 + graph_draw_octopus_merge(graph, line);
943 } else if (seen_this && (graph->num_parents > 2)) {
930 - strbuf_write_column(sb, col, '\\');
931 - chars_written++;
944 + graph_line_write_column(line, col, '\\');
945 } else if (seen_this && (graph->num_parents == 2)) {
946 /*
947 * This is a 2-way merge commit.
@@ -945,19 +958,16 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
958 */
959 if (graph->prev_state == GRAPH_POST_MERGE &&
960 graph->prev_commit_index < i)
948 - strbuf_write_column(sb, col, '\\');
961 + graph_line_write_column(line, col, '\\');
962 else
950 - strbuf_write_column(sb, col, '|');
951 - chars_written++;
963 + graph_line_write_column(line, col, '|');
964 } else {
953 - strbuf_write_column(sb, col, '|');
954 - chars_written++;
965 + graph_line_write_column(line, col, '|');
966 }
956 - strbuf_addch(sb, ' ');
957 - chars_written++;
967 + graph_line_addch(line, ' ');
968 }
969
960 - graph_pad_horizontally(graph, sb, chars_written);
970 + graph_pad_horizontally(graph, line);
971
972 /*
973 * Update graph->state
@@ -981,15 +991,14 @@ static struct column *find_new_column_by_commit(struct git_graph *graph,
991 return NULL;
992 }
993
984 -static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
994 +static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)
995 {
996 int seen_this = 0;
987 - int i, j, chars_written;
997 + int i, j;
998
999 /*
1000 * Output the post-merge row
1001 */
992 - chars_written = 0;
1002 for (i = 0; i <= graph->num_columns; i++) {
1003 struct column *col = &graph->columns[i];
1004 struct commit *col_commit;
@@ -1016,29 +1025,25 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf
1025 par_column = find_new_column_by_commit(graph, parents->item);
1026 assert(par_column);
1027
1019 - strbuf_write_column(sb, par_column, '|');
1020 - chars_written++;
1028 + graph_line_write_column(line, par_column, '|');
1029 for (j = 0; j < graph->num_parents - 1; j++) {
1030 parents = next_interesting_parent(graph, parents);
1031 assert(parents);
1032 par_column = find_new_column_by_commit(graph, parents->item);
1033 assert(par_column);
1026 - strbuf_write_column(sb, par_column, '\\');
1027 - strbuf_addch(sb, ' ');
1034 + graph_line_write_column(line, par_column, '\\');
1035 + graph_line_addch(line, ' ');
1036 }
1029 - chars_written += j * 2;
1037 } else if (seen_this) {
1031 - strbuf_write_column(sb, col, '\\');
1032 - strbuf_addch(sb, ' ');
1033 - chars_written += 2;
1038 + graph_line_write_column(line, col, '\\');
1039 + graph_line_addch(line, ' ');
1040 } else {
1035 - strbuf_write_column(sb, col, '|');
1036 - strbuf_addch(sb, ' ');
1037 - chars_written += 2;
1041 + graph_line_write_column(line, col, '|');
1042 + graph_line_addch(line, ' ');
1043 }
1044 }
1045
1041 - graph_pad_horizontally(graph, sb, chars_written);
1046 + graph_pad_horizontally(graph, line);
1047
1048 /*
1049 * Update graph->state
@@ -1049,7 +1054,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf
1054 graph_update_state(graph, GRAPH_COLLAPSING);
1055 }
1056
1052 -static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)
1057 +static void graph_output_collapsing_line(struct git_graph *graph, struct graph_line *line)
1058 {
1059 int i;
1060 short used_horizontal = 0;
@@ -1159,9 +1164,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
1164 for (i = 0; i < graph->mapping_size; i++) {
1165 int target = graph->new_mapping[i];
1166 if (target < 0)
1162 - strbuf_addch(sb, ' ');
1167 + graph_line_addch(line, ' ');
1168 else if (target * 2 == i)
1164 - strbuf_write_column(sb, &graph->new_columns[target], '|');
1169 + graph_line_write_column(line, &graph->new_columns[target], '|');
1170 else if (target == horizontal_edge_target &&
1171 i != horizontal_edge - 1) {
1172 /*
@@ -1172,16 +1177,16 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
1177 if (i != (target * 2)+3)
1178 graph->new_mapping[i] = -1;
1179 used_horizontal = 1;
1175 - strbuf_write_column(sb, &graph->new_columns[target], '_');
1180 + graph_line_write_column(line, &graph->new_columns[target], '_');
1181 } else {
1182 if (used_horizontal && i < horizontal_edge)
1183 graph->new_mapping[i] = -1;
1179 - strbuf_write_column(sb, &graph->new_columns[target], '/');
1184 + graph_line_write_column(line, &graph->new_columns[target], '/');
1185
1186 }
1187 }
1188
1184 - graph_pad_horizontally(graph, sb, graph->mapping_size);
1189 + graph_pad_horizontally(graph, line);
1190
1191 /*
1192 * Swap mapping and new_mapping
@@ -1199,24 +1204,26 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
1204
1205 int graph_next_line(struct git_graph *graph, struct strbuf *sb)
1206 {
1207 + struct graph_line line = { .buf = sb, .width = 0 };
1208 +
1209 switch (graph->state) {
1210 case GRAPH_PADDING:
1204 - graph_output_padding_line(graph, sb);
1211 + graph_output_padding_line(graph, &line);
1212 return 0;
1213 case GRAPH_SKIP:
1207 - graph_output_skip_line(graph, sb);
1214 + graph_output_skip_line(graph, &line);
1215 return 0;
1216 case GRAPH_PRE_COMMIT:
1210 - graph_output_pre_commit_line(graph, sb);
1217 + graph_output_pre_commit_line(graph, &line);
1218 return 0;
1219 case GRAPH_COMMIT:
1213 - graph_output_commit_line(graph, sb);
1220 + graph_output_commit_line(graph, &line);
1221 return 1;
1222 case GRAPH_POST_MERGE:
1216 - graph_output_post_merge_line(graph, sb);
1223 + graph_output_post_merge_line(graph, &line);
1224 return 0;
1225 case GRAPH_COLLAPSING:
1219 - graph_output_collapsing_line(graph, sb);
1226 + graph_output_collapsing_line(graph, &line);
1227 return 0;
1228 }
1229
@@ -1227,7 +1234,7 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)
1234 static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
1235 {
1236 int i;
1230 - int chars_written = 0;
1237 + struct graph_line line = { .buf = sb, .width = 0 };
1238
1239 if (graph->state != GRAPH_COMMIT) {
1240 graph_next_line(graph, sb);
@@ -1244,20 +1251,17 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
1251 for (i = 0; i < graph->num_columns; i++) {
1252 struct column *col = &graph->columns[i];
1253
1247 - strbuf_write_column(sb, col, '|');
1248 - chars_written++;
1254 + graph_line_write_column(&line, col, '|');
1255
1256 if (col->commit == graph->commit && graph->num_parents > 2) {
1257 int len = (graph->num_parents - 2) * 2;
1252 - strbuf_addchars(sb, ' ', len);
1253 - chars_written += len;
1258 + graph_line_addchars(&line, ' ', len);
1259 } else {
1255 - strbuf_addch(sb, ' ');
1256 - chars_written++;
1260 + graph_line_addch(&line, ' ');
1261 }
1262 }
1263
1260 - graph_pad_horizontally(graph, sb, chars_written);
1264 + graph_pad_horizontally(graph, &line);
1265
1266 /*
1267 * Update graph->prev_state since we have output a padding line