From 044a4920bbdfb41c5c8d105318a79720c21c637f Mon Sep 17 00:00:00 2001 From: jay Date: Sat, 16 May 2026 03:30:31 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20terminal=20rendering=20=E2=80=94=20resiz?= =?UTF-8?q?e=20sync,=20TERM=20via=20sidecar,=20remove=20double-dot=20statu?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/Core/Network/FrameCodec.swift | 10 +++++++++- Sources/UI/Terminal/MainTerminalView.swift | 16 +++++++++++++--- Sources/UI/Terminal/TerminalViewController.swift | 11 +++++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Sources/Core/Network/FrameCodec.swift b/Sources/Core/Network/FrameCodec.swift index fc444ba..12fc301 100644 --- a/Sources/Core/Network/FrameCodec.swift +++ b/Sources/Core/Network/FrameCodec.swift @@ -93,11 +93,14 @@ public enum ClientToServer: Sendable { case paste(data: String) /// Request a full ANSI snapshot of the current pane. case snapshotRequest + /// Notify the sidecar of the client's terminal dimensions so tmux can + /// resize the window to match. Send on connect and on every layout change. + case resize(cols: Int, rows: Int) } extension ClientToServer: Encodable { private enum CodingKeys: String, CodingKey { - case type, lastSeq, name, data + case type, lastSeq, name, data, cols, rows } public func encode(to encoder: any Encoder) throws { @@ -123,6 +126,11 @@ extension ClientToServer: Encodable { case .snapshotRequest: try c.encode("snapshot-request", forKey: .type) + + case .resize(let cols, let rows): + try c.encode("resize", forKey: .type) + try c.encode(cols, forKey: .cols) + try c.encode(rows, forKey: .rows) } } } diff --git a/Sources/UI/Terminal/MainTerminalView.swift b/Sources/UI/Terminal/MainTerminalView.swift index 3c3fda9..b2c038d 100644 --- a/Sources/UI/Terminal/MainTerminalView.swift +++ b/Sources/UI/Terminal/MainTerminalView.swift @@ -19,9 +19,6 @@ struct MainTerminalView: View { VStack(spacing: 0) { // ── Status bar ────────────────────────────────────────── HStack { - Circle() - .fill(connection != nil ? Color.green : Color.orange) - .frame(width: 8, height: 8) Text(statusText) .font(.caption.monospaced()) .foregroundStyle(.secondary) @@ -85,10 +82,23 @@ struct MainTerminalView: View { } .store(in: &cancellables) + // Wire resize callback — fires on layout + font changes. + terminalVC.onResize = { [weak conn] cols, rows in + guard let conn else { return } + Task { try? await conn.send(.resize(cols: cols, rows: rows)) } + } + connection = conn await conn.resume(from: conn.scrollback.sizeBytes > 0 ? ResumeCursor().lastSeq(for: sessionId) : nil) + + // Send the current terminal size immediately after connecting + // so tmux resizes before the first output frame arrives. + let (cols, rows) = terminalVC.terminalSize + if cols > 0 && rows > 0 { + try? await conn.send(.resize(cols: cols, rows: rows)) + } } catch { statusText = "Error: \(error.localizedDescription)" } diff --git a/Sources/UI/Terminal/TerminalViewController.swift b/Sources/UI/Terminal/TerminalViewController.swift index d23adad..36a17b8 100644 --- a/Sources/UI/Terminal/TerminalViewController.swift +++ b/Sources/UI/Terminal/TerminalViewController.swift @@ -31,6 +31,10 @@ public final class TerminalViewController: UIViewController { /// forwarded to the remote PTY (wired by T-2.5). public var onInput: ((Data) -> Void)? + /// Called when SwiftTerm recalculates its column/row count (layout, + /// font change, rotation). Forward this to the sidecar so tmux resizes. + public var onResize: ((Int, Int) -> Void)? + // MARK: View lifecycle public override func viewDidLoad() { @@ -139,8 +143,11 @@ extension TerminalViewController: TerminalViewDelegate { // MARK: Required — size change notification public nonisolated func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) { - // No-op in T-2.3; the stream layer (T-2.5) will negotiate PTY size - // with the sidecar when it subscribes to this controller. + // Propagate to whoever manages the sidecar connection so they can + // call `tmux resize-window` and keep line-wrapping in sync. + MainActor.assumeIsolated { + onResize?(newCols, newRows) + } } // MARK: Required — title change