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