Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions Packages/CrowTerminal/Sources/CrowTerminal/TmuxBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,16 +241,33 @@ public final class TmuxBackend {
/// Send text to `id`'s window via the buffer-paste path. Works for
/// arbitrary-size payloads (Phase 3 §3 finding: send-keys -l fails
/// on >10KB; load-buffer + paste-buffer scales to 50KB+ in 133ms).
///
/// Quirk: Claude Code's TUI enables bracketed-paste mode, which wraps
/// `paste-buffer` output in `\e[200~…\e[201~`. A trailing `\n` inside the
/// bracket is treated as literal text, not as Enter — so prompts that
/// rely on `\n` to submit (quick actions: Merge PR, Fix Conflicts, …) get
/// pasted but never submitted (#264). Strip the trailing newline before
/// pasting and deliver a separate `Enter` via `send-keys` afterwards,
/// mirroring what `GhosttySurfaceView.writeText` does with keycode 36.
public func sendText(id: UUID, text: String) throws {
guard let windowIndex = bindings[id] else {
throw TmuxBackendError.unknownTerminal(id)
}
do {
let ctrl = try ensureRunningServer()
let bufferName = "crow-\(id.uuidString)"
try ctrl.loadBufferFromStdin(name: bufferName, data: Data(text.utf8))
defer { ctrl.deleteBuffer(name: bufferName) }
try ctrl.pasteBuffer(name: bufferName, target: "\(ctrl.sessionName):\(windowIndex)")
let target = "\(ctrl.sessionName):\(windowIndex)"
let endsWithNewline = text.hasSuffix("\n")
let payload = endsWithNewline ? String(text.dropLast()) : text

if !payload.isEmpty {
let bufferName = "crow-\(id.uuidString)"
try ctrl.loadBufferFromStdin(name: bufferName, data: Data(payload.utf8))
defer { ctrl.deleteBuffer(name: bufferName) }
try ctrl.pasteBuffer(name: bufferName, target: target)
}
if endsWithNewline {
try ctrl.sendKeys(target: target, keys: ["Enter"])
}
} catch {
reportIfTimeout(error)
throw error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,14 @@ public struct TmuxController: Sendable {
try run(["paste-buffer", "-b", name, "-t", target])
}

/// `tmux send-keys -t <target> <keys...>`. Each entry in `keys` is passed
/// as a separate argument (e.g. "Enter", "C-c"). Used by
/// `TmuxBackend.sendText` to deliver an Enter *outside* the bracketed-paste
/// bracket so prompts that end with `\n` are actually submitted (#264).
public func sendKeys(target: String, keys: [String]) throws {
try run(["send-keys", "-t", target] + keys)
}

public func deleteBuffer(name: String) {
_ = try? run(["delete-buffer", "-b", name])
}
Expand Down
Loading