PranitInitial commit439f83c3 months agoHistory
806 lines · 28.6 KB · Zig
RawDownload
const std = @import("std");const image_attachments = @import("../images/image_attachments.zig");const debug_trace = @import("../shared/debug_trace.zig");const entity_spans = @import("../shared/entity_spans.zig");const types = @import("../shared/types.zig");const skill_contract = @import("../skills/skill_contract.zig");const editor_state = @import("editor_state.zig");const input_limit_rejection = @import("input_limit_rejection.zig");const paste_blocks = @import("pasted_blocks.zig");const picker_state = @import("picker_state.zig");const registered_entities = @import("registered_entities.zig");const text_boundaries = @import("text_boundaries.zig");const vertical_navigation = @import("vertical_navigation.zig"); const Allocator = std.mem.Allocator; pub const ImageBlocks = std.ArrayList(types.ImageAttachment); pub const YankResult = union(enum) {    inactive,    limit_exceeded: usize,    inserted: usize,}; pub const LineBounds = struct {    start: usize,    cursor: usize,    end: usize,}; pub const DeleteRange = struct {    start: usize,    end: usize,}; pub const ActiveComposer = struct {    edit: *editor_state.State,    entities: *registered_entities.State,    picker: *picker_state.State,    vertical_navigation: *vertical_navigation.State,    input_limit_rejection: *input_limit_rejection.State,    images: ?*ImageBlocks,}; pub const State = struct {    text: std.ArrayList(u8) = .empty,    images: ImageBlocks = .empty,    image_tokens: std.ArrayList(entity_spans.ImageTokenSpan) = .empty,    skill_tokens: std.ArrayList(registered_entities.SkillTokenSpan) = .empty,    owns_snapshots: bool = false,     pub fn deinit(self: *State, alloc: Allocator) void {        self.text.deinit(alloc);        for (self.images.items) |image| {            if (self.owns_snapshots) {                image_attachments.discardImageAttachment(alloc, image);            } else {                types.freeImageAttachment(alloc, image);            }        }        self.images.deinit(alloc);        self.image_tokens.deinit(alloc);        for (self.skill_tokens.items) |token| {            alloc.free(token.name);            alloc.free(token.path);        }        self.skill_tokens.deinit(alloc);        self.* = .{};    }     pub fn resetForSession(self: *State, alloc: Allocator) void {        if (self.text.items.len > 0) {            debug_trace.logf(                "input",                "kill ring dropped bytes={d} images={d} skills={d} reason=session_reset",                .{                    self.text.items.len,                    self.images.items.len,                    self.skill_tokens.items.len,                },            );        }        self.deinit(alloc);    }     pub fn deleteRange(        self: *State,        alloc: Allocator,        active: ActiveComposer,        bounds: LineBounds,        requested: DeleteRange,    ) !bool {        if (requested.start >= requested.end) return false;        const actual = actualDeleteRange(            active.entities.pasted_blocks.items,            active.entities.image_tokens.items,            bounds,            requested,        ) orelse return false;         var next = try capturePayload(alloc, active, actual);        errdefer next.deinit(alloc);         active.picker.clearModelPickerFlow();        active.picker.resetFilePickerIndex();        removeBackingMetadata(alloc, active, actual);        next.owns_snapshots = true;        deleteInputRange(alloc, active, actual.start, actual.end);        _ = active.edit.setCursor(actual.start);        active.picker.reconcileInlinePickerAfterEdit(active.edit);        active.picker.resetActiveModelPickerIndex();         var previous = self.*;        self.* = next;        previous.deinit(alloc);        return true;    }     pub fn yank(        self: *const State,        alloc: Allocator,        active: ActiveComposer,        first_image_id: usize,        max_len: usize,    ) !YankResult {        if (self.text.items.len == 0) return .inactive;         const replacement_len = try self.replacementLen(first_image_id);        const selection = active.edit.selectionRange();        const admitted = if (selection) |range|            canReplaceInputRange(active, range.start, range.end, replacement_len, max_len)        else            canInsertInput(active, replacement_len, max_len);        if (!admitted) return .{ .limit_exceeded = replacement_len };         var prepared = try self.prepareYank(alloc, first_image_id);        defer prepared.deinit(alloc);        std.debug.assert(prepared.text.items.len == replacement_len);         const image_count = prepared.images.items.len;        const blocks = if (image_count > 0)            active.images orelse return error.MissingImageAttachmentOwner        else            null;        try active.edit.input.ensureUnusedCapacity(alloc, prepared.text.items.len);        try active.entities.image_tokens.ensureUnusedCapacity(alloc, prepared.image_tokens.items.len);        try active.entities.skill_tokens.ensureUnusedCapacity(alloc, prepared.skill_tokens.items.len);        if (blocks) |active_images| {            try active_images.ensureUnusedCapacity(alloc, prepared.images.items.len);            if (active_images.items.len > 0) {                std.debug.assert(active_images.items[active_images.items.len - 1].id < first_image_id);            }        }         const insertion_start = if (selection) |range| range.start else active.edit.cursor;        active.vertical_navigation.reset();        if (selection) |range| {            removeRegisteredEntitiesOverlapping(alloc, active, range.start, range.end);            deleteInputRange(alloc, active, range.start, range.end);            _ = active.edit.setCursor(range.start);            _ = active.edit.clearSelection();        }        insertInputSliceAssumeCapacity(alloc, active, prepared.text.items);        if (blocks) |active_images| {            active_images.appendSliceAssumeCapacity(prepared.images.items);            prepared.images.clearRetainingCapacity();        }        for (prepared.image_tokens.items) |token| {            active.entities.registerImageTokenAssumeCapacity(.{                .id = token.id,                .span = .{                    .raw_start = insertion_start + token.span.raw_start,                    .raw_end = insertion_start + token.span.raw_end,                },            });        }        for (prepared.skill_tokens.items) |token| {            active.entities.registerSkillTokenAssumeCapacity(.{                .raw_start = insertion_start + token.raw_start,                .raw_end = insertion_start + token.raw_end,                .name = token.name,                .path = token.path,                .display_source = token.display_source,                .owns_trailing_separator = token.owns_trailing_separator,            });        }        prepared.skill_tokens.clearRetainingCapacity();        active.input_limit_rejection.* = input_limit_rejection.clear();        return .{ .inserted = image_count };    }     fn replacementLen(self: *const State, first_image_id: usize) !usize {        _ = try std.math.add(usize, first_image_id, self.image_tokens.items.len);        var replacement_len = self.text.items.len;        var previous_end: usize = 0;        for (self.image_tokens.items, 0..) |token, index| {            if (!token.span.isValid(self.text.items.len) or                token.span.raw_start < previous_end)            {                return error.InvalidKillPayload;            }            const match = image_attachments.matchImagePlaceholder(                self.text.items,                token.span.raw_start,            ) orelse return error.InvalidKillPayload;            if (match.id != token.id or token.span.raw_start + match.length != token.span.raw_end) {                return error.InvalidKillPayload;            }            const next_id = try std.math.add(usize, first_image_id, index);            var placeholder_buf: [64]u8 = undefined;            const placeholder = try image_attachments.formatImagePlaceholder(                &placeholder_buf,                next_id,            );            replacement_len = try std.math.sub(                usize,                replacement_len,                token.span.raw_end - token.span.raw_start,            );            replacement_len = try std.math.add(usize, replacement_len, placeholder.len);            previous_end = token.span.raw_end;        }        return replacement_len;    }     fn prepareYank(        self: *const State,        alloc: Allocator,        first_image_id: usize,    ) !State {        var prepared = State{ .owns_snapshots = true };        errdefer prepared.deinit(alloc);         var source_offset: usize = 0;        for (self.image_tokens.items, 0..) |token, index| {            if (index >= self.images.items.len or self.images.items[index].id != token.id) {                return error.InvalidKillPayload;            }            try prepared.text.appendSlice(                alloc,                self.text.items[source_offset..token.span.raw_start],            );            const relative_start = prepared.text.items.len;            const next_id = try std.math.add(usize, first_image_id, index);            var placeholder_buf: [64]u8 = undefined;            const placeholder = try image_attachments.formatImagePlaceholder(                &placeholder_buf,                next_id,            );            try prepared.text.appendSlice(alloc, placeholder);            try prepared.appendYankedImage(                alloc,                self.images.items[index],                .{                    .id = next_id,                    .span = .{                        .raw_start = relative_start,                        .raw_end = relative_start + placeholder.len,                    },                },            );            source_offset = token.span.raw_end;        }        if (self.images.items.len != self.image_tokens.items.len) {            return error.InvalidKillPayload;        }        try prepared.text.appendSlice(alloc, self.text.items[source_offset..]);         for (self.skill_tokens.items) |token| {            if (token.raw_start >= token.raw_end or token.raw_end > self.text.items.len) {                return error.InvalidKillPayload;            }            const raw = self.text.items[token.raw_start..token.raw_end];            if (raw.len != token.name.len + 1 or                raw[0] != '$' or                !std.mem.eql(u8, raw[1..], token.name))            {                return error.InvalidKillPayload;            }            const relative_start = image_attachments.projectOffsetThroughPlaceholderRewrite(                self.image_tokens.items,                prepared.image_tokens.items,                self.text.items.len,                token.raw_start,            ) orelse return error.InvalidKillPayload;            const relative_end = image_attachments.projectOffsetThroughPlaceholderRewrite(                self.image_tokens.items,                prepared.image_tokens.items,                self.text.items.len,                token.raw_end,            ) orelse return error.InvalidKillPayload;            try prepared.appendSkillCopy(                alloc,                relative_start,                relative_end,                token.name,                token.path,                token.display_source,                token.owns_trailing_separator,            );        }        return prepared;    }     fn appendImageCopy(        self: *State,        alloc: Allocator,        attachment: types.ImageAttachment,        token: entity_spans.ImageTokenSpan,    ) !void {        try self.image_tokens.append(alloc, token);        errdefer self.image_tokens.items.len -= 1;         const path = try alloc.dupe(u8, attachment.path);        errdefer alloc.free(path);        const media_type = try alloc.dupe(u8, attachment.media_type);        errdefer alloc.free(media_type);        const snapshot_path = if (attachment.snapshot_path) |value|            try alloc.dupe(u8, value)        else            null;        errdefer if (snapshot_path) |value| alloc.free(value);        const snapshot_sha256 = if (attachment.snapshot_sha256) |value|            try alloc.dupe(u8, value)        else            null;        errdefer if (snapshot_sha256) |value| alloc.free(value);        try self.images.append(alloc, .{            .id = token.id,            .path = path,            .media_type = media_type,            .snapshot_path = snapshot_path,            .snapshot_sha256 = snapshot_sha256,        });    }     fn appendYankedImage(        self: *State,        alloc: Allocator,        attachment: types.ImageAttachment,        token: entity_spans.ImageTokenSpan,    ) !void {        try self.image_tokens.append(alloc, token);        errdefer self.image_tokens.items.len -= 1;         const cloned = try image_attachments.cloneVerifiedImageAttachment(            alloc,            attachment,            token.id,        );        errdefer image_attachments.discardImageAttachment(alloc, cloned);        try self.images.append(alloc, cloned);    }     fn appendSkillCopy(        self: *State,        alloc: Allocator,        raw_start: usize,        raw_end: usize,        name: []const u8,        path: []const u8,        display_source: ?skill_contract.SkillSource,        owns_trailing_separator: bool,    ) !void {        const owned_name = try alloc.dupe(u8, name);        errdefer alloc.free(owned_name);        const owned_path = try alloc.dupe(u8, path);        errdefer alloc.free(owned_path);        try self.skill_tokens.append(alloc, .{            .raw_start = raw_start,            .raw_end = raw_end,            .name = owned_name,            .path = owned_path,            .display_source = display_source,            .owns_trailing_separator = owns_trailing_separator,        });    }}; pub fn lineBounds(input: []const u8, cursor: usize) LineBounds {    const clamped = @min(cursor, input.len);    return .{        .start = text_boundaries.logicalLineStart(input, clamped),        .cursor = clamped,        .end = text_boundaries.logicalLineEnd(input, clamped),    };} fn actualDeleteRange(    pasted: []const paste_blocks.PastedBlock,    image_tokens: []const entity_spans.ImageTokenSpan,    bounds: LineBounds,    requested: DeleteRange,) ?DeleteRange {    if (requested.start >= requested.end) return requested;     var actual = requested;    while (true) {        var changed = false;        for (pasted) |block| {            const span_range = DeleteRange{                .start = block.span.raw_start,                .end = block.span.raw_end,            };            if (rangesOverlap(actual, span_range) and !rangeContains(actual, span_range)) {                actual.start = @min(actual.start, span_range.start);                actual.end = @max(actual.end, span_range.end);                if (actual.start < bounds.start or actual.end > bounds.end) return null;                changed = true;            }        }         for (image_tokens) |token| {            const span_range = DeleteRange{                .start = token.span.raw_start,                .end = token.span.raw_end,            };            if (rangesOverlap(actual, span_range) and !rangeContains(actual, span_range)) {                actual.start = @min(actual.start, span_range.start);                actual.end = @max(actual.end, span_range.end);                if (actual.start < bounds.start or actual.end > bounds.end) return null;                changed = true;            }        }        if (!changed) break;    }    return actual;} fn capturePayload(    alloc: Allocator,    active: ActiveComposer,    actual: DeleteRange,) !State {    var payload: State = .{};    errdefer payload.deinit(alloc);     const expanded = try paste_blocks.expandRange(        alloc,        active.edit.input.items,        active.entities.pasted_blocks.items,        actual.start,        actual.end,    );    defer if (expanded.owned) alloc.free(expanded.text);    try payload.text.appendSlice(alloc, expanded.text);     const expanded_origin = paste_blocks.projectOffsetThroughExpansion(        active.edit.input.items,        active.entities.pasted_blocks.items,        actual.start,    ) orelse return error.InvalidKillRange;    const actual_span = entity_spans.Span{        .raw_start = actual.start,        .raw_end = actual.end,    };     for (active.entities.image_tokens.items) |token| {        if (!entity_spans.containsSpan(actual_span, token.span)) continue;        if (!active.entities.isValidImageToken(active.edit.input.items, token)) continue;        const blocks = active.images orelse return error.MissingImageAttachment;        const attachment = image_attachments.findById(blocks.items, token.id) orelse            return error.MissingImageAttachment;        const relative_start = try projectKillOffset(            active,            actual,            expanded_origin,            token.span.raw_start,        );        const relative_end = try projectKillOffset(            active,            actual,            expanded_origin,            token.span.raw_end,        );        try payload.appendImageCopy(alloc, attachment, .{            .id = token.id,            .span = .{                .raw_start = relative_start,                .raw_end = relative_end,            },        });    }     for (active.entities.skill_tokens.items) |token| {        const span = entity_spans.Span{            .raw_start = token.raw_start,            .raw_end = token.raw_end,        };        if (!entity_spans.containsSpan(actual_span, span)) continue;        if (!active.entities.isValidSkillToken(active.edit.input.items, token)) continue;        const relative_start = try projectKillOffset(            active,            actual,            expanded_origin,            token.raw_start,        );        const relative_end = try projectKillOffset(            active,            actual,

Lines 1–500 of 806.