4981 lines · 205.1 KB · Zig
RawDownload
const std = @import("std");const question_prompt = @import("../../core/agent/question_prompt.zig");const approval_decision = @import("../../core/permissions/approval_decision.zig");const subagent_input = @import("../../core/subagent/input_action.zig");const paste_blocks = @import("../../core/input/pasted_blocks.zig");const core_input_runtime = @import("../../core/input/runtime.zig");const native_clear_probe_runtime = @import("native_clear_probe.zig");const image_attachments = @import("../../core/images/image_attachments.zig");const debug_trace = @import("../../core/shared/debug_trace.zig");const io_mod = @import("../../core/shared/io.zig");const types = @import("../../core/shared/types.zig");const cursor_probe = @import("../terminal/cursor_probe.zig");const theme_monitor = @import("../terminal/theme_monitor.zig");const gesture_state = @import("../../core/input/gesture_state.zig");const horizontal_navigation = @import("../../core/input/horizontal_navigation.zig");const picker_state = @import("../../core/input/picker_state.zig");const composer_history = @import("../../core/input/composer_history.zig");const composer_insertion = @import("../../core/input/composer_insertion.zig");const kill_ring = @import("../../core/input/kill_ring.zig");const entity_spans = @import("../../core/shared/entity_spans.zig");const input_action = @import("../../core/input/input_action.zig");const vertical_navigation = @import("../../core/input/vertical_navigation.zig");const visual_layout = @import("visual_layout.zig");const escape_parser = @import("escape_parser.zig");const shortcuts = @import("shortcuts.zig");const terminal_action_decoder = @import("terminal_action_decoder.zig"); const ImageBlocks = kill_ring.ImageBlocks;const InputRuntime = core_input_runtime.Runtime;const InputAppearance = core_input_runtime.InputAppearance; const Allocator = std.mem.Allocator;const InsertResult = composer_insertion.InsertResult; pub const DeferredTerminalInputSource = enum {    theme_monitor,    cursor_probe,}; pub const TerminalInputOwner = enum {    theme_monitor,    paste,    takeover,    fx_input,}; pub fn terminalInputOwner(    monitor: *const theme_monitor.Monitor,    paste_active: bool,    takeover_active: bool,) TerminalInputOwner {    if (monitor.ownsInput()) return .theme_monitor;    if (paste_active) return .paste;    if (takeover_active) return .takeover;    if (monitor.enabled) return .theme_monitor;    return .fx_input;} const InputEscapeAction = input_action.Action;const MouseInput = escape_parser.MouseInput; pub const shortcutFromControlByte = shortcuts.fromControlByte;pub const shortcutFromFocusedEditorControlByte = shortcuts.fromFocusedEditorControlByte;pub const shortcutFromEscapeAction = shortcuts.fromEscapeAction; pub fn approvalActionFromByte(byte: u8) ?approval_decision.Action {    return switch (byte) {        3 => .deny,        '\r', '\n' => .submit,        '\t' => .tab,        0x7F, 0x08 => .backspace,        '1'...'3' => .{ .number = byte - '1' },        else => if (byte >= 0x20 and byte <= 0x7E)            .{ .insert_ascii = byte }        else            null,    };} fn approvalDraftActionFromShortcut(    action: input_action.ShortcutAction,) ?approval_decision.DraftAction {    return switch (action) {        .move => |intent| switch (intent.kind) {            .character_left => .cursor_left,            .character_right => .cursor_right,            .line_start, .draft_start => .cursor_home,            .line_end, .draft_end => .cursor_end,            .word_left => .cursor_word_left,            .word_right => .cursor_word_right,            else => null,        },        .delete_forward => .delete_next,        .delete_word_left => .delete_word_left,        .delete_whitespace_word_left => .delete_whitespace_word_left,        .delete_word_right => .delete_word_right,        .delete_to_line_start => .delete_to_line_start,        .delete_to_line_end => .delete_to_line_end,        else => null,    };} fn approvalDraftActionFromControlByte(byte: u8) ?approval_decision.DraftAction {    const action = shortcuts.fromFocusedEditorControlByte(byte) orelse return null;    return approvalDraftActionFromShortcut(action);} fn approvalActionFromRawByte(byte: u8) ?approval_decision.Action {    if (approvalDraftActionFromControlByte(byte)) |edit| {        return .{ .edit_draft = edit };    }    return approvalActionFromByte(byte);} fn withApprovalInput(    ingress: input_action.TerminalInputIngress,) input_action.TerminalInputIngress {    var typed = ingress;    const event = typed.event orelse return typed;    typed.event = switch (event) {        .raw => |raw| input_action.TerminalInputEvent{ .raw = .{            .byte = raw.byte,            .composer_shortcut = raw.composer_shortcut,            .approval_action = approvalActionFromRawByte(raw.byte),        } },        .action => |decoded| input_action.TerminalInputEvent{ .action = .{            .action = decoded.action,            .composer_shortcut = decoded.composer_shortcut,            .approval_focused_edit = if (decoded.composer_shortcut) |shortcut|                approvalDraftActionFromShortcut(shortcut)            else                null,            .cancel_pending = decoded.cancel_pending,        } },        .paste_byte, .unrecognized_escape => event,    };    return typed;} fn questionActionFromByte(    byte: u8,    freeform_selected: bool,) ?question_prompt.Action {    return switch (byte) {        3 => .cancel,        '\r', '\n' => .submit,        '\t' => .next_entry,        0x7F, 0x08 => if (freeform_selected) .backspace else null,        '1'...'9' => if (freeform_selected)            .{ .insert_ascii = byte }        else            .{ .select_ordinal = byte - '1' },        else => if (freeform_selected and byte >= 0x20 and byte <= 0x7E)            .{ .insert_ascii = byte }        else            null,    };} fn questionFreeformActionFromShortcut(    action: input_action.ShortcutAction,) ?question_prompt.FreeformAction {    return switch (action) {        .move => |intent| switch (intent.kind) {            .character_left => .cursor_left,            .character_right => .cursor_right,            .line_start, .draft_start => .cursor_home,            .line_end, .draft_end => .cursor_end,            .word_left => .cursor_word_left,            .word_right => .cursor_word_right,            else => null,        },        .delete_forward => .delete_next,        .delete_word_left => .delete_word_left,        .delete_whitespace_word_left => .delete_whitespace_word_left,        .delete_word_right => .delete_word_right,        .delete_to_line_start => .delete_to_line_start,        .delete_to_line_end => .delete_to_line_end,        else => null,    };} fn questionActionFromShortcut(    action: input_action.ShortcutAction,    freeform_selected: bool,) ?question_prompt.Action {    switch (action) {        .move => |intent| {            if (intent.extend_selection) {                switch (intent.kind) {                    .visual_up => return .{ .move_choice = .previous },                    .visual_down => return .{ .move_choice = .next },                    else => {},                }            }        },        else => {},    }    if (!freeform_selected) return null;    const edit = questionFreeformActionFromShortcut(action) orelse return null;    return .{ .edit_freeform = edit };} fn questionActionFromRawByte(    byte: u8,    freeform_selected: bool,) ?question_prompt.Action {    if (freeform_selected) {        if (shortcuts.fromFocusedEditorControlByte(byte)) |shortcut| {            return questionActionFromShortcut(shortcut, true);        }    }    return questionActionFromByte(byte, freeform_selected);} fn questionActionFromDecoded(    action: input_action.Action,    shortcut: ?input_action.ShortcutAction,    freeform_selected: bool,) ?question_prompt.Action {    switch (action) {        .remapped_byte => |byte| return questionActionFromRawByte(            byte,            freeform_selected,        ),        else => {},    }    return questionActionFromShortcut(        shortcut orelse return null,        freeform_selected,    );} fn withQuestionInput(    ingress: input_action.TerminalInputIngress,    freeform_selected: bool,) input_action.TerminalInputIngress {    var typed = ingress;    const event = typed.event orelse return typed;    typed.event = switch (event) {        .raw => |raw| input_action.TerminalInputEvent{ .raw = .{            .byte = raw.byte,            .composer_shortcut = raw.composer_shortcut,            .approval_action = raw.approval_action,            .question_action = questionActionFromRawByte(                raw.byte,                freeform_selected,            ),        } },        .action => |decoded| input_action.TerminalInputEvent{ .action = .{            .action = decoded.action,            .composer_shortcut = decoded.composer_shortcut,            .approval_focused_edit = decoded.approval_focused_edit,            .question_action = questionActionFromDecoded(                decoded.action,                decoded.composer_shortcut,                freeform_selected,            ),            .cancel_pending = decoded.cancel_pending,        } },        .paste_byte, .unrecognized_escape => event,    };    return typed;} fn subagentActionFromShortcut(    action: input_action.ShortcutAction,) ?subagent_input.Action {    return switch (action) {        .move => |intent| if (intent.extend_selection)            null        else switch (intent.kind) {            .character_left => .left,            .character_right => .right,            .word_left => .word_left,            .word_right => .word_right,            .line_start, .draft_start => .home,            .line_end, .draft_end => .end,            .visual_up => .up,            .visual_down => .down,            .page_up => .page_up,            .page_down => .page_down,            .paragraph_up, .paragraph_down => null,        },        .delete_backward => .delete_backward,        .delete_forward => .delete_next,        .delete_word_left => .delete_word_left,        .delete_word_right => .delete_word_right,        .delete_to_line_start => .delete_to_line_start,        .delete_to_line_end => .delete_to_line_end,        .insert_newline => .insert_newline,        .select_all,        .copy_selection,        .cut_selection,        .undo,        .redo,        .history_previous,        .history_next,        .delete_whitespace_word_left,        .yank,        .redraw,        => null,    };} fn subagentActionFromRawByte(byte: u8) ?subagent_input.Action {    return switch (byte) {        3 => .ctrl_c,        24 => .toggle,        '\r' => .enter,        '\t' => .focus_next,        1 => .home,        5 => .end,        0x7f, 8 => .delete_backward,        11 => .delete_to_line_end,        21 => .clear_line,        23 => .delete_word_left,        else => null,    };} fn subagentActionFromDecoded(action: input_action.Action) ?subagent_input.Action {    return switch (action) {        .escape => .escape,        .history_up, .cursor_up => .up,        .history_down, .cursor_down => .down,        .cursor_left => .left,        .cursor_right => .right,        .home => .home,        .end => .end,        .word_left => .word_left,        .word_right => .word_right,        .delete_next => .delete_next,        .delete_word_left => .delete_word_left,        .delete_word_right => .delete_word_right,        .delete_to_line_start => .delete_to_line_start,        .delete_to_line_end => .delete_to_line_end,        .clear_line => .clear_line,        .insert_newline => .insert_newline,        .page_up => .page_up,        .page_down => .page_down,        .composer_shortcut => |typed| subagentActionFromShortcut(typed),        .remapped_byte => |byte| subagentActionFromRawByte(byte),        .mouse_wheel,        .mouse_pointer,        .toggle_full_transcript,        .toggle_permission_mode,        .open_all_sessions,        .paste_start,        .paste_end,        .ignore,        => null,    };} fn withSubagentInput(    ingress: input_action.TerminalInputIngress,) input_action.TerminalInputIngress {    var typed = ingress;    const event = typed.event orelse return typed;    typed.event = switch (event) {        .raw => |raw| input_action.TerminalInputEvent{ .raw = .{            .byte = raw.byte,            .composer_shortcut = raw.composer_shortcut,            .approval_action = raw.approval_action,            .question_action = raw.question_action,            .subagent_action = subagentActionFromRawByte(raw.byte),        } },        .action => |decoded| input_action.TerminalInputEvent{ .action = .{            .action = decoded.action,            .composer_shortcut = decoded.composer_shortcut,            .approval_focused_edit = decoded.approval_focused_edit,            .question_action = decoded.question_action,            .subagent_action = subagentActionFromDecoded(decoded.action),            .cancel_pending = decoded.cancel_pending,        } },        .paste_byte, .unrecognized_escape => event,    };    return typed;} test "terminal input carries typed subagent controls and shortcuts" {    var runtime = Runtime{};    defer runtime.deinit(std.testing.allocator);    const context: input_action.TerminalDecodeContext = .{        .now_ms = 1,        .paste_active = false,        .cancel_pending = false,        .child_route_active = true,    };     const toggle = runtime.decodeTerminalByte(24, context);    try std.testing.expectEqual(        subagent_input.Action.toggle,        toggle.event.?.raw.subagent_action.?,    );     const line_start = runtime.decodeTerminalByte(1, context);    try std.testing.expectEqual(        subagent_input.Action.home,        line_start.event.?.raw.subagent_action.?,    );     const clear_line = runtime.decodeTerminalByte(21, context);    try std.testing.expectEqual(        subagent_input.Action.clear_line,        clear_line.event.?.raw.subagent_action.?,    );     const child_only_shortcut = runtime.decodeTerminalByte(2, context);    try std.testing.expect(child_only_shortcut.event.?.raw.subagent_action == null);    try std.testing.expectEqual(        input_action.ShortcutAction{ .move = .{ .kind = .character_left } },        child_only_shortcut.event.?.raw.composer_shortcut.?,    );     const text = runtime.decodeTerminalByte('j', context);    try std.testing.expect(text.event.?.raw.subagent_action == null);     var word_delete = input_action.TerminalInputIngress{};    for ("\x1bd") |byte| {        word_delete = runtime.decodeTerminalByte(byte, context);    }    try std.testing.expectEqual(        subagent_input.Action.delete_word_right,        word_delete.event.?.action.subagent_action.?,    );     var arrow = input_action.TerminalInputIngress{};    for ("\x1b[A") |byte| {        arrow = runtime.decodeTerminalByte(byte, context);    }    try std.testing.expectEqual(        subagent_input.Action.up,        arrow.event.?.action.subagent_action.?,    );} test "question bytes translate to typed prompt actions" {    try std.testing.expectEqual(        question_prompt.Action.cancel,        questionActionFromByte(3, false).?,    );    try std.testing.expectEqual(        question_prompt.Action.submit,        questionActionFromByte('\r', false).?,    );    try std.testing.expectEqual(        question_prompt.Action.next_entry,        questionActionFromByte('\t', false).?,    );    try std.testing.expectEqual(        question_prompt.Action{ .select_ordinal = 2 },        questionActionFromByte('3', false).?,    );    try std.testing.expectEqual(        question_prompt.Action{ .insert_ascii = '3' },        questionActionFromByte('3', true).?,    );    try std.testing.expectEqual(        question_prompt.Action.backspace,        questionActionFromByte(0x7F, true).?,    );    try std.testing.expect(questionActionFromByte('x', false) == null);    try std.testing.expect(questionActionFromByte(0x7F, false) == null);    try std.testing.expect(questionActionFromByte(0x80, true) == null);} test "terminal input carries typed question decisions and focused edits" {    var runtime = Runtime{};    defer runtime.deinit(std.testing.allocator);     const choice_context: input_action.TerminalDecodeContext = .{        .now_ms = 1,        .paste_active = false,        .cancel_pending = false,        .child_route_active = false,        .question_freeform_selected = false,    };    const choice = runtime.decodeTerminalByte('3', choice_context);    try std.testing.expectEqual(        question_prompt.Action{ .select_ordinal = 2 },        choice.event.?.raw.question_action.?,    );     const freeform_context: input_action.TerminalDecodeContext = .{        .now_ms = 2,        .paste_active = false,        .cancel_pending = false,        .child_route_active = false,        .question_freeform_selected = true,    };    const digit = runtime.decodeTerminalByte('3', freeform_context);    try std.testing.expectEqual(        question_prompt.Action{ .insert_ascii = '3' },        digit.event.?.raw.question_action.?,    );    const focused_control = runtime.decodeTerminalByte(1, freeform_context);    try std.testing.expectEqual(        question_prompt.Action{ .edit_freeform = .cursor_home },

Lines 1–500 of 4981.