On 11/10/2019 17:02, Johannes Schindelin wrote: > Hi, > > On Thu, 10 Oct 2019, Denton Liu wrote: > >> On Fri, Oct 11, 2019 at 10:42:20AM +0900, Junio C Hamano wrote: >>> Denton Liu <liu.denton@xxxxxxxxx> writes: >>> >>>> static int calculate_width(const struct strbuf *row) >>>> { >>>> int in_termcode = 0; >>>> int width = 0; >>>> int i; >>>> >>>> for (i = 0; i < row.len; i++) { >>>> if (row.buf[i] == '\033') >>>> in_termcode = 1; >>>> >>>> if (!in_termcode) >>>> width++; >>>> else if (row.buf[i] == 'm') >>>> in_termcode = 0; >>>> } >>>> } >>> >>> Not every byte that is outside the escape sequence contributes to >>> one display columns. You would want to take a look at utf8_width() >>> for inspiration. >>> >> >> Heh, I guess you're right. Looking right below the definition of >> utf8_width, I realised we have the utf8_strnwidth function. We should be >> able to just call >> >> utf8_strnwidth(row.buf, row.len, 1); > > Correct me if I'm wrong, but don't we want to keep track of the columns > *only* in the part with the actual line graph, i.e. we're not at all > interested in the onelines' widths? > > If so, I could imagine that a good idea would be to introduce > > struct graphbuf { > struct strbuf buf; > int width; > }; > > and then introduce wrappers for `_addch()` and whatever else is used in > `graph.c`, these wrappers will increment the width together with the > `buf.len` field, and one additional helper that adds color sequences to > that graphbuf that leaves `width` unchanged. That's exactly what I've spent this afternoon doing, in response to the feedback here. I took into consideration that: - We don't want a general solution to this problem for everything `strbuf` could be used for; it only needs to address the graph padding problem. - We only want to count printing characters, not color formatting sequences. - We only need to consider the width of a small set of characters: { | / \ _ - . * }. We don't need to worry about Unicode, and the simple character counting in graph.c was working fine. - Attempting to count the characters in a `strbuf` after the fact is problematic, because sometimes the line prefix (`diff_options.line_prefix`) is included when `graph_pad_horizontally()` is called, and sometimes the prefix has already been sent to stdout and is not included in the `strbuf`. We need to know how many printing characters were added only during `graph_next_line()`. To this end I've prepared a different implementation that introduces the indirection described above, and does not modify `strbuf`: diff --git a/graph.c b/graph.c index f53135485f..24bf1f4fe1 100644 --- a/graph.c +++ b/graph.c @@ -112,14 +112,38 @@ static const char *column_get_color_code(unsigned short color) return column_colors[color]; } -static void strbuf_write_column(struct strbuf *sb, const struct column *c, - char col_char) +struct graph_strbuf { + struct strbuf *buf; + size_t width; +}; + +static inline void graph_strbuf_addch(struct graph_strbuf *sb, int c) +{ + strbuf_addch(sb->buf, c); + sb->width++; +} + +void graph_strbuf_addchars(struct graph_strbuf *sb, int c, size_t n) +{ + strbuf_addchars(sb->buf, c, n); + sb->width += n; +} + +static inline void graph_strbuf_addstr(struct graph_strbuf *sb, const char *s) +{ + strbuf_addstr(sb->buf, s); + sb->width += strlen(s); +} + +static void graph_strbuf_write_column(struct graph_strbuf *sb, const struct column *c, + char col_char) { if (c->color < column_colors_max) - strbuf_addstr(sb, column_get_color_code(c->color)); - strbuf_addch(sb, col_char); + strbuf_addstr(sb->buf, column_get_color_code(c->color)); + strbuf_addch(sb->buf, col_char); + sb->width++; if (c->color < column_colors_max) - strbuf_addstr(sb, column_get_color_code(column_colors_max)); + strbuf_addstr(sb->buf, column_get_color_code(column_colors_max)); } struct git_graph { @@ -686,8 +710,7 @@ static int graph_is_mapping_correct(struct git_graph *graph) return 1; } -static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb, - int chars_written) +static void graph_pad_horizontally(struct git_graph *graph, struct graph_strbuf *sb) { /* * Add additional spaces to the end of the strbuf, so that all @@ -696,12 +719,12 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb, * This way, fields printed to the right of the graph will remain * aligned for the entire commit. */ - if (chars_written < graph->width) - strbuf_addchars(sb, ' ', graph->width - chars_written); + if (sb->width < graph->width) + strbuf_addchars(sb->buf, ' ', graph->width - sb->width); } static void graph_output_padding_line(struct git_graph *graph, - struct strbuf *sb) + struct graph_strbuf *sb) { int i; @@ -719,11 +742,11 @@ static void graph_output_padding_line(struct git_graph *graph, * Output a padding row, that leaves all branch lines unchanged */ for (i = 0; i < graph->num_new_columns; i++) { - strbuf_write_column(sb, &graph->new_columns[i], '|'); - strbuf_addch(sb, ' '); + graph_strbuf_write_column(sb, &graph->new_columns[i], '|'); + graph_strbuf_addch(sb, ' '); } - graph_pad_horizontally(graph, sb, graph->num_new_columns * 2); + graph_pad_horizontally(graph, sb); } @@ -733,14 +756,14 @@ int graph_width(struct git_graph *graph) } -static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb) +static void graph_output_skip_line(struct git_graph *graph, struct graph_strbuf *sb) { /* * Output an ellipsis to indicate that a portion * of the graph is missing. */ - strbuf_addstr(sb, "..."); - graph_pad_horizontally(graph, sb, 3); + graph_strbuf_addstr(sb, "..."); + graph_pad_horizontally(graph, sb); if (graph->num_parents >= 3 && graph->commit_index < (graph->num_columns - 1)) @@ -750,11 +773,10 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb) } static void graph_output_pre_commit_line(struct git_graph *graph, - struct strbuf *sb) + struct graph_strbuf *sb) { int num_expansion_rows; int i, seen_this; - int chars_written; /* * This function formats a row that increases the space around a commit @@ -777,14 +799,12 @@ static void graph_output_pre_commit_line(struct git_graph *graph, * Output the row */ seen_this = 0; - chars_written = 0; for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; if (col->commit == graph->commit) { seen_this = 1; - strbuf_write_column(sb, col, '|'); - strbuf_addchars(sb, ' ', graph->expansion_row); - chars_written += 1 + graph->expansion_row; + graph_strbuf_write_column(sb, col, '|'); + graph_strbuf_addchars(sb, ' ', graph->expansion_row); } else if (seen_this && (graph->expansion_row == 0)) { /* * This is the first line of the pre-commit output. @@ -797,22 +817,18 @@ static void graph_output_pre_commit_line(struct git_graph *graph, */ if (graph->prev_state == GRAPH_POST_MERGE && graph->prev_commit_index < i) - strbuf_write_column(sb, col, '\\'); + graph_strbuf_write_column(sb, col, '\\'); else - strbuf_write_column(sb, col, '|'); - chars_written++; + graph_strbuf_write_column(sb, col, '|'); } else if (seen_this && (graph->expansion_row > 0)) { - strbuf_write_column(sb, col, '\\'); - chars_written++; + graph_strbuf_write_column(sb, col, '\\'); } else { - strbuf_write_column(sb, col, '|'); - chars_written++; + graph_strbuf_write_column(sb, col, '|'); } - strbuf_addch(sb, ' '); - chars_written++; + graph_strbuf_addch(sb, ' '); } - graph_pad_horizontally(graph, sb, chars_written); + graph_pad_horizontally(graph, sb); /* * Increment graph->expansion_row, @@ -823,7 +839,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph, graph_update_state(graph, GRAPH_COMMIT); } -static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb) +static void graph_output_commit_char(struct git_graph *graph, struct graph_strbuf *sb) { /* * For boundary commits, print 'o' @@ -831,22 +847,20 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb) */ if (graph->commit->object.flags & BOUNDARY) { assert(graph->revs->boundary); - strbuf_addch(sb, 'o'); + graph_strbuf_addch(sb, 'o'); return; } /* * get_revision_mark() handles all other cases without assert() */ - strbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit)); + graph_strbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit)); } /* - * Draw the horizontal dashes of an octopus merge and return the number of - * characters written. + * Draw the horizontal dashes of an octopus merge. */ -static int graph_draw_octopus_merge(struct git_graph *graph, - struct strbuf *sb) +static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_strbuf *sb) { /* * Here dashless_parents represents the number of parents which don't @@ -886,17 +900,16 @@ static int graph_draw_octopus_merge(struct git_graph *graph, int i; for (i = 0; i < dashful_parents; i++) { - strbuf_write_column(sb, &graph->new_columns[i+first_col], '-'); - strbuf_write_column(sb, &graph->new_columns[i+first_col], - i == dashful_parents-1 ? '.' : '-'); + graph_strbuf_write_column(sb, &graph->new_columns[i+first_col], '-'); + graph_strbuf_write_column(sb, &graph->new_columns[i+first_col], + i == dashful_parents-1 ? '.' : '-'); } - return 2 * dashful_parents; } -static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) +static void graph_output_commit_line(struct git_graph *graph, struct graph_strbuf *sb) { int seen_this = 0; - int i, chars_written; + int i; /* * Output the row containing this commit @@ -906,7 +919,6 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) * children that we have already processed.) */ seen_this = 0; - chars_written = 0; for (i = 0; i <= graph->num_columns; i++) { struct column *col = &graph->columns[i]; struct commit *col_commit; @@ -921,14 +933,11 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) if (col_commit == graph->commit) { seen_this = 1; graph_output_commit_char(graph, sb); - chars_written++; if (graph->num_parents > 2) - chars_written += graph_draw_octopus_merge(graph, - sb); + graph_draw_octopus_merge(graph, sb); } else if (seen_this && (graph->num_parents > 2)) { - strbuf_write_column(sb, col, '\\'); - chars_written++; + graph_strbuf_write_column(sb, col, '\\'); } else if (seen_this && (graph->num_parents == 2)) { /* * This is a 2-way merge commit. @@ -945,19 +954,16 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) */ if (graph->prev_state == GRAPH_POST_MERGE && graph->prev_commit_index < i) - strbuf_write_column(sb, col, '\\'); + graph_strbuf_write_column(sb, col, '\\'); else - strbuf_write_column(sb, col, '|'); - chars_written++; + graph_strbuf_write_column(sb, col, '|'); } else { - strbuf_write_column(sb, col, '|'); - chars_written++; + graph_strbuf_write_column(sb, col, '|'); } - strbuf_addch(sb, ' '); - chars_written++; + graph_strbuf_addch(sb, ' '); } - graph_pad_horizontally(graph, sb, chars_written); + graph_pad_horizontally(graph, sb); /* * Update graph->state @@ -981,15 +987,14 @@ static struct column *find_new_column_by_commit(struct git_graph *graph, return NULL; } -static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb) +static void graph_output_post_merge_line(struct git_graph *graph, struct graph_strbuf *sb) { int seen_this = 0; - int i, j, chars_written; + int i, j; /* * Output the post-merge row */ - chars_written = 0; for (i = 0; i <= graph->num_columns; i++) { struct column *col = &graph->columns[i]; struct commit *col_commit; @@ -1016,29 +1021,25 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf par_column = find_new_column_by_commit(graph, parents->item); assert(par_column); - strbuf_write_column(sb, par_column, '|'); - chars_written++; + graph_strbuf_write_column(sb, par_column, '|'); for (j = 0; j < graph->num_parents - 1; j++) { parents = next_interesting_parent(graph, parents); assert(parents); par_column = find_new_column_by_commit(graph, parents->item); assert(par_column); - strbuf_write_column(sb, par_column, '\\'); - strbuf_addch(sb, ' '); + graph_strbuf_write_column(sb, par_column, '\\'); + graph_strbuf_addch(sb, ' '); } - chars_written += j * 2; } else if (seen_this) { - strbuf_write_column(sb, col, '\\'); - strbuf_addch(sb, ' '); - chars_written += 2; + graph_strbuf_write_column(sb, col, '\\'); + graph_strbuf_addch(sb, ' '); } else { - strbuf_write_column(sb, col, '|'); - strbuf_addch(sb, ' '); - chars_written += 2; + graph_strbuf_write_column(sb, col, '|'); + graph_strbuf_addch(sb, ' '); } } - graph_pad_horizontally(graph, sb, chars_written); + graph_pad_horizontally(graph, sb); /* * Update graph->state @@ -1049,7 +1050,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf graph_update_state(graph, GRAPH_COLLAPSING); } -static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb) +static void graph_output_collapsing_line(struct git_graph *graph, struct graph_strbuf *sb) { int i; short used_horizontal = 0; @@ -1159,9 +1160,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf for (i = 0; i < graph->mapping_size; i++) { int target = graph->new_mapping[i]; if (target < 0) - strbuf_addch(sb, ' '); + graph_strbuf_addch(sb, ' '); else if (target * 2 == i) - strbuf_write_column(sb, &graph->new_columns[target], '|'); + graph_strbuf_write_column(sb, &graph->new_columns[target], '|'); else if (target == horizontal_edge_target && i != horizontal_edge - 1) { /* @@ -1172,16 +1173,16 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf if (i != (target * 2)+3) graph->new_mapping[i] = -1; used_horizontal = 1; - strbuf_write_column(sb, &graph->new_columns[target], '_'); + graph_strbuf_write_column(sb, &graph->new_columns[target], '_'); } else { if (used_horizontal && i < horizontal_edge) graph->new_mapping[i] = -1; - strbuf_write_column(sb, &graph->new_columns[target], '/'); + graph_strbuf_write_column(sb, &graph->new_columns[target], '/'); } } - graph_pad_horizontally(graph, sb, graph->mapping_size); + graph_pad_horizontally(graph, sb); /* * Swap mapping and new_mapping @@ -1199,24 +1200,26 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf int graph_next_line(struct git_graph *graph, struct strbuf *sb) { + struct graph_strbuf gsb = { .buf = sb, .width = 0 }; + switch (graph->state) { case GRAPH_PADDING: - graph_output_padding_line(graph, sb); + graph_output_padding_line(graph, &gsb); return 0; case GRAPH_SKIP: - graph_output_skip_line(graph, sb); + graph_output_skip_line(graph, &gsb); return 0; case GRAPH_PRE_COMMIT: - graph_output_pre_commit_line(graph, sb); + graph_output_pre_commit_line(graph, &gsb); return 0; case GRAPH_COMMIT: - graph_output_commit_line(graph, sb); + graph_output_commit_line(graph, &gsb); return 1; case GRAPH_POST_MERGE: - graph_output_post_merge_line(graph, sb); + graph_output_post_merge_line(graph, &gsb); return 0; case GRAPH_COLLAPSING: - graph_output_collapsing_line(graph, sb); + graph_output_collapsing_line(graph, &gsb); return 0; } @@ -1227,7 +1230,7 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb) static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) { int i; - int chars_written = 0; + struct graph_strbuf gsb = { .buf = sb, .width = 0 }; if (graph->state != GRAPH_COMMIT) { graph_next_line(graph, sb); @@ -1244,20 +1247,17 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; - strbuf_write_column(sb, col, '|'); - chars_written++; + graph_strbuf_write_column(&gsb, col, '|'); if (col->commit == graph->commit && graph->num_parents > 2) { int len = (graph->num_parents - 2) * 2; - strbuf_addchars(sb, ' ', len); - chars_written += len; + graph_strbuf_addchars(&gsb, ' ', len); } else { - strbuf_addch(sb, ' '); - chars_written++; + graph_strbuf_addch(&gsb, ' '); } } - graph_pad_horizontally(graph, sb, chars_written); + graph_pad_horizontally(graph, &gsb); /* * Update graph->prev_state since we have output a padding line