PranitInitial commit439f83c3 months agoHistory
379 lines · 13.0 KB · Zig
RawDownload
const std = @import("std");const entity_spans = @import("../shared/entity_spans.zig");const Allocator = std.mem.Allocator; /// Pasted text stays visible inline until it exceeds the threshold; oversized/// blocks collapse to placeholders and expand again before submit.pub const large_paste_char_threshold: usize = 1000; /// A block of pasted text replaced in the input by a short/// `[Pasted text #N, M lines]` placeholder. The containing list owns/// `text` and releases it through `clearBlocks` or `deinitBlocks`.pub const PastedBlock = struct {    id: usize,    text: []u8,    line_count: usize,    span: entity_spans.Span = .{ .raw_start = 0, .raw_end = 0 },}; /// Releases every backing text allocation and the list allocation.pub fn deinitBlocks(alloc: Allocator, blocks: *std.ArrayList(PastedBlock)) void {    for (blocks.items) |block| alloc.free(block.text);    blocks.deinit(alloc);} /// Releases every backing text allocation while retaining the list capacity.pub fn clearBlocks(alloc: Allocator, blocks: *std.ArrayList(PastedBlock)) void {    for (blocks.items) |block| alloc.free(block.text);    blocks.clearRetainingCapacity();} pub fn countLines(bytes: []const u8) usize {    if (bytes.len == 0) return 0;    var lines: usize = 1;    for (bytes) |b| {        if (b == '\n') lines += 1;    }    // A trailing newline shouldn't inflate the count.    if (bytes[bytes.len - 1] == '\n' and lines > 1) lines -= 1;    return lines;} pub fn countCodepoints(bytes: []const u8) usize {    var count: usize = 0;    var i: usize = 0;    while (i < bytes.len) {        const width = std.unicode.utf8ByteSequenceLength(bytes[i]) catch 1;        i += @min(width, bytes.len - i);        count += 1;    }    return count;} pub fn shouldUsePlaceholder(bytes: []const u8) bool {    return countCodepoints(bytes) > large_paste_char_threshold;} /// Write the placeholder that stands in for a pasted block inside the input./// Returns the slice of `buf` that was filled.pub fn formatPlaceholder(buf: []u8, id: usize, line_count: usize) ![]const u8 {    const noun = if (line_count == 1) "line" else "lines";    return std.fmt.bufPrint(buf, "[Pasted text #{d}, {d} {s}]", .{ id, line_count, noun });} /// Replace every registered pasted-text span with its matching block content./// If nothing matches, returns `text` and `owned=false` — callers must not free/// the result in that case.pub const ExpandResult = struct {    text: []const u8,    owned: bool,}; pub fn expand(alloc: Allocator, text: []const u8, blocks: []const PastedBlock) !ExpandResult {    if (blocks.len == 0) {        return .{ .text = text, .owned = false };    }     var out: std.ArrayList(u8) = .empty;    errdefer out.deinit(alloc);     var i: usize = 0;    var expanded_any = false;    while (i < text.len) {        if (registeredBlockStartingAt(text, blocks, i)) |block| {            try out.appendSlice(alloc, block.text);            i = block.span.raw_end;            expanded_any = true;            continue;        }        try out.append(alloc, text[i]);        i += 1;    }     if (!expanded_any) {        out.deinit(alloc);        return .{ .text = text, .owned = false };    }    return .{ .text = try out.toOwnedSlice(alloc), .owned = true };} /// Expands registered paste spans fully contained in `[range_start, range_end)`./// The returned text contains only that range.pub fn expandRange(    alloc: Allocator,    text: []const u8,    blocks: []const PastedBlock,    range_start: usize,    range_end: usize,) !ExpandResult {    if (range_start > range_end or range_end > text.len) return error.InvalidPasteRange;    if (blocks.len == 0) {        return .{ .text = text[range_start..range_end], .owned = false };    }     var out: std.ArrayList(u8) = .empty;    errdefer out.deinit(alloc);    var expanded_any = false;    var i = range_start;    while (i < range_end) {        if (registeredBlockStartingAt(text, blocks, i)) |block| {            if (block.span.raw_end <= range_end) {                try out.appendSlice(alloc, block.text);                i = block.span.raw_end;                expanded_any = true;                continue;            }        }        try out.append(alloc, text[i]);        i += 1;    }     if (!expanded_any) {        out.deinit(alloc);        return .{ .text = text[range_start..range_end], .owned = false };    }    return .{ .text = try out.toOwnedSlice(alloc), .owned = true };} /// Returns the byte length after registered paste placeholders are expanded./// Null means the requested range is invalid or its length overflowed.pub fn expandedRangeLen(    text: []const u8,    blocks: []const PastedBlock,    range_start: usize,    range_end: usize,) ?usize {    if (range_start > range_end or range_end > text.len) return null;    if (blocks.len == 0) return range_end - range_start;     var expanded_len: usize = 0;    var i = range_start;    while (i < range_end) {        if (registeredBlockStartingAt(text, blocks, i)) |block| {            if (block.span.raw_end <= range_end) {                expanded_len = std.math.add(                    usize,                    expanded_len,                    block.text.len,                ) catch return null;                i = block.span.raw_end;                continue;            }        }        expanded_len = std.math.add(usize, expanded_len, 1) catch return null;        i += 1;    }    return expanded_len;} pub fn expandedLen(text: []const u8, blocks: []const PastedBlock) ?usize {    var expanded_len = text.len;    for (blocks) |block| {        if (!isRegisteredBlock(text, block)) continue;        const placeholder_len = block.span.raw_end - block.span.raw_start;        if (block.text.len >= placeholder_len) {            expanded_len = std.math.add(                usize,                expanded_len,                block.text.len - placeholder_len,            ) catch return null;        } else {            expanded_len = std.math.sub(                usize,                expanded_len,                placeholder_len - block.text.len,            ) catch return null;        }    }    return expanded_len;} fn registeredBlockStartingAt(text: []const u8, blocks: []const PastedBlock, raw_start: usize) ?PastedBlock {    for (blocks) |block| {        if (block.span.raw_start < raw_start) continue;        if (block.span.raw_start > raw_start) return null;        if (!isRegisteredBlock(text, block)) return null;        return block;    }    return null;} fn isRegisteredBlock(text: []const u8, block: PastedBlock) bool {    if (!block.span.isValid(text.len)) return false;    var placeholder_buf: [64]u8 = undefined;    const canonical = formatPlaceholder(        &placeholder_buf,        block.id,        block.line_count,    ) catch return false;    return std.mem.eql(        u8,        text[block.span.raw_start..block.span.raw_end],        canonical,    );} pub fn registeredPlaceholderSpanStartingAt(    text: []const u8,    raw_start: usize,    blocks: []const PastedBlock,) ?PlaceholderSpan {    const block = registeredBlockStartingAt(text, blocks, raw_start) orelse return null;    return .{        .start = block.span.raw_start,        .end = block.span.raw_end,        .id = block.id,    };} pub const PlaceholderSpan = struct { start: usize, end: usize, id: usize }; /// Maps a byte offset in placeholder-bearing input into the expanded text./// Returns null when the offset lands inside a placeholder span.pub fn projectOffsetThroughExpansion(    raw_input: []const u8,    pasted_blocks: []const PastedBlock,    raw_offset: usize,) ?usize {    if (raw_offset > raw_input.len) return null;    var input_offset: usize = 0;    var output_offset: usize = 0;    while (input_offset < raw_offset) {        if (registeredBlockStartingAt(raw_input, pasted_blocks, input_offset)) |block| {            if (raw_offset < block.span.raw_end) return null;            output_offset = std.math.add(                usize,                output_offset,                block.text.len,            ) catch return null;            input_offset = block.span.raw_end;            continue;        }        input_offset += 1;        output_offset = std.math.add(usize, output_offset, 1) catch return null;    }    return output_offset;} test "countLines counts lines with and without trailing newline" {    try std.testing.expectEqual(@as(usize, 0), countLines(""));    try std.testing.expectEqual(@as(usize, 1), countLines("foo"));    try std.testing.expectEqual(@as(usize, 1), countLines("foo\n"));    try std.testing.expectEqual(@as(usize, 2), countLines("foo\nbar"));    try std.testing.expectEqual(@as(usize, 2), countLines("foo\nbar\n"));    try std.testing.expectEqual(@as(usize, 3), countLines("a\nb\nc"));} test "formatPlaceholder singular and plural" {    var buf: [64]u8 = undefined;    try std.testing.expectEqualStrings("[Pasted text #1, 1 line]", try formatPlaceholder(&buf, 1, 1));    try std.testing.expectEqualStrings("[Pasted text #42, 17 lines]", try formatPlaceholder(&buf, 42, 17));} test "shouldUsePlaceholder matches large paste threshold" {    try std.testing.expect(!shouldUsePlaceholder("hello"));    try std.testing.expect(!shouldUsePlaceholder("x" ** large_paste_char_threshold));    try std.testing.expect(shouldUsePlaceholder("x" ** (large_paste_char_threshold + 1)));    try std.testing.expect(!shouldUsePlaceholder("\xc3\xa9" ** large_paste_char_threshold));    try std.testing.expect(shouldUsePlaceholder("\xc3\xa9" ** (large_paste_char_threshold + 1)));} test "expand substitutes placeholder for real text" {    const alloc = std.testing.allocator;    var blocks: std.ArrayList(PastedBlock) = .empty;    defer deinitBlocks(alloc, &blocks);    const input = "prefix [Pasted text #1, 2 lines] suffix";    const start = "prefix ".len;    try blocks.append(alloc, .{        .id = 1,        .text = try alloc.dupe(u8, "hello\nworld"),        .line_count = 2,        .span = .{            .raw_start = start,            .raw_end = start + "[Pasted text #1, 2 lines]".len,        },    });     const result = try expand(alloc, input, blocks.items);    defer if (result.owned) alloc.free(result.text);    try std.testing.expectEqualStrings("prefix hello\nworld suffix", result.text);    try std.testing.expect(result.owned);} test "expand with no placeholder returns original slice unowned" {    const alloc = std.testing.allocator;    var blocks: std.ArrayList(PastedBlock) = .empty;    defer deinitBlocks(alloc, &blocks);     const result = try expand(alloc, "nothing to replace", blocks.items);    try std.testing.expect(!result.owned);    try std.testing.expectEqualStrings("nothing to replace", result.text);} test "expand preserves unknown placeholder ids verbatim" {    const alloc = std.testing.allocator;    var blocks: std.ArrayList(PastedBlock) = .empty;    defer deinitBlocks(alloc, &blocks);    try blocks.append(alloc, .{        .id = 1,        .text = try alloc.dupe(u8, "X"),        .line_count = 1,        .span = .{            .raw_start = "before [Pasted text #9, 4 lines] ".len,            .raw_end = "before [Pasted text #9, 4 lines] [Pasted text #1, 1 line]".len,        },    });     const result = try expand(alloc, "before [Pasted text #9, 4 lines] [Pasted text #1, 1 line]", blocks.items);    defer if (result.owned) alloc.free(result.text);    try std.testing.expectEqualStrings("before [Pasted text #9, 4 lines] X", result.text);} test "expanded lengths count registered backing text without allocating" {    const alloc = std.testing.allocator;    const placeholder = "[Pasted text #1, 1 line]";    const input = "ab" ++ placeholder ++ "cd";    var blocks: std.ArrayList(PastedBlock) = .empty;    defer deinitBlocks(alloc, &blocks);    try blocks.append(alloc, .{        .id = 1,        .text = try alloc.dupe(u8, "expanded"),        .line_count = 1,        .span = .{            .raw_start = "ab".len,            .raw_end = "ab".len + placeholder.len,        },    });     try std.testing.expectEqual(        @as(?usize, "abexpandedcd".len),        expandedLen(input, blocks.items),    );    try std.testing.expectEqual(        @as(?usize, "expanded".len),        expandedRangeLen(            input,            blocks.items,            "ab".len,            "ab".len + placeholder.len,        ),    );} test "expand ignores typed lookalikes with a registered id" {    const alloc = std.testing.allocator;    const registered = "[Pasted text #1, 1 line]";    const input = registered ++ " typed [Pasted text #1, 999 lines]";    var blocks: std.ArrayList(PastedBlock) = .empty;    defer deinitBlocks(alloc, &blocks);    try blocks.append(alloc, .{        .id = 1,        .text = try alloc.dupe(u8, "original"),        .line_count = 1,        .span = .{ .raw_start = 0, .raw_end = registered.len },    });     const result = try expand(alloc, input, blocks.items);    defer if (result.owned) alloc.free(result.text);    try std.testing.expectEqualStrings("original typed [Pasted text #1, 999 lines]", result.text);}