const std = @import("std");const debug_trace = @import("../shared/debug_trace.zig");const text_utils = @import("../shared/text_utils.zig");const types = @import("../shared/types.zig"); const Allocator = std.mem.Allocator; pub const max_preview_lines: usize = 6;pub const max_encoded_path_bytes: usize = 4 * 1024;pub const max_encoded_label_bytes: usize = 4 * 1024;pub const max_encoded_line_bytes: usize = 4 * 1024;pub const max_encoded_preview_bytes: usize = 24 * 1024;pub const max_request_projection_bytes: usize = 32 * 1024; pub const LineOp = enum(u8) { equal, add, remove }; pub const EditKind = enum(u8) { edited, added }; pub const diff_block_start_prefix: []const u8 = "\x1b]9050;";pub const diff_block_end_prefix: []const u8 = "\x1b]9051;";const osc_bell: u8 = 0x07;const trailing_newline_added_marker = "(trailing newline added)";const trailing_newline_removed_marker = "(trailing newline removed)"; pub const DiffEntry = struct { id: u32, full: ?FullDiff = null, pub fn deinit(self: *DiffEntry, alloc: Allocator) void { if (self.full) |full| full.deinit(alloc); }}; pub const FullDiff = struct { content: []u8, lifecycle_id: types.ToolLifecycleId, pub fn deinit(self: FullDiff, alloc: Allocator) void { alloc.free(self.content); alloc.free(@constCast(self.lifecycle_id.call_id)); }}; pub const DiffEntryPayload = struct { preview: []u8, additions: u32 = 0, deletions: u32 = 0, full: ?FullDiff = null,}; pub fn freeDiffEntryPayload(alloc: Allocator, payload: DiffEntryPayload) void { alloc.free(payload.preview); if (payload.full) |full| full.deinit(alloc);} test "diff entry payload has one canonical preview" { try std.testing.expect(@hasField(DiffEntryPayload, "preview")); try std.testing.expect(!@hasField(DiffEntryPayload, "quiet")); try std.testing.expect(!@hasField(DiffEntryPayload, "normal")); try std.testing.expect(!@hasField(DiffEntryPayload, "active"));} pub fn wrapWithMarkers(alloc: Allocator, id: u32, content: []const u8) ![]u8 { var out: std.ArrayList(u8) = .empty; errdefer out.deinit(alloc); var id_buf: [16]u8 = undefined; const id_str = try std.fmt.bufPrint(&id_buf, "{d}", .{id}); try out.appendSlice(alloc, diff_block_start_prefix); try out.appendSlice(alloc, id_str); try out.append(alloc, osc_bell); try out.appendSlice(alloc, content); try out.appendSlice(alloc, diff_block_end_prefix); try out.appendSlice(alloc, id_str); try out.append(alloc, osc_bell); return out.toOwnedSlice(alloc);} pub fn markedDiffBlockId(bytes: []const u8) ?u32 { if (!std.mem.startsWith(u8, bytes, diff_block_start_prefix)) return null; const id_start = diff_block_start_prefix.len; const bell_pos = std.mem.indexOfScalarPos(u8, bytes, id_start, osc_bell) orelse return null; const id_digits = bytes[id_start..bell_pos]; const id = std.fmt.parseInt(u32, id_digits, 10) catch return null; var end_marker: [32]u8 = undefined; const end = std.fmt.bufPrint( &end_marker, "{s}{s}{c}", .{ diff_block_end_prefix, id_digits, osc_bell }, ) catch return null; if (!std.mem.endsWith(u8, bytes, end)) return null; return id;} pub const DiffLine = struct { op: LineOp, old_num: ?u32 = null, new_num: ?u32 = null, text: []const u8,}; pub const PreviewOptions = struct { max_lines: usize = max_preview_lines, max_indexed_lines: usize = canonical_max_indexed_lines, max_matrix_cells: usize = canonical_max_matrix_cells, max_encoded_line_bytes: usize = max_encoded_line_bytes, max_encoded_preview_bytes: usize = max_encoded_preview_bytes,}; pub const PreviewOp = enum { context, addition, deletion, elision, notice,}; pub const PreviewLine = struct { op: PreviewOp, old_line: ?u32 = null, new_line: ?u32 = null, text: []const u8,}; pub const PreviewValidationError = error{ TooManyLines, PathTooLong, InvalidPathMetadata, LineTooLong, PreviewTooLarge, InvalidLineNumbers,}; pub const FileChangePreview = struct { path: []const u8, path_basename_start: usize = 0, path_source_truncated: bool = false, lines: []const PreviewLine, additions: usize, deletions: usize, truncated: bool, pub fn validate(self: FileChangePreview) PreviewValidationError!void { if (self.path.len > max_encoded_path_bytes) return error.PathTooLong; if (self.path.len == 0 or self.path_basename_start >= self.path.len) { return error.InvalidPathMetadata; } if (self.lines.len > max_preview_lines) return error.TooManyLines; var encoded_bytes: usize = 0; var addition_rows: usize = 0; var deletion_rows: usize = 0; var elision_rows: usize = 0; var notice_rows: usize = 0; for (self.lines) |line| { if (line.text.len > max_encoded_line_bytes) return error.LineTooLong; encoded_bytes = std.math.add(usize, encoded_bytes, line.text.len) catch return error.PreviewTooLarge; if (encoded_bytes > max_encoded_preview_bytes) return error.PreviewTooLarge; const valid_numbers = switch (line.op) { .context => line.old_line != null and line.old_line.? > 0 and line.new_line != null and line.new_line.? > 0, .addition => line.old_line == null and line.new_line != null and line.new_line.? > 0, .deletion => line.old_line != null and line.old_line.? > 0 and line.new_line == null, .elision, .notice => line.old_line == null and line.new_line == null, }; if (!valid_numbers) return error.InvalidLineNumbers; switch (line.op) { .context => {}, .addition => addition_rows += 1, .deletion => deletion_rows += 1, .elision => elision_rows += 1, .notice => notice_rows += 1, } } if (elision_rows > 1) return error.InvalidLineNumbers; if (self.additions == 0 and self.deletions == 0) { if (self.lines.len != 1 or notice_rows != 1) return error.InvalidLineNumbers; return; } if (notice_rows != 0 or addition_rows + deletion_rows == 0) { return error.InvalidLineNumbers; } if (addition_rows > self.additions or deletion_rows > self.deletions) { return error.InvalidLineNumbers; } } pub fn eql(a: FileChangePreview, b: FileChangePreview) bool { if (!std.mem.eql(u8, a.path, b.path)) return false; if (a.path_basename_start != b.path_basename_start) return false; if (a.path_source_truncated != b.path_source_truncated) return false; if (a.lines.len != b.lines.len) return false; for (a.lines, b.lines) |a_line, b_line| { if (!previewLineEql(a_line, b_line)) return false; } return a.additions == b.additions and a.deletions == b.deletions and a.truncated == b.truncated; }}; fn previewLineEql(a: PreviewLine, b: PreviewLine) bool { return a.op == b.op and a.old_line == b.old_line and a.new_line == b.new_line and std.mem.eql(u8, a.text, b.text);} pub const ProjectedLine = struct { op: PreviewOp, old_line: ?u32 = null, new_line: ?u32 = null, text: []u8,}; pub const BoundedDiffProjection = struct { lines: []ProjectedLine, additions: usize, deletions: usize, truncated: bool, pub fn deinit(self: BoundedDiffProjection, alloc: Allocator) void { for (self.lines) |line| alloc.free(line.text); alloc.free(self.lines); }}; /// Immutable, borrowed review source for a prepared file mutation.////// Unlike `FileChangePreview`, this never selects a fixed number of changed/// rows. It borrows the already-frozen file contents and materializes only the/// requested viewport.pub const FileReview = struct { mode: union(enum) { computed: []DiffLine, fallback: FallbackReview, }, additions: usize, deletions: usize, pub fn init( alloc: Allocator, old_text: []const u8, new_text: []const u8, ) FileReviewError!FileReview { const old_marker = trailingNewlineMarker(old_text, new_text, true); const new_marker = trailingNewlineMarker(old_text, new_text, false); const old_line_count = try checkedReviewAdd( countTextLines(old_text), @intFromBool(old_marker != null), ); const new_line_count = try checkedReviewAdd( countTextLines(new_text), @intFromBool(new_marker != null), ); const indexed_lines = try checkedReviewAdd(old_line_count, new_line_count); const matrix_cells = try checkedReviewMul( try checkedReviewAdd(old_line_count, 1), try checkedReviewAdd(new_line_count, 1), ); if (indexed_lines <= canonical_max_indexed_lines and matrix_cells <= canonical_max_matrix_cells) { const lines = try compute(alloc, old_text, new_text); var additions: usize = 0; var deletions: usize = 0; for (lines) |line| { switch (line.op) { .equal => {}, .add => additions += 1, .remove => deletions += 1, } } return .{ .mode = .{ .computed = lines }, .additions = additions, .deletions = deletions, }; } const fallback = try FallbackReview.init( alloc, old_text, new_text, old_marker, new_marker, ); return .{ .mode = .{ .fallback = fallback }, .additions = fallback.additions, .deletions = fallback.deletions, }; } pub fn deinit(self: *FileReview, alloc: Allocator) void { switch (self.mode) { .computed => |lines| alloc.free(lines), .fallback => |fallback| { alloc.free(fallback.old_line_starts); alloc.free(fallback.new_line_starts); }, } self.* = undefined; } pub fn rowCount(self: FileReview) usize { const changed = self.additions + self.deletions; if (changed == 0) return 1; return switch (self.mode) { .computed => |source| countComputedReviewRows(source), .fallback => |source| countFallbackReviewRows(source), }; } pub fn viewport( self: FileReview, alloc: Allocator, start_row: usize, max_rows: usize, ) Allocator.Error!ReviewViewport { const total_rows = self.rowCount(); const start = @min(start_row, total_rows); const count = @min(max_rows, total_rows - start); const lines = try alloc.alloc(ReviewLine, count); errdefer alloc.free(lines); if (self.additions == 0 and self.deletions == 0) { if (count == 1) { lines[0] = .{ .op = .notice, .text = "No content changes" }; } return .{ .lines = lines, .total_rows = total_rows }; } switch (self.mode) { .computed => |source| fillComputedReviewViewport(lines, source, start), .fallback => |fallback| fillFallbackReviewViewport(lines, fallback, start), } return .{ .lines = lines, .total_rows = total_rows }; }}; pub const FileReviewError = error{ OutOfMemory, ArithmeticOverflow,}; pub const ReviewLine = struct { op: PreviewOp, old_line: ?u32 = null, new_line: ?u32 = null, text: []const u8, elision_count: usize = 0,}; pub const ReviewViewport = struct { lines: []ReviewLine, total_rows: usize, pub fn deinit(self: ReviewViewport, alloc: Allocator) void { alloc.free(self.lines); }}; const FallbackReview = struct { old_text: []const u8, new_text: []const u8, old_line_starts: []usize, new_line_starts: []usize, prefix_count: usize, suffix_count: usize, old_middle_count: usize, new_middle_count: usize, old_marker: ?[]const u8, new_marker: ?[]const u8, additions: usize, deletions: usize, fn init( alloc: Allocator, old_text: []const u8, new_text: []const u8, old_marker: ?[]const u8, new_marker: ?[]const u8, ) FileReviewError!FallbackReview { const old_line_starts = try indexLineStarts(alloc, old_text); errdefer alloc.free(old_line_starts); const new_line_starts = try indexLineStarts(alloc, new_text); errdefer alloc.free(new_line_starts); const old_normal_count = old_line_starts.len; const new_normal_count = new_line_starts.len; const comparable_count = @min(old_normal_count, new_normal_count); var prefix_count: usize = 0; while (prefix_count < comparable_count) { const old_line = indexedTextLine(old_text, old_line_starts, prefix_count); const new_line = indexedTextLine(new_text, new_line_starts, prefix_count); if (!std.mem.eql(u8, old_line.text, new_line.text)) break; prefix_count += 1; } var suffix_count: usize = 0; while (prefix_count + suffix_count < comparable_count) { const old_line = indexedTextLine( old_text, old_line_starts, old_normal_count - suffix_count - 1, ); const new_line = indexedTextLine( new_text, new_line_starts, new_normal_count - suffix_count - 1, ); if (!std.mem.eql(u8, old_line.text, new_line.text)) break; suffix_count += 1; } const old_middle_count = old_normal_count - prefix_count - suffix_count; const new_middle_count = new_normal_count - prefix_count - suffix_count; return .{ .old_text = old_text, .new_text = new_text, .old_line_starts = old_line_starts, .new_line_starts = new_line_starts, .prefix_count = prefix_count, .suffix_count = suffix_count, .old_middle_count = old_middle_count, .new_middle_count = new_middle_count, .old_marker = old_marker, .new_marker = new_marker, .additions = try checkedReviewAdd(new_middle_count, @intFromBool(new_marker != null)), .deletions = try checkedReviewAdd(old_middle_count, @intFromBool(old_marker != null)), }; }}; fn checkedReviewAdd(a: usize, b: usize) FileReviewError!usize { return std.math.add(usize, a, b) catch error.ArithmeticOverflow;} fn checkedReviewMul(a: usize, b: usize) FileReviewError!usize { return std.math.mul(usize, a, b) catch error.ArithmeticOverflow;} const review_context_lines: usize = 5; const ReviewRowCounter = struct { count: usize = 0, fn append(self: *@This(), _: ReviewLine) void { self.count += 1; } fn isFull(_: @This()) bool { return false; }}; const ReviewViewportWriter = struct { destination: []ReviewLine, start_row: usize, ordinal: usize = 0, written: usize = 0, fn append(self: *@This(), line: ReviewLine) void { appendReviewViewportLine( self.destination, &self.ordinal, &self.written, self.start_row, line, ); } fn isFull(self: @This()) bool { return self.written == self.destination.len; }}; fn countComputedReviewRows(source: []const DiffLine) usize { var counter = ReviewRowCounter{}; visitComputedReviewRows(source, &counter); return counter.count;} fn countFallbackReviewRows(source: FallbackReview) usize { return equalRunRowCount(source.prefix_count) + source.deletions + source.additions + equalRunRowCount(source.suffix_count);} fn equalRunRowCount(count: usize) usize {Lines 1–500 of 2779. Load more
const std = @import("std"); const debug_trace = @import("../shared/debug_trace.zig"); const text_utils = @import("../shared/text_utils.zig"); const types = @import("../shared/types.zig"); const Allocator = std.mem.Allocator; pub const max_preview_lines: usize = 6; pub const max_encoded_path_bytes: usize = 4 * 1024; pub const max_encoded_label_bytes: usize = 4 * 1024; pub const max_encoded_line_bytes: usize = 4 * 1024; pub const max_encoded_preview_bytes: usize = 24 * 1024; pub const max_request_projection_bytes: usize = 32 * 1024; pub const LineOp = enum(u8) { equal, add, remove }; pub const EditKind = enum(u8) { edited, added }; pub const diff_block_start_prefix: []const u8 = "\x1b]9050;"; pub const diff_block_end_prefix: []const u8 = "\x1b]9051;"; const osc_bell: u8 = 0x07; const trailing_newline_added_marker = "(trailing newline added)"; const trailing_newline_removed_marker = "(trailing newline removed)"; pub const DiffEntry = struct { id: u32, full: ?FullDiff = null, pub fn deinit(self: *DiffEntry, alloc: Allocator) void { if (self.full) |full| full.deinit(alloc); } }; pub const FullDiff = struct { content: []u8, lifecycle_id: types.ToolLifecycleId, pub fn deinit(self: FullDiff, alloc: Allocator) void { alloc.free(self.content); alloc.free(@constCast(self.lifecycle_id.call_id)); } }; pub const DiffEntryPayload = struct { preview: []u8, additions: u32 = 0, deletions: u32 = 0, full: ?FullDiff = null, }; pub fn freeDiffEntryPayload(alloc: Allocator, payload: DiffEntryPayload) void { alloc.free(payload.preview); if (payload.full) |full| full.deinit(alloc); } test "diff entry payload has one canonical preview" { try std.testing.expect(@hasField(DiffEntryPayload, "preview")); try std.testing.expect(!@hasField(DiffEntryPayload, "quiet")); try std.testing.expect(!@hasField(DiffEntryPayload, "normal")); try std.testing.expect(!@hasField(DiffEntryPayload, "active")); } pub fn wrapWithMarkers(alloc: Allocator, id: u32, content: []const u8) ![]u8 { var out: std.ArrayList(u8) = .empty; errdefer out.deinit(alloc); var id_buf: [16]u8 = undefined; const id_str = try std.fmt.bufPrint(&id_buf, "{d}", .{id}); try out.appendSlice(alloc, diff_block_start_prefix); try out.appendSlice(alloc, id_str); try out.append(alloc, osc_bell); try out.appendSlice(alloc, content); try out.appendSlice(alloc, diff_block_end_prefix); try out.appendSlice(alloc, id_str); try out.append(alloc, osc_bell); return out.toOwnedSlice(alloc); } pub fn markedDiffBlockId(bytes: []const u8) ?u32 { if (!std.mem.startsWith(u8, bytes, diff_block_start_prefix)) return null; const id_start = diff_block_start_prefix.len; const bell_pos = std.mem.indexOfScalarPos(u8, bytes, id_start, osc_bell) orelse return null; const id_digits = bytes[id_start..bell_pos]; const id = std.fmt.parseInt(u32, id_digits, 10) catch return null; var end_marker: [32]u8 = undefined; const end = std.fmt.bufPrint( &end_marker, "{s}{s}{c}", .{ diff_block_end_prefix, id_digits, osc_bell }, ) catch return null; if (!std.mem.endsWith(u8, bytes, end)) return null; return id; } pub const DiffLine = struct { op: LineOp, old_num: ?u32 = null, new_num: ?u32 = null, text: []const u8, }; pub const PreviewOptions = struct { max_lines: usize = max_preview_lines, max_indexed_lines: usize = canonical_max_indexed_lines, max_matrix_cells: usize = canonical_max_matrix_cells, max_encoded_line_bytes: usize = max_encoded_line_bytes, max_encoded_preview_bytes: usize = max_encoded_preview_bytes, }; pub const PreviewOp = enum { context, addition, deletion, elision, notice, }; pub const PreviewLine = struct { op: PreviewOp, old_line: ?u32 = null, new_line: ?u32 = null, text: []const u8, }; pub const PreviewValidationError = error{ TooManyLines, PathTooLong, InvalidPathMetadata, LineTooLong, PreviewTooLarge, InvalidLineNumbers, }; pub const FileChangePreview = struct { path: []const u8, path_basename_start: usize = 0, path_source_truncated: bool = false, lines: []const PreviewLine, additions: usize, deletions: usize, truncated: bool, pub fn validate(self: FileChangePreview) PreviewValidationError!void { if (self.path.len > max_encoded_path_bytes) return error.PathTooLong; if (self.path.len == 0 or self.path_basename_start >= self.path.len) { return error.InvalidPathMetadata; } if (self.lines.len > max_preview_lines) return error.TooManyLines; var encoded_bytes: usize = 0; var addition_rows: usize = 0; var deletion_rows: usize = 0; var elision_rows: usize = 0; var notice_rows: usize = 0; for (self.lines) |line| { if (line.text.len > max_encoded_line_bytes) return error.LineTooLong; encoded_bytes = std.math.add(usize, encoded_bytes, line.text.len) catch return error.PreviewTooLarge; if (encoded_bytes > max_encoded_preview_bytes) return error.PreviewTooLarge; const valid_numbers = switch (line.op) { .context => line.old_line != null and line.old_line.? > 0 and line.new_line != null and line.new_line.? > 0, .addition => line.old_line == null and line.new_line != null and line.new_line.? > 0, .deletion => line.old_line != null and line.old_line.? > 0 and line.new_line == null, .elision, .notice => line.old_line == null and line.new_line == null, }; if (!valid_numbers) return error.InvalidLineNumbers; switch (line.op) { .context => {}, .addition => addition_rows += 1, .deletion => deletion_rows += 1, .elision => elision_rows += 1, .notice => notice_rows += 1, } } if (elision_rows > 1) return error.InvalidLineNumbers; if (self.additions == 0 and self.deletions == 0) { if (self.lines.len != 1 or notice_rows != 1) return error.InvalidLineNumbers; return; } if (notice_rows != 0 or addition_rows + deletion_rows == 0) { return error.InvalidLineNumbers; } if (addition_rows > self.additions or deletion_rows > self.deletions) { return error.InvalidLineNumbers; } } pub fn eql(a: FileChangePreview, b: FileChangePreview) bool { if (!std.mem.eql(u8, a.path, b.path)) return false; if (a.path_basename_start != b.path_basename_start) return false; if (a.path_source_truncated != b.path_source_truncated) return false; if (a.lines.len != b.lines.len) return false; for (a.lines, b.lines) |a_line, b_line| { if (!previewLineEql(a_line, b_line)) return false; } return a.additions == b.additions and a.deletions == b.deletions and a.truncated == b.truncated; } }; fn previewLineEql(a: PreviewLine, b: PreviewLine) bool { return a.op == b.op and a.old_line == b.old_line and a.new_line == b.new_line and std.mem.eql(u8, a.text, b.text); } pub const ProjectedLine = struct { op: PreviewOp, old_line: ?u32 = null, new_line: ?u32 = null, text: []u8, }; pub const BoundedDiffProjection = struct { lines: []ProjectedLine, additions: usize, deletions: usize, truncated: bool, pub fn deinit(self: BoundedDiffProjection, alloc: Allocator) void { for (self.lines) |line| alloc.free(line.text); alloc.free(self.lines); } }; /// Immutable, borrowed review source for a prepared file mutation. /// /// Unlike `FileChangePreview`, this never selects a fixed number of changed /// rows. It borrows the already-frozen file contents and materializes only the /// requested viewport. pub const FileReview = struct { mode: union(enum) { computed: []DiffLine, fallback: FallbackReview, }, additions: usize, deletions: usize, pub fn init( alloc: Allocator, old_text: []const u8, new_text: []const u8, ) FileReviewError!FileReview { const old_marker = trailingNewlineMarker(old_text, new_text, true); const new_marker = trailingNewlineMarker(old_text, new_text, false); const old_line_count = try checkedReviewAdd( countTextLines(old_text), @intFromBool(old_marker != null), ); const new_line_count = try checkedReviewAdd( countTextLines(new_text), @intFromBool(new_marker != null), ); const indexed_lines = try checkedReviewAdd(old_line_count, new_line_count); const matrix_cells = try checkedReviewMul( try checkedReviewAdd(old_line_count, 1), try checkedReviewAdd(new_line_count, 1), ); if (indexed_lines <= canonical_max_indexed_lines and matrix_cells <= canonical_max_matrix_cells) { const lines = try compute(alloc, old_text, new_text); var additions: usize = 0; var deletions: usize = 0; for (lines) |line| { switch (line.op) { .equal => {}, .add => additions += 1, .remove => deletions += 1, } } return .{ .mode = .{ .computed = lines }, .additions = additions, .deletions = deletions, }; } const fallback = try FallbackReview.init( alloc, old_text, new_text, old_marker, new_marker, ); return .{ .mode = .{ .fallback = fallback }, .additions = fallback.additions, .deletions = fallback.deletions, }; } pub fn deinit(self: *FileReview, alloc: Allocator) void { switch (self.mode) { .computed => |lines| alloc.free(lines), .fallback => |fallback| { alloc.free(fallback.old_line_starts); alloc.free(fallback.new_line_starts); }, } self.* = undefined; } pub fn rowCount(self: FileReview) usize { const changed = self.additions + self.deletions; if (changed == 0) return 1; return switch (self.mode) { .computed => |source| countComputedReviewRows(source), .fallback => |source| countFallbackReviewRows(source), }; } pub fn viewport( self: FileReview, alloc: Allocator, start_row: usize, max_rows: usize, ) Allocator.Error!ReviewViewport { const total_rows = self.rowCount(); const start = @min(start_row, total_rows); const count = @min(max_rows, total_rows - start); const lines = try alloc.alloc(ReviewLine, count); errdefer alloc.free(lines); if (self.additions == 0 and self.deletions == 0) { if (count == 1) { lines[0] = .{ .op = .notice, .text = "No content changes" }; } return .{ .lines = lines, .total_rows = total_rows }; } switch (self.mode) { .computed => |source| fillComputedReviewViewport(lines, source, start), .fallback => |fallback| fillFallbackReviewViewport(lines, fallback, start), } return .{ .lines = lines, .total_rows = total_rows }; } }; pub const FileReviewError = error{ OutOfMemory, ArithmeticOverflow, }; pub const ReviewLine = struct { op: PreviewOp, old_line: ?u32 = null, new_line: ?u32 = null, text: []const u8, elision_count: usize = 0, }; pub const ReviewViewport = struct { lines: []ReviewLine, total_rows: usize, pub fn deinit(self: ReviewViewport, alloc: Allocator) void { alloc.free(self.lines); } }; const FallbackReview = struct { old_text: []const u8, new_text: []const u8, old_line_starts: []usize, new_line_starts: []usize, prefix_count: usize, suffix_count: usize, old_middle_count: usize, new_middle_count: usize, old_marker: ?[]const u8, new_marker: ?[]const u8, additions: usize, deletions: usize, fn init( alloc: Allocator, old_text: []const u8, new_text: []const u8, old_marker: ?[]const u8, new_marker: ?[]const u8, ) FileReviewError!FallbackReview { const old_line_starts = try indexLineStarts(alloc, old_text); errdefer alloc.free(old_line_starts); const new_line_starts = try indexLineStarts(alloc, new_text); errdefer alloc.free(new_line_starts); const old_normal_count = old_line_starts.len; const new_normal_count = new_line_starts.len; const comparable_count = @min(old_normal_count, new_normal_count); var prefix_count: usize = 0; while (prefix_count < comparable_count) { const old_line = indexedTextLine(old_text, old_line_starts, prefix_count); const new_line = indexedTextLine(new_text, new_line_starts, prefix_count); if (!std.mem.eql(u8, old_line.text, new_line.text)) break; prefix_count += 1; } var suffix_count: usize = 0; while (prefix_count + suffix_count < comparable_count) { const old_line = indexedTextLine( old_text, old_line_starts, old_normal_count - suffix_count - 1, ); const new_line = indexedTextLine( new_text, new_line_starts, new_normal_count - suffix_count - 1, ); if (!std.mem.eql(u8, old_line.text, new_line.text)) break; suffix_count += 1; } const old_middle_count = old_normal_count - prefix_count - suffix_count; const new_middle_count = new_normal_count - prefix_count - suffix_count; return .{ .old_text = old_text, .new_text = new_text, .old_line_starts = old_line_starts, .new_line_starts = new_line_starts, .prefix_count = prefix_count, .suffix_count = suffix_count, .old_middle_count = old_middle_count, .new_middle_count = new_middle_count, .old_marker = old_marker, .new_marker = new_marker, .additions = try checkedReviewAdd(new_middle_count, @intFromBool(new_marker != null)), .deletions = try checkedReviewAdd(old_middle_count, @intFromBool(old_marker != null)), }; } }; fn checkedReviewAdd(a: usize, b: usize) FileReviewError!usize { return std.math.add(usize, a, b) catch error.ArithmeticOverflow; } fn checkedReviewMul(a: usize, b: usize) FileReviewError!usize { return std.math.mul(usize, a, b) catch error.ArithmeticOverflow; } const review_context_lines: usize = 5; const ReviewRowCounter = struct { count: usize = 0, fn append(self: *@This(), _: ReviewLine) void { self.count += 1; } fn isFull(_: @This()) bool { return false; } }; const ReviewViewportWriter = struct { destination: []ReviewLine, start_row: usize, ordinal: usize = 0, written: usize = 0, fn append(self: *@This(), line: ReviewLine) void { appendReviewViewportLine( self.destination, &self.ordinal, &self.written, self.start_row, line, ); } fn isFull(self: @This()) bool { return self.written == self.destination.len; } }; fn countComputedReviewRows(source: []const DiffLine) usize { var counter = ReviewRowCounter{}; visitComputedReviewRows(source, &counter); return counter.count; } fn countFallbackReviewRows(source: FallbackReview) usize { return equalRunRowCount(source.prefix_count) + source.deletions + source.additions + equalRunRowCount(source.suffix_count); } fn equalRunRowCount(count: usize) usize {