Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ final class MachineRegistry {
session(machineID)?.machine.title ?? sshSession(machineID)?.host.label ?? server(machineID)?.name
}

/// The name a tab's machine badge takes its color from (`TabItem.machineName`): the sidebar
/// name of `machineID` when `badge` is that machine's short name (an SSH host's badge shows
/// its destination, the sidebar its label), so the tab wears its rows' MachineColor. Nil for
/// any other badge (a "this Mac" chip) or a machine not known here: the badge's own text.
func tabColorName(_ machineID: String?, badge: String?) -> String? {
guard let machineID, let badge, badge == machineBadge(machineID) else { return nil }
return machineName(machineID)
}

/// What `daemon` can do for the app. A remote daemon never needs the
/// home-only capabilities, nor, once the local daemon keeps personal
/// state (`profiles-v1`), workspace groups and saved tab groups.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ final class PaneController: SurfacePresenter, PresentablePane {
item.isDormant = services.cache.dormantTabs.contains(tab.id)
if tab.kind == .remoteTerminal {
// Its terminal runs on another machine: that machine's name.
item.machineBadge = services.remoteTerminals.badge(for: tab)
(item.machineBadge, item.machineName) = services.remoteTerminals.badge(for: tab)
item.icon = .icon(.terminal)
} else if tab.kind != .browser {
(item.machineBadge, item.machineName) = (machine, machine.flatMap { _ in services.machines.machineName(daemon.machineID) })
Expand All @@ -224,7 +224,7 @@ final class PaneController: SurfacePresenter, PresentablePane {
// A browser tab names the machine whose localhost it sees.
let engine: BrowserEngineKind = tab.browserEngine == BrowserEngineTag.cef.rawValue ? .cef : .webkit
let badge = services.remoteLocalhost.badge(for: tab, url: tab.url.flatMap(URL.init(string:)), engine: engine)
(item.machineBadge, item.machineBadgeHelp) = (badge?.text, badge?.help)
(item.machineBadge, item.machineBadgeHelp, item.machineName) = (badge?.text, badge?.help, services.machines.tabColorName(daemon.machineID, badge: badge?.text))
// Incognito tabs have only a placeholder record in the daemon.
let incognito = services.cache.browserTabs.isIncognitoTab(tab.id)
if incognito {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,12 @@ final class RemoteTerminalService {
/// The placeholder tab `key` shows now, if any.
func existingPlaceholder(_ key: String) -> RemoteTerminalPlaceholderView? { placeholders[key] }

/// The machine name on a remote-terminal tab (the subtle machine badge).
func badge(for tab: TabModel) -> String? {
guard let ref = tab.remote else { return nil }
return machineName(ref)
/// The machine name on a remote-terminal tab (the subtle machine badge), and the name its
/// color comes from (`MachineRegistry.tabColorName`: the machine's sidebar name).
func badge(for tab: TabModel) -> (text: String?, colorName: String?) {
guard let ref = tab.remote else { return (nil, nil) }
let text = machineName(ref)
return (text, services.machines.tabColorName(services.machines.daemon(session: ref.sessionID)?.machineID, badge: text))
}

func machineName(_ ref: RemoteTerminalRef) -> String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import CmuxNextDaemonService
import CmuxNextRemote
import CmuxNextRemoteLocalhostService
import Foundation
import Testing
@testable import CmuxNextApp

/// A machine wears one color (MachineColor, #22763), hashed from its sidebar name: a tab's
/// machine badge takes it from `MachineRegistry.tabColorName`, so remote terminals and
/// remote-localhost browser tabs match the machine's sidebar rows and composer chip even
/// where the badge shows other text. An SSH host named "lab" at leo@huge-blue badges its
/// tabs "huge-blue"; they hashed "huge-blue" and wore another color than the "lab" rows.
@MainActor
struct TabMachineColorTests {
static func sshMachine(_ machines: MachineRegistry) throws -> SSHMachineSession {
let host = try SSHHost(destination: SSHDestination(parsing: "leo@huge-blue.tail1.ts.net")).named("lab")
let session = SSHMachineSession(host: host, binary: URL(fileURLWithPath: "/usr/bin/false"),
paths: SSHPaths(root: URL(fileURLWithPath: "/private/tmp/cn-tab-machine-color-unused")),
environment: { [:] })
machines.add(session)
return session
}

@Test func anSSHHostsTabBadgeTakesTheColorOfItsSidebarName() throws {
let machines = MachineRegistry(local: DaemonService())
let id = try Self.sshMachine(machines).machineID
let (badge, sidebar) = (machines.machineBadge(id), machines.machineName(id))
#expect(badge == "huge-blue" && sidebar == "lab", "the badge shows the destination, the sidebar the label")
#expect(machines.tabColorName(id, badge: badge) == "lab", "a terminal tab on the host")
// A remote-localhost browser tab badges the machine's localhost with the same short name.
let localhost = RemoteLocalhostService(machines: machines, environment: [:])
let browser = localhost.machineName(of: try #require(machines.anyDaemon(machine: id)))
#expect(machines.tabColorName(id, badge: browser) == "lab", "a browser tab seeing the host's localhost")
}

@Test func otherBadgesKeepTheirOwnTextsColor() throws {
let machines = MachineRegistry(local: DaemonService())
let id = try Self.sshMachine(machines).machineID
#expect(machines.tabColorName(id, badge: "this Mac") == nil, "a This Mac chip names no machine of the tree")
#expect(machines.tabColorName(id, badge: nil) == nil)
#expect(machines.tabColorName("vm-unknown", badge: "vm-unknown") == nil, "a machine not known here")
#expect(machines.tabColorName(MachineRegistry.localID, badge: "huge-blue") == nil)
#expect(machines.tabColorName(nil, badge: "huge-blue") == nil)
}
}