diff --git a/Packages/CrowTerminal/Sources/CrowTerminal/TmuxBackend.swift b/Packages/CrowTerminal/Sources/CrowTerminal/TmuxBackend.swift index b23dfe6..a82ff1f 100644 --- a/Packages/CrowTerminal/Sources/CrowTerminal/TmuxBackend.swift +++ b/Packages/CrowTerminal/Sources/CrowTerminal/TmuxBackend.swift @@ -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 diff --git a/Packages/CrowTerminal/Sources/CrowTerminal/TmuxController.swift b/Packages/CrowTerminal/Sources/CrowTerminal/TmuxController.swift index d099e47..ac50131 100644 --- a/Packages/CrowTerminal/Sources/CrowTerminal/TmuxController.swift +++ b/Packages/CrowTerminal/Sources/CrowTerminal/TmuxController.swift @@ -206,6 +206,14 @@ public struct TmuxController: Sendable { try run(["paste-buffer", "-b", name, "-t", target]) } + /// `tmux send-keys -t `. 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]) }