fix(terminal): use default transport for SFTP companion sessions

This commit is contained in:
Kaede Akino committed 2026-10-16 15:21:10 +08:00
1 parent 3c06c23435
commit 7f66b3a444
10 files changed
+160 -103

No files matched your search

+82 -82
View File
@@ -47438,6 +47438,47 @@
}
}
},
"Open current directory with %@ — right-click for more options" : {
"extractionState" : "manual",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aktuelles Verzeichnis mit %@ öffnen — Rechtsklick für weitere Optionen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ouvrir le répertoire actuel via %@ — clic droit pour plus d’options"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "現在のディレクトリを%@で開く — 右クリックでその他の操作を表示"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@로 현재 디렉터리 열기 — 추가 옵션을 보려면 마우스 오른쪽 버튼 클릭"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "使用%@打开当前目录—右键查看更多操作"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "使用%@開啟目前目錄—右鍵檢視更多操作"
}
}
}
},
"Open current directory with SFTP — right-click for more options" : {
"comment" : "A contextual help for the open button in the status bar.",
"extractionState" : "manual",
@@ -47481,47 +47522,6 @@
}
}
},
"Open current directory with SSH — right-click for more options" : {
"extractionState" : "manual",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aktuelles Verzeichnis mit SSH öffnen — Rechtsklick für weitere Optionen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ouvrir le répertoire actuel via SSH — clic droit pour plus d’options"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "現在のディレクトリをSSHで開く — 右クリックでその他の操作を表示"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "SSH로 현재 디렉터리 열기 — 추가 옵션을 보려면 마우스 오른쪽 버튼 클릭"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "使用SSH打开当前目录—右键查看更多操作"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "使用SSH開啟目前目錄—右鍵檢視更多操作"
}
}
}
},
"Open login page" : {
"extractionState" : "manual",
"localizations" : {
@@ -47645,6 +47645,47 @@
}
}
},
"Open with %@" : {
"extractionState" : "manual",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mit %@ öffnen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ouvrir via %@"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@で開く"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@로 열기"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "使用%@打开"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "使用%@開啟"
}
}
}
},
"Open with SFTP" : {
"extractionState" : "manual",
"localizations" : {
@@ -47686,47 +47727,6 @@
}
}
},
"Open with SSH" : {
"extractionState" : "manual",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mit SSH öffnen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ouvrir via SSH"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "SSHで開く"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "SSH로 열기"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "使用SSH打开"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "使用SSH開啟"
}
}
}
},
"OpenSSH commands are unavailable for routes using an HTTP or SOCKS5 proxy." : {
"extractionState" : "manual",
"localizations" : {
@@ -143,8 +143,8 @@ extension AppStrings {
public static var hostsYouConnectToAppearHere: LocalizedStringResource {
"Hosts you connect to appear here."
}
public static var openWithSsh: LocalizedStringResource {
"Open with SSH"
public static func openWithTransport(_ name: String) -> LocalizedStringResource {
"Open with \(name)"
}
public static var sshIsDisabledForThisHost: LocalizedStringResource {
"SSH is disabled for this Host."
@@ -306,8 +306,8 @@ extension AppStrings {
"Open Telnet Connection?"
}
public static var openDirectoryWithSSHHelp: LocalizedStringResource {
"Open current directory with SSH — right-click for more options"
public static func openDirectoryWithTransportHelp(_ name: String) -> LocalizedStringResource {
"Open current directory with \(name) — right-click for more options"
}
public static var previousWorkspace: LocalizedStringResource {
@@ -241,10 +241,12 @@ final class MobileWorkspace: Identifiable {
path: RemotePath.normalized(workingDirectory)
)
case .sftp:
guard !sftpChannel.path.isEmpty else { return nil }
guard !sftpChannel.path.isEmpty, let host = connectionHost(descriptor.hostID),
host.sshEnabled, let transport = host.preferredTerminalProtocol
else { return nil }
return MobileWorkspaceDirectoryLaunch(
hostID: descriptor.hostID,
kind: .ssh,
kind: .terminal(transport),
path: RemotePath.normalized(sftpChannel.path)
)
case .telnet:
@@ -765,7 +765,12 @@ struct MobileWorkspaceView: View {
if let launch = workspace.sameDirectoryLaunch {
switch launch.kind {
case .ssh, .mosh, .eternalTerminal:
Button(AppStrings.Connections.openWithSsh.swiftUIKey, systemImage: "terminal") {
Button(
AppStrings.Connections.openWithTransport(
(launch.kind.terminalProtocol ?? .ssh).displayName
).swiftUIKey,
systemImage: launch.kind.symbol
) {
openVerifiedDirectory(launch)
}
case .telnet:
+9 -3
View File
@@ -345,13 +345,18 @@ struct MobileWorkspaceTests {
@Suite @MainActor
struct MobileWorkspaceConnectionRecoveryTests {
@Test func sameDirectorySFTPStartupUsesTheOverrideAndOffersSSHOnlyWhileConnected() async throws {
@Test(arguments: [HostTerminalProtocol.ssh, .mosh, .eternalTerminal])
func sameDirectorySFTPUsesTheDefaultTransportOnlyWhileConnected(_ transport: HostTerminalProtocol) async throws {
let connections = MobileSFTPConnectionBox(clients: [MobileTestSFTPClient()])
let app = try MobileAppModel(
inMemory: true,
workspaceConnectionServices: connections.services
)
let host = try configureConnectionFixture(in: app)
var host = try configureConnectionFixture(in: app)
host.terminalTransports.moshEnabled = true
host.terminalTransports.eternalTerminalEnabled = true
host.terminalTransports.defaultTransport = transport
app.library.upsert(host)
let id = try #require(
app.testWorkspaceSession.open(hostID: host.id, kind: .sftp, initialPath: "/srv/releases/"))
let workspace = try #require(app.workspaces.first(where: { $0.id == id }))
@@ -362,7 +367,8 @@ struct MobileWorkspaceConnectionRecoveryTests {
#expect(workspace.sftpChannel.path == "/srv/releases")
#expect(
workspace.sameDirectoryLaunch
== MobileWorkspaceDirectoryLaunch(hostID: host.id, kind: .ssh, path: "/srv/releases")
== MobileWorkspaceDirectoryLaunch(
hostID: host.id, kind: .terminal(transport), path: "/srv/releases")
)
#expect(app.testWorkspaceSession.renameWorkspace(id, to: " Release Files "))
#expect(workspace.descriptor.title == "Release Files")
+6 -2
View File
@@ -505,7 +505,8 @@ struct SFTPBrowserView: View {
}
private func actionsMenu(layout: Layout) -> some View {
Menu {
let transport = app.hostLibrary.host(sftpChannel.hostID)?.preferredTerminalProtocol ?? .ssh
return Menu {
Button(AppStrings.SFTP.newFolder.swiftUIKey, systemImage: MenuSymbol.newFolder) {
newFolderPresented = true
}
@@ -513,7 +514,10 @@ struct SFTPBrowserView: View {
Button(AppStrings.Common.paste.swiftUIKey, systemImage: MenuSymbol.paste) { pasteRemoteItems() }
.disabled(sftpChannel.status != .connected || !canPasteRemoteItems)
Divider()
Menu(AppStrings.Connections.openWithSsh.swiftUIKey, systemImage: MenuSymbol.ssh) {
Menu(
AppStrings.Connections.openWithTransport(transport.displayName).swiftUIKey,
systemImage: transport.symbol
) {
Button(AppStrings.Workspace.inCurrentWorkspace.swiftUIKey, systemImage: MenuSymbol.currentWorkspace) {
window.openCompanionSession(from: sftpChannel.id, placement: .currentWorkspace)
}
+12 -7
View File
@@ -301,20 +301,25 @@ struct StatusBarView: View {
}
private func sftpDirectoryButton(_ path: String, displayed: String) -> some View {
Button {
let transport = host?.preferredTerminalProtocol ?? .ssh
return Button {
app.onboarding.recordUse(of: .statusBarPath)
openInSSH(path, placement: .currentWorkspace)
} label: {
StatusBarPathLabel(
symbol: "apple.terminal", text: displayed, destinationSymbol: "arrow.up.right"
symbol: transport.symbol, text: displayed, destinationSymbol: "arrow.up.right"
)
}
.buttonStyle(.plain)
.help(Text(AppStrings.Workspace.openDirectoryWithSSHHelp))
.accessibilityLabel(Text(AppStrings.Connections.openWithSsh))
.disabled(host?.sshEnabled != true)
.help(Text(AppStrings.Workspace.openDirectoryWithTransportHelp(transport.displayName)))
.accessibilityLabel(Text(AppStrings.Connections.openWithTransport(transport.displayName)))
.onDrag { TerminalPathDrag.provider(for: path) }
.contextMenu {
Menu(AppStrings.Connections.openWithSsh.swiftUIKey, systemImage: MenuSymbol.ssh) {
Menu(
AppStrings.Connections.openWithTransport(transport.displayName).swiftUIKey,
systemImage: transport.symbol
) {
Button(AppStrings.Workspace.inCurrentWorkspace.swiftUIKey, systemImage: MenuSymbol.currentWorkspace) {
app.onboarding.recordUse(of: .statusBarPath)
openInSSH(path, placement: .currentWorkspace)
@@ -355,8 +360,8 @@ struct StatusBarView: View {
}
private func openInSSH(_ path: String, placement: SessionOpenPlacement) {
guard let host else { return }
window.openSSHSession(host: host, initialPath: path, placement: placement)
guard let host, host.sshEnabled, let transport = host.preferredTerminalProtocol else { return }
window.openSSHSession(host: host, transport: transport, initialPath: path, placement: placement)
}
}
+4 -2
View File
@@ -860,7 +860,8 @@ final class WindowModel {
}
func canOpenCompanionSession(from sessionID: UUID) -> Bool {
companionSessionSource(sessionID) != nil
guard let source = companionSessionSource(sessionID) else { return false }
return source.pane.sshSession != nil || source.host.sshEnabled
}
/// Resolve the owning pane again at dispatch, not the pane that happened to
@@ -892,7 +893,8 @@ final class WindowModel {
if source.pane.sshSession != nil {
return openSFTPChannel(host: source.host, initialPath: source.path, placement: placement)
}
return openSSHSession(host: source.host, initialPath: source.path, placement: placement)
guard source.host.sshEnabled, let transport = source.host.preferredTerminalProtocol else { return nil }
return openSSHSession(host: source.host, transport: transport, initialPath: source.path, placement: placement)
}
private func companionSessionSource(_ sessionID: UUID) -> (
+33
View File
@@ -1454,6 +1454,39 @@ struct WindowStateTests {
#expect(sftpWorkspace.focusedPane.sshSession?.hostID == host.id)
}
@Test
func sftpCompanionUsesTheHostDefaultWithoutChangingExplicitSSH() throws {
for transport in [HostTerminalProtocol.ssh, .mosh, .eternalTerminal] {
var host = Host(name: "Shell", host: "shell.example")
host.terminalTransports.moshEnabled = true
host.terminalTransports.eternalTerminalEnabled = true
host.terminalTransports.defaultTransport = transport
let channel = SFTPChannelModel.demo(hostID: host.id, path: "/srv/app")
let workspace = Workspace(pane: Pane(.sftpChannel(channel)))
let window = WindowModel(workspaces: [workspace])
let library = HostLibraryModel(hosts: [host])
window.hostStore = library
#expect(window.canOpenCompanionSession(from: channel.id))
let companion = try #require(
window.openCompanionSession(from: channel.id, placement: .newWorkspace))
#expect(companion.focusedPane.sshSession?.transport == transport)
#expect(window.openSSHSession(host: host).focusedPane.sshSession?.transport == .ssh)
window.select(workspace.id)
let split = try #require(
window.openCompanionSession(from: channel.id, placement: .currentWorkspace))
#expect(split === workspace)
#expect(split.focusedPane.sshSession?.transport == transport)
host.sshEnabled = false
host.telnetEnabled = true
#expect(library.saveHost(host))
#expect(!window.canOpenCompanionSession(from: channel.id))
#expect(window.openCompanionSession(from: channel.id, placement: .newWorkspace) == nil)
}
}
@Test
func sftpWithoutAnOverrideKeepsTheHostsConfiguredInitialPath() {
var host = Host(name: "Files", host: "files.example")