fix: sync SFTP directory context

This commit is contained in:
Kaede Akino committed 2026-08-25 12:28:40 +08:00
1 parent 264a2dc8ae
commit 339665e6fb
3 files changed
+64 -2

No files matched your search

+11 -1
View File
@@ -1,5 +1,9 @@
import Foundation
protocol SFTPSessionNavigationRouting: AnyObject {
func sftpSession(_ sftpSession: SFTPSessionModel, didChangePath path: String)
}
/// State consumed by SwiftUI's macOS `Table` must change as one value.
///
/// `Table` is backed by `NSOutlineView`. Publishing its rows, filter, and
@@ -64,7 +68,12 @@ final class SFTPSessionModel: Identifiable {
let id = UUID()
let hostID: Host.ID
let initialPath: String
private(set) var path: String
private(set) var path: String {
didSet {
guard path != oldValue else { return }
navigationRouter?.sftpSession(self, didChangePath: path)
}
}
var pathText: String
private(set) var backHistory: [String] = []
private(set) var forwardHistory: [String] = []
@@ -117,6 +126,7 @@ final class SFTPSessionModel: Identifiable {
private var hasConnectedOnce = false
private var isRecoveringConnection = false
private var operationCancellation: TransferCancellationToken?
weak var navigationRouter: (any SFTPSessionNavigationRouting)?
private weak var statusStore: HostStore?
private weak var transferCoordinator: SFTPTransferCoordinator?
private var didResolveInitialPath: Bool
+17 -1
View File
@@ -221,30 +221,46 @@ final class Workspace: Identifiable {
}
}
/// Keeps the sshSession-to-Workspace relationship correct as panes are merged,
/// Keeps each session-to-Workspace relationship correct as panes are merged,
/// detached, replaced, and tiled. Removed sessions are cleared only when
/// this Workspace still owns their route; a destination may already have
/// claimed them during a tree rewrite.
private func synchronizeInputRouters(previousLayout: SplitNode? = nil) {
let currentSessions = broadcastTargets
let currentIDs = Set(currentSessions.map(\.id))
let currentSFTPSessions = panes.compactMap(\.sftpSession)
let currentSFTPIDs = Set(currentSFTPSessions.map(\.id))
if let previousLayout {
for sshSession in previousLayout.panes.compactMap(\.sshSession)
where !currentIDs.contains(sshSession.id) && sshSession.inputRouter === self {
sshSession.inputRouter = nil
}
for sftpSession in previousLayout.panes.compactMap(\.sftpSession)
where !currentSFTPIDs.contains(sftpSession.id) && sftpSession.navigationRouter === self {
sftpSession.navigationRouter = nil
}
}
for sshSession in currentSessions {
sshSession.inputRouter = self
}
for sftpSession in currentSFTPSessions {
sftpSession.navigationRouter = self
}
if !canBroadcast { broadcastsInput = false }
}
}
extension Workspace: SFTPSessionNavigationRouting {
func sftpSession(_ sftpSession: SFTPSessionModel, didChangePath path: String) {
guard focusedPane.sftpSession === sftpSession else { return }
lifecycleRouter?.workspaceContextDidChange(self)
}
}
extension Workspace: SSHSessionInputRouting {
/// Copies the exact bytes Ghostty produced for a keyboard or paste action.
/// Sending directly preserves each event's terminal encoding and avoids
+36
View File
@@ -110,6 +110,42 @@ struct WindowStateTests {
#expect(window.sidebar.assistantDraft.contextChips.map(\.label) == ["Files", "/srv/app"])
}
@Test
func focusedSFTPPathChangesUpdateAssistantContext() {
let host = Host(name: "Files", host: "files.example")
let workspace = Workspace.sftpSession(host: host, initialPath: "/srv")
let sftpSession = workspace.focusedPane.sftpSession!
let window = WindowModel(tabs: [workspace])
window.hostStore = HostStore(hosts: [host])
sftpSession.navigate(to: "/srv/releases")
#expect(window.sidebar.assistantDraft.contextChips.map(\.kind) == [.host, .directory])
#expect(window.sidebar.assistantDraft.contextChips.map(\.label) == ["Files", "/srv/releases"])
sftpSession.goBack()
#expect(window.sidebar.assistantDraft.contextChips.map(\.label) == ["Files", "/srv"])
}
@Test
func backgroundSFTPPathDoesNotReplaceFocusedPaneContext() {
let host = Host(name: "Files", host: "files.example")
let workspace = Workspace.sftpSession(host: host, initialPath: "/focused")
let focusedPane = workspace.focusedPane
workspace.splitFocusedPane(
axis: .horizontal,
with: .sftpSession(host: host, initialPath: "/background")
)
let backgroundSession = workspace.focusedPane.sftpSession!
workspace.focus(focusedPane.id)
let window = WindowModel(tabs: [workspace])
window.hostStore = HostStore(hosts: [host])
backgroundSession.navigate(to: "/background/changed")
#expect(window.sidebar.assistantDraft.contextChips.map(\.label) == ["Files", "/focused"])
}
@Test
func openingSidebarConversationRefreshesPinnedContextAndPreservesAttachments() {
let host = Host(name: "Server", host: "server.example")