| ... | ... | @@ -14,6 +14,8 @@ gpa: Allocator, |
| 14 | 14 | ais: *AutoIndentingStream, |
| 15 | 15 | tree: Ast, |
| 16 | 16 | fixups: Fixups, |
| 17 | checking_newlines: bool, |
| 18 | scratch_array_init: std.ArrayList(usize), |
| 17 | 19 | |
| 18 | 20 | const indent_delta = 4; |
| 19 | 21 | const asm_indent_delta = 2; |
| ... | ... | @@ -92,7 +94,10 @@ pub fn renderTree(gpa: Allocator, w: *Writer, tree: Ast, fixups: Fixups) Error!v |
| 92 | 94 | .ais = &auto_indenting_stream, |
| 93 | 95 | .tree = tree, |
| 94 | 96 | .fixups = fixups, |
| 97 | .checking_newlines = false, |
| 98 | .scratch_array_init = .empty, |
| 95 | 99 | }; |
| 100 | defer r.scratch_array_init.deinit(gpa); |
| 96 | 101 | |
| 97 | 102 | // Render all the line comments at the beginning of the file. |
| 98 | 103 | const comment_end_loc = tree.tokenStart(0); |
| ... | ... | @@ -983,6 +988,8 @@ fn rendersMultiline(r: *const Render, node: Ast.Node.Index) error{OutOfMemory}!b |
| 983 | 988 | .ais = &sub_ais, |
| 984 | 989 | .tree = r.tree, |
| 985 | 990 | .fixups = r.fixups, |
| 991 | .checking_newlines = true, |
| 992 | .scratch_array_init = undefined, // unused since `checking_newlines` is `true` |
| 986 | 993 | }; |
| 987 | 994 | |
| 988 | 995 | renderExpression(&sub_r, node, .none) catch |err| return switch (err) { |
| ... | ... | @@ -2101,6 +2108,21 @@ fn renderStructInit( |
| 2101 | 2108 | return renderToken(r, rbrace, space); |
| 2102 | 2109 | } |
| 2103 | 2110 | |
| 2111 | fn renderCommentsBetweenTokens(r: *Render, start: Ast.TokenIndex, end: Ast.TokenIndex) Error!void { |
| 2112 | const tree = r.tree; |
| 2113 | |
| 2114 | var next_start = tree.tokenStart(start); |
| 2115 | for (start + 1..end) |token_i| { |
| 2116 | const token_start = next_start; |
| 2117 | next_start = tree.tokenStart(@intCast(token_i + 1)); |
| 2118 | _ = try renderComments( |
| 2119 | r, |
| 2120 | token_start + tokenSliceForRender(tree, @intCast(token_i)).len, |
| 2121 | next_start, |
| 2122 | ); |
| 2123 | } |
| 2124 | } |
| 2125 | |
| 2104 | 2126 | fn renderArrayInit( |
| 2105 | 2127 | r: *Render, |
| 2106 | 2128 | array_init: Ast.full.ArrayInit, |
| ... | ... | @@ -2157,137 +2179,276 @@ fn renderArrayInit( |
| 2157 | 2179 | } |
| 2158 | 2180 | } |
| 2159 | 2181 | return renderToken(r, last_elem_token + 1, space); // rbrace |
| 2182 | } else if (r.checking_newlines) { |
| 2183 | // To avoid taking quadratic time while rendering nested arrays, just write a newline |
| 2184 | // since it is already gauraunteed. However, comments still need to be written since they |
| 2185 | // may enable/disable zig fmt. |
| 2186 | try r.renderCommentsBetweenTokens(array_init.ast.lbrace, rbrace); |
| 2187 | return r.ais.insertNewline(); |
| 2160 | 2188 | } |
| 2161 | 2189 | |
| 2162 | 2190 | try ais.pushIndent(.normal); |
| 2163 | 2191 | try renderToken(r, array_init.ast.lbrace, .newline); |
| 2164 | 2192 | try ais.pushSpace(.comma); |
| 2165 | 2193 | |
| 2166 | | const expr_widths = try gpa.alloc(enum(usize) { |
| 2167 | | /// The expression contains non-printable characters (e.g. unicode / newlines) |
| 2168 | | /// or has formatting disabled at the start or end. |
| 2169 | | nonprint = std.math.maxInt(usize), |
| 2170 | | _, |
| 2171 | | }, array_init.ast.elements.len); |
| 2172 | | defer gpa.free(expr_widths); |
| 2173 | | { |
| 2174 | | var buf: Writer.Allocating = .init(gpa); |
| 2175 | | defer buf.deinit(); |
| 2176 | | var sub_ais: AutoIndentingStream = .init(gpa, &buf.writer, indent_delta); |
| 2177 | | sub_ais.disabled_offset = ais.disabled_offset; |
| 2178 | | defer sub_ais.deinit(); |
| 2179 | | var sub_r: Render = .{ |
| 2180 | | .gpa = r.gpa, |
| 2181 | | .ais = &sub_ais, |
| 2182 | | .tree = r.tree, |
| 2183 | | .fixups = r.fixups, |
| 2184 | | }; |
| 2185 | | for (array_init.ast.elements, expr_widths) |e, *width| { |
| 2186 | | const begin_disabled = sub_ais.disabled_offset != null; |
| 2187 | | // `.skip` space so trailing commments aren't included |
| 2188 | | try renderExpressionComma(&sub_r, e, .skip); |
| 2189 | | if (!begin_disabled and sub_ais.disabled_offset == null) { |
| 2190 | | const w = buf.written(); |
| 2191 | | width.* = for (w) |c| { |
| 2192 | | if (!std.ascii.isPrint(c)) |
| 2193 | | break .nonprint; |
| 2194 | | } else @fromBackingInt(@intCast(w.len - @intFromBool(w[w.len - 1] == ','))); |
| 2195 | | } else { |
| 2196 | | width.* = .nonprint; |
| 2197 | | } |
| 2194 | const scratch = &r.scratch_array_init; |
| 2195 | const scratch_start = scratch.items.len; |
| 2196 | defer assert(scratch.items.len == scratch_start); |
| 2198 | 2197 | |
| 2199 | | // Write trailing comments since they may enable/disable zig fmt |
| 2200 | | buf.clearRetainingCapacity(); |
| 2201 | | var after_expr = tree.lastToken(e); |
| 2202 | | after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma); |
| 2203 | | try renderSpace(&sub_r, after_expr, tokenSliceForRender(tree, after_expr).len, .none); |
| 2204 | | |
| 2205 | | buf.clearRetainingCapacity(); |
| 2206 | | // The following are needed to make sure isLineOverIndented is not influenced by |
| 2207 | | // the previous element. |
| 2208 | | sub_ais.indent_count = 0; |
| 2209 | | sub_ais.applied_indent = 0; |
| 2210 | | } |
| 2211 | | } |
| 2198 | var sub_buf: Writer.Allocating = .init(gpa); |
| 2199 | defer sub_buf.deinit(); |
| 2200 | var sub_ais: AutoIndentingStream = .init(gpa, &sub_buf.writer, indent_delta); |
| 2201 | defer sub_ais.deinit(); |
| 2202 | var sub_r: Render = .{ |
| 2203 | .gpa = r.gpa, |
| 2204 | .ais = &sub_ais, |
| 2205 | .tree = r.tree, |
| 2206 | .fixups = r.fixups, |
| 2207 | .checking_newlines = true, |
| 2208 | .scratch_array_init = undefined, // unused since `checking_newlines` is `true` |
| 2209 | }; |
| 2212 | 2210 | |
| 2213 | 2211 | var remaining_exprs = array_init.ast.elements; |
| 2214 | | var remaining_widths = expr_widths; |
| 2215 | | while (remaining_exprs.len != 0) { |
| 2216 | | var row_size: usize = 1; |
| 2217 | | for (1.., remaining_exprs, remaining_widths) |len, e, w| { |
| 2218 | | if (w == .nonprint) break; |
| 2219 | | row_size = len; |
| 2220 | | |
| 2221 | | var after_expr = tree.lastToken(e); |
| 2222 | | after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma); |
| 2223 | | assert(tree.tokenTag(after_expr) == .comma or after_expr + 1 == rbrace); |
| 2224 | | if (!tree.tokensOnSameLine(after_expr, after_expr + 1)) |
| 2225 | | break; |
| 2226 | | } else { |
| 2227 | | // All the expressions are on the same line. |
| 2228 | | // However, if there is a trailing comma, we put them each on their own line. |
| 2229 | | if (tree.tokenTag(rbrace - 1) == .comma) |
| 2230 | | row_size = 1; |
| 2231 | | } |
| 2232 | | |
| 2233 | | // Determine the size of this section |
| 2234 | | const section_end = end: { |
| 2235 | | var line_start = row_size; // Start after the first row to ignore comments on it |
| 2236 | | break :end for (line_start.., remaining_exprs[line_start..]) |i, e| { |
| 2237 | | const expr_first = tree.firstToken(e); |
| 2238 | | // Any nonprint character terminates the line because they are always put on their |
| 2239 | | // own line, so they will not end up on the same line as the trailing comment. |
| 2240 | | if (expr_widths[i - 1] == .nonprint or !tree.tokensOnSameLine(expr_first - 1, expr_first)) { |
| 2241 | | line_start = i; |
| 2212 | section: while (remaining_exprs.len != 0) { |
| 2213 | defer scratch.items.len = scratch_start; |
| 2214 | // May not already be equal due to a section end and when `sub_ais` is initialized. |
| 2215 | sub_ais.disabled_offset = ais.disabled_offset; |
| 2216 | |
| 2217 | var n_section_exprs: usize = 0; |
| 2218 | var n_cols: usize = 0; |
| 2219 | var line_col: usize = 0; |
| 2220 | var first_line: bool = true; |
| 2221 | // `scratch` will contain the width of the first `n_section_exprs`; |
| 2222 | // zero indicates the expression is on its own line. |
| 2223 | while (true) { |
| 2224 | assert(scratch.items[scratch_start..].len == n_section_exprs); |
| 2225 | |
| 2226 | const expr = remaining_exprs[n_section_exprs]; |
| 2227 | const final_expr = n_section_exprs + 1 == remaining_exprs.len; |
| 2228 | var elem_last = tree.lastToken(expr); |
| 2229 | elem_last += @intFromBool(tree.tokenTag(elem_last + 1) == .comma); |
| 2230 | assert(tree.tokenTag(elem_last) == .comma or elem_last + 1 == rbrace); |
| 2231 | |
| 2232 | const own_line = ends_line: { |
| 2233 | const ends_line = final_expr or !tree.tokensOnSameLine(elem_last, elem_last + 1); |
| 2234 | if (ends_line and n_cols == 0) { |
| 2235 | // Avoid doing extra work below since the width will not be required. |
| 2236 | break :ends_line false; |
| 2237 | } // Otherwise a correct value for `own_line` is required. |
| 2238 | // This is the case even for `n_cols == 1` since it may become `2` below. |
| 2239 | |
| 2240 | if (sub_ais.disabled_offset != null) { |
| 2241 | if (n_cols >= 2) { |
| 2242 | // Comments still need rendered since they may enable/disable zig fmt |
| 2243 | // for later expressions. |
| 2244 | try renderCommentsBetweenTokens(&sub_r, tree.firstToken(expr), elem_last + 1); |
| 2245 | sub_buf.clearRetainingCapacity(); |
| 2246 | } |
| 2247 | break :ends_line true; |
| 2248 | } |
| 2249 | // `.skip` space so trailing commments aren't included |
| 2250 | try renderExpressionComma(&sub_r, expr, .skip); |
| 2251 | var own_line = sub_ais.disabled_offset != null; |
| 2252 | for (sub_buf.written()) |c| { |
| 2253 | if (!std.ascii.isPrint(c)) { |
| 2254 | // The expression contained a newline or unicode character so it is |
| 2255 | // put on its own line since there is no reasonable way to align it. |
| 2256 | own_line = true; |
| 2257 | } |
| 2258 | } |
| 2259 | const width = sub_buf.written().len - |
| 2260 | @intFromBool(sub_buf.written()[sub_buf.written().len - 1] == ','); |
| 2261 | sub_buf.clearRetainingCapacity(); |
| 2262 | |
| 2263 | // Write trailing comments since they may enable/disable zig fmt. |
| 2264 | _ = try renderComments( |
| 2265 | &sub_r, |
| 2266 | tree.tokenStart(elem_last) + tokenSliceForRender(tree, elem_last).len, |
| 2267 | tree.tokenStart(elem_last + 1), |
| 2268 | ); |
| 2269 | sub_buf.clearRetainingCapacity(); |
| 2270 | |
| 2271 | // Make sure isLineOverIndented is not influenced for future elements. |
| 2272 | sub_ais.indent_count = 0; |
| 2273 | sub_ais.applied_indent = 0; |
| 2274 | |
| 2275 | if (own_line) { |
| 2276 | break :ends_line true; |
| 2242 | 2277 | } |
| 2243 | 2278 | |
| 2244 | | var after_expr = tree.lastToken(e); |
| 2245 | | after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma); |
| 2246 | | assert(tree.tokenTag(after_expr) == .comma or after_expr + 1 == rbrace); |
| 2247 | | if (hasTrailingComment(tree, after_expr)) |
| 2248 | | break line_start; |
| 2249 | | } else remaining_exprs.len; |
| 2250 | | }; |
| 2251 | | const section_exprs = remaining_exprs[0..section_end]; |
| 2252 | | const section_widths = remaining_widths[0..section_end]; |
| 2253 | | remaining_exprs = remaining_exprs[section_end..]; |
| 2254 | | remaining_widths = remaining_widths[section_end..]; |
| 2279 | // This is a valid element to be put on a line with others. |
| 2280 | assert(width != 0); |
| 2255 | 2281 | |
| 2256 | | // Determine the width of each column |
| 2257 | | var col_widths = try gpa.alloc(usize, row_size); |
| 2258 | | defer gpa.free(col_widths); |
| 2259 | | @memset(col_widths, 0); |
| 2282 | try scratch.append(r.gpa, width); |
| 2283 | line_col += 1; |
| 2284 | n_cols += @intFromBool(first_line); |
| 2285 | n_section_exprs += 1; |
| 2286 | |
| 2287 | if (ends_line) { |
| 2288 | break :ends_line false; |
| 2289 | } |
| 2260 | 2290 | |
| 2261 | | var col: usize = 0; |
| 2262 | | for (section_widths) |w| { |
| 2263 | | if (w == .nonprint) { |
| 2264 | | col = 0; |
| 2265 | 2291 | continue; |
| 2292 | }; |
| 2293 | |
| 2294 | if (n_cols <= 1 and first_line) { |
| 2295 | // Since there is only one column, there is no need to render each expression to |
| 2296 | // find its width. However, the section ending still needs to be accounted for, so |
| 2297 | // below we find the number of expressions that can be rendered before the section |
| 2298 | // might end. |
| 2299 | n_section_exprs = 1; |
| 2300 | n_cols = 1; |
| 2301 | line_col = 0; |
| 2302 | scratch.items.len = scratch_start; |
| 2303 | |
| 2304 | while (n_section_exprs != remaining_exprs.len) { |
| 2305 | const s_expr = remaining_exprs[n_section_exprs]; |
| 2306 | var s_elem_last = tree.lastToken(s_expr); |
| 2307 | s_elem_last += @intFromBool(tree.tokenTag(s_elem_last + 1) == .comma); |
| 2308 | assert(tree.tokenTag(s_elem_last) == .comma or s_elem_last + 1 == rbrace); |
| 2309 | |
| 2310 | // `line_col` here does not account for expressions being put on their own line. |
| 2311 | if (tree.tokensOnSameLine(s_elem_last, s_elem_last + 1)) { |
| 2312 | line_col += 1; |
| 2313 | } else if (hasTrailingComment(tree, s_elem_last)) { |
| 2314 | n_section_exprs -= line_col; |
| 2315 | break; |
| 2316 | } else { |
| 2317 | line_col = 0; |
| 2318 | } |
| 2319 | n_section_exprs += 1; |
| 2320 | } else { |
| 2321 | line_col = 0; |
| 2322 | } |
| 2323 | |
| 2324 | for (remaining_exprs[0..n_section_exprs]) |s_expr| { |
| 2325 | if (s_expr != array_init.ast.elements[0]) { |
| 2326 | try renderExtraNewline(r, s_expr); |
| 2327 | } |
| 2328 | try renderExpression(r, s_expr, .comma); |
| 2329 | } |
| 2330 | remaining_exprs = remaining_exprs[n_section_exprs..]; |
| 2331 | |
| 2332 | if (line_col != 0) { |
| 2333 | assert(remaining_exprs.len != 0); |
| 2334 | |
| 2335 | // Since the final expressions may be forced on their own line, and so still |
| 2336 | // part of this section, the final row is handled by the slower path. |
| 2337 | sub_ais.disabled_offset = ais.disabled_offset; |
| 2338 | n_section_exprs = 0; |
| 2339 | line_col = 0; |
| 2340 | first_line = false; |
| 2341 | continue; |
| 2342 | } |
| 2343 | |
| 2344 | continue :section; |
| 2266 | 2345 | } |
| 2267 | | col_widths[col] = @max(col_widths[col], @backingInt(w)); |
| 2268 | | col += 1; |
| 2269 | | if (col == row_size) { |
| 2270 | | col = 0; |
| 2346 | |
| 2347 | first_line &= !own_line or n_section_exprs == 0; |
| 2348 | |
| 2349 | if (!first_line and hasTrailingComment(tree, elem_last)) { |
| 2350 | // End of the section |
| 2351 | if (own_line) { |
| 2352 | line_col = 0; |
| 2353 | } |
| 2354 | n_section_exprs -= line_col; |
| 2355 | scratch.items.len -= line_col; |
| 2356 | |
| 2357 | break; |
| 2271 | 2358 | } |
| 2359 | |
| 2360 | if (own_line) { |
| 2361 | // The expression has no width, so it is not yet present in `scratch`. |
| 2362 | try scratch.append(r.gpa, 0); |
| 2363 | n_section_exprs += 1; |
| 2364 | } |
| 2365 | |
| 2366 | if (final_expr) { |
| 2367 | break; |
| 2368 | } |
| 2369 | |
| 2370 | line_col = 0; |
| 2371 | first_line = false; |
| 2272 | 2372 | } |
| 2373 | line_col = undefined; |
| 2273 | 2374 | |
| 2274 | | // Render each expression |
| 2275 | | col = 0; |
| 2276 | | for (0.., section_exprs, section_widths) |i, e, w| { |
| 2277 | | if (i + 1 == section_end or col + 1 == row_size or |
| 2278 | | w == .nonprint or section_widths[i + 1] == .nonprint) |
| 2279 | | { |
| 2280 | | try renderExpression(r, e, .comma); |
| 2281 | | col = 0; |
| 2282 | | if (i + 1 != section_end) { |
| 2283 | | try renderExtraNewline(r, section_exprs[i + 1]); |
| 2375 | const section_exprs = remaining_exprs[0..n_section_exprs]; |
| 2376 | remaining_exprs = remaining_exprs[n_section_exprs..]; |
| 2377 | |
| 2378 | if (n_cols == 1 or |
| 2379 | // If all the expressions are on the same line with a |
| 2380 | // trailing comma, they are each put on their own line. |
| 2381 | (n_cols == array_init.ast.elements.len and |
| 2382 | tree.tokenTag(rbrace - 1) == .comma and tree.tokensOnSameLine(rbrace - 1, rbrace))) |
| 2383 | { |
| 2384 | scratch.items.len = scratch_start; |
| 2385 | for (section_exprs) |expr| { |
| 2386 | if (expr != array_init.ast.elements[0]) { |
| 2387 | try renderExtraNewline(r, expr); |
| 2388 | } else { |
| 2389 | // Can only be the case if `n_cols` became `1` at the end (one line with a |
| 2390 | // trailing comma) since otherwise the first rows were already rendered. |
| 2391 | assert(n_section_exprs == array_init.ast.elements.len); |
| 2284 | 2392 | } |
| 2285 | | } else { |
| 2286 | | try renderExpression(r, e, .comma_space); |
| 2287 | | try ais.splatByteAll(' ', col_widths[col] - @backingInt(w)); |
| 2393 | try renderExpression(r, expr, .comma); |
| 2394 | } |
| 2395 | continue; |
| 2396 | } |
| 2397 | |
| 2398 | { |
| 2399 | assert(scratch.items[scratch_start..].len == n_section_exprs); |
| 2400 | // Determine the width of each column. This cannot be done with the width calculations |
| 2401 | // above since the section may have ended, removing elements already iterated over. |
| 2402 | // |
| 2403 | // The first `n_section_exprs` of `scratch` will contain zero at each end-of-line. |
| 2404 | // The remaining `n_cols - 1` will contain the widths of each column. |
| 2405 | const col_widths = try scratch.addManyAsSlice(gpa, n_cols - 1); |
| 2406 | |
| 2407 | @memcpy(col_widths, scratch.items[scratch_start..][0 .. n_cols - 1]); |
| 2408 | scratch.items[scratch_start + n_cols - 1] = 0; |
| 2409 | |
| 2410 | var col: usize = 0; |
| 2411 | for ( |
| 2412 | scratch_start + n_cols.., |
| 2413 | scratch.items[scratch_start..][n_cols..n_section_exprs], |
| 2414 | ) |i, width| { |
| 2415 | if (width == 0) { |
| 2416 | scratch.items[i - 1] = 0; |
| 2417 | col = 0; |
| 2418 | } else if (col + 1 == n_cols) { |
| 2419 | scratch.items[i] = 0; |
| 2420 | col = 0; |
| 2421 | } else { |
| 2422 | col_widths[col] = @max(col_widths[col], width); |
| 2423 | col += 1; |
| 2424 | } |
| 2425 | } |
| 2426 | |
| 2427 | scratch.items[scratch_start + n_section_exprs - 1] = 0; |
| 2428 | } |
| 2429 | |
| 2430 | var col: usize = 0; |
| 2431 | for (0.., section_exprs) |i, expr| { |
| 2432 | assert(col < n_cols); |
| 2433 | |
| 2434 | if (col == 0 and expr != array_init.ast.elements[0]) { |
| 2435 | try renderExtraNewline(r, expr); |
| 2436 | } |
| 2437 | |
| 2438 | const width = scratch.items[scratch_start + i]; |
| 2439 | if (width != 0) { |
| 2440 | try renderExpression(r, expr, .comma_space); |
| 2441 | |
| 2442 | const col_width = scratch.items[scratch_start + n_section_exprs + col]; |
| 2443 | try ais.splatByteAll(' ', col_width - width); |
| 2444 | |
| 2288 | 2445 | col += 1; |
| 2446 | } else { |
| 2447 | try renderExpression(r, expr, .comma); |
| 2448 | col = 0; |
| 2289 | 2449 | } |
| 2290 | 2450 | } |
| 2451 | assert(col == 0); |
| 2291 | 2452 | } |
| 2292 | 2453 | |
| 2293 | 2454 | ais.popSpace(); |