Something went wrong. Try again.
My version of https://github.com/vercel-labs/fx
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293const std = @import("std");const edit_history = @import("edit_history.zig");const editor_state = @import("editor_state.zig");const input_limit_rejection = @import("input_limit_rejection.zig");const pasted_blocks = @import("pasted_blocks.zig");const picker_state = @import("picker_state.zig");const registered_entities = @import("registered_entities.zig");const text_scalar = @import("text_scalar.zig");const vertical_navigation = @import("vertical_navigation.zig");
const Allocator = std.mem.Allocator;
pub const InsertResult = editor_state.InsertResult;
pub const PickerPolicy = enum { preserve, clear,};
/// Borrows the state changed by one layout-independent composer insertion./// The containing runtime retains ownership of every pointer and allocation.pub const State = struct { edit: *editor_state.State, history: *edit_history.State, picker: *picker_state.State, entities: *registered_entities.State, vertical_navigation: *vertical_navigation.State, input_limit_rejection: *input_limit_rejection.State,
pub fn expandedInputLen(self: State) ?usize { return pasted_blocks.expandedLen( self.edit.input.items, self.entities.pasted_blocks.items, ); }
pub fn canInsert( self: State, inserted_len: usize, max_len: usize, ) bool { const current_len = self.expandedInputLen() orelse return false; return editor_state.canInsert(current_len, inserted_len, max_len); }
pub fn canReplaceRange( self: State, replace_start: usize, replace_end: usize, inserted_len: usize, max_len: usize, ) bool { const current_len = self.expandedInputLen() orelse return false; const replaced_len = pasted_blocks.expandedRangeLen( self.edit.input.items, self.entities.pasted_blocks.items, replace_start, replace_end, ) orelse return false; return editor_state.canReplace( current_len, replaced_len, inserted_len, max_len, ); }
pub fn insertByte( self: State, alloc: Allocator, byte: u8, picker_policy: PickerPolicy, ) Allocator.Error!void { try self.insertSlice(alloc, &.{byte}, picker_policy); }
pub fn insertByteBounded( self: State, alloc: Allocator, byte: u8, max_len: usize, picker_policy: PickerPolicy, ) Allocator.Error!InsertResult { return self.insertSliceBounded(alloc, &.{byte}, max_len, picker_policy); }
pub fn insertSlice( self: State, alloc: Allocator, bytes: []const u8, picker_policy: PickerPolicy, ) Allocator.Error!void { if (bytes.len == 0) return; if (bytes.len == 1 and self.claimPendingAutoSeparator(bytes[0], picker_policy)) return;
const insert_start = self.edit.cursor; var prepared = if (self.entities.entityContaining( self.edit.input.items, insert_start, ) != null) edit_history.Prepared.boundary else try self.history.prepare( alloc, insert_start, "", bytes, self.edit.cursor, insert_start + bytes.len, ); defer prepared.deinit(alloc); self.entities.discardPendingAutoSeparator(); self.vertical_navigation.reset(); self.applyPickerPolicy(picker_policy); self.entities.convertSkillTokenContaining(alloc, insert_start); try self.edit.insertSlice(alloc, bytes); self.entities.shiftForInsert(alloc, insert_start, bytes.len); self.reconcilePickerAfterEdit(); self.history.commit(alloc, &prepared); }
pub fn insertSliceBounded( self: State, alloc: Allocator, bytes: []const u8, max_len: usize, picker_policy: PickerPolicy, ) Allocator.Error!InsertResult { if (!self.canInsert(bytes.len, max_len)) { if (bytes.len == 1) { const pending_auto_separator = self.entities.pending_auto_separator; if (self.claimPendingAutoSeparator(bytes[0], picker_policy)) { self.input_limit_rejection.* = input_limit_rejection.clear(); return .inserted; } self.entities.pending_auto_separator = pending_auto_separator; } return .limit_exceeded; }
try self.insertSlice(alloc, bytes, picker_policy); self.input_limit_rejection.* = input_limit_rejection.clear(); return .inserted; }
pub fn insertSliceAssumeCapacity( self: State, alloc: Allocator, bytes: []const u8, ) void { if (bytes.len == 0) return;
self.entities.discardPendingAutoSeparator(); const insert_start = self.edit.cursor; self.entities.convertSkillTokenContaining(alloc, insert_start); self.edit.insertSliceAssumeCapacity(bytes); self.entities.shiftForInsert(alloc, insert_start, bytes.len); self.reconcilePickerAfterEdit(); }
fn claimPendingAutoSeparator( self: State, byte: u8, picker_policy: PickerPolicy, ) bool { if (!self.entities.claimPendingAutoSeparator( self.edit.input.items, self.edit.cursor, byte, )) return false;
self.vertical_navigation.reset(); self.applyPickerPolicy(picker_policy); self.reconcilePickerAfterEdit(); return true; }
fn applyPickerPolicy(self: State, picker_policy: PickerPolicy) void { switch (picker_policy) { .preserve => {}, .clear => self.picker.clearModelPickerFlow(), } }
fn reconcilePickerAfterEdit(self: State) void { self.picker.reconcileInlinePickerAfterEdit(self.edit); self.picker.resetActiveCompletionIndex(); }};
test "bounded insertion rejects before applying the successful transition" { const alloc = std.testing.allocator; const placeholder = "[Pasted text #1, 1 line]";
var edit: editor_state.State = .{}; defer edit.deinit(alloc); try edit.setText(alloc, placeholder);
var picker: picker_state.State = .{}; defer picker.deinit(alloc); try picker.beginModelPickerFlow(alloc, "openai/gpt-5", 2, false, .effort); picker.file_completion_index = 3; picker.file_completion_window_start = 1;
var entities: registered_entities.State = .{}; defer entities.deinit(alloc); try entities.pasted_blocks.append(alloc, .{ .id = 1, .text = try alloc.dupe(u8, "expanded"), .line_count = 1, .span = .{ .raw_start = 0, .raw_end = placeholder.len }, });
var vertical: vertical_navigation.State = .{ .preferred_column = 7 }; var rejection = input_limit_rejection.begin(.{}, text_scalar.Owner.composer).next; var history: edit_history.State = .{}; defer history.deinit(alloc); var insertion = State{ .edit = &edit, .history = &history, .picker = &picker, .entities = &entities, .vertical_navigation = &vertical, .input_limit_rejection = &rejection, };
try std.testing.expectEqual( editor_state.InsertResult.limit_exceeded, try insertion.insertSliceBounded(alloc, "!", "expanded".len, .clear), ); try std.testing.expectEqualStrings(placeholder, edit.input.items); try std.testing.expectEqual(@as(?usize, 7), vertical.preferredColumn()); try std.testing.expectEqual(text_scalar.Owner.composer, rejection.owner.?); try std.testing.expectEqual(picker_state.ModelPickerStage.effort, picker.model_picker_stage); try std.testing.expectEqual(@as(usize, 3), picker.file_completion_index); try std.testing.expectEqual(@as(usize, 1), picker.file_completion_window_start);
try std.testing.expectEqual( editor_state.InsertResult.inserted, try insertion.insertSliceBounded(alloc, "!", "expanded!".len, .clear), ); try std.testing.expectEqualStrings(placeholder ++ "!", edit.input.items); try std.testing.expectEqual(@as(?usize, null), vertical.preferredColumn()); try std.testing.expect(rejection.owner == null); try std.testing.expectEqual(picker_state.ModelPickerStage.model, picker.model_picker_stage); try std.testing.expectEqual(@as(usize, 3), picker.file_completion_index); try std.testing.expectEqual(@as(usize, 1), picker.file_completion_window_start); try std.testing.expectEqual(@as(?usize, "expanded!".len), pasted_blocks.expandedLen( edit.input.items, entities.pasted_blocks.items, ));}
test "bounded rejection preserves a pending auto separator" { const alloc = std.testing.allocator;
var edit: editor_state.State = .{}; defer edit.deinit(alloc); try edit.setText(alloc, "@AGENTS.md ");
var picker: picker_state.State = .{}; defer picker.deinit(alloc); var entities: registered_entities.State = .{}; defer entities.deinit(alloc); entities.markFileCompletionSeparator( edit.input.items, edit.cursor, edit.input.items.len - 1, ); var vertical: vertical_navigation.State = .{}; var rejection: input_limit_rejection.State = .{}; var history: edit_history.State = .{}; defer history.deinit(alloc); const insertion = State{ .edit = &edit, .history = &history, .picker = &picker, .entities = &entities, .vertical_navigation = &vertical, .input_limit_rejection = &rejection, };
try std.testing.expectEqual( InsertResult.limit_exceeded, try insertion.insertByteBounded(alloc, 'x', edit.input.items.len, .clear), ); try std.testing.expectEqual( InsertResult.inserted, try insertion.insertByteBounded(alloc, ' ', edit.input.items.len, .clear), ); try std.testing.expectEqualStrings("@AGENTS.md ", edit.input.items);}