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
@@ -0,0 +1,36 @@
public import Foundation

/// Decides whether a terminal open-URL target may be handled by cmux's file UI.
public struct TerminalOpenURLFileRoutingPolicy: Sendable {
/// Creates the file-routing policy.
public init() {}

/// Returns whether cmux may attempt to open the target in its file preview UI.
///
/// The caller still owns settings, file existence, workspace locality, and
/// split creation checks. This policy only answers whether the raw terminal
/// open-URL payload represents a local file shape that cmux is allowed to
/// intercept before the normal URL routing decision.
///
/// - Parameters:
/// - rawOpenURLValue: The raw open-URL payload from the terminal runtime.
/// - target: The parsed terminal link target.
public func shouldAttemptCmuxFileRouting(
rawOpenURLValue: String,
target: TerminalOpenURLTarget
) -> Bool {
guard !hasExplicitURLScheme(rawOpenURLValue) else { return false }
guard target.url.isFileURL else { return false }
return isLocalFileURL(target.url)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private func hasExplicitURLScheme(_ rawValue: String) -> Bool {
let trimmed = rawValue.trimmingCharacters(in: .whitespacesAndNewlines)
guard let scheme = URL(string: trimmed)?.scheme else { return false }
return !scheme.isEmpty
}

private func isLocalFileURL(_ url: URL) -> Bool {
url.host?.isEmpty ?? true
Comment thread
austinywang marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Foundation
import Testing
import CmuxTerminalCore

@Suite struct TerminalOpenURLFileRoutingPolicyTests {
private let policy = TerminalOpenURLFileRoutingPolicy()

@Test func explicitFileSchemeBypassesCmuxFileRouting() throws {
let url = try #require(URL(string: "file:///Users/dev/out/ab_cosyvoice_emo.wav"))
#expect(
policy.shouldAttemptCmuxFileRouting(
rawOpenURLValue: url.absoluteString,
target: .external(url)
) == false
)
}

@Test func localhostFileSchemeBypassesCmuxFileRouting() throws {
let url = try #require(URL(string: "file://localhost/Users/dev/out/ab_cosyvoice_emo.wav"))
#expect(
policy.shouldAttemptCmuxFileRouting(
rawOpenURLValue: url.absoluteString,
target: .external(url)
) == false
)
}

@Test func hostedFileTargetBypassesCmuxFileRoutingEvenWithoutRawScheme() throws {
let url = try #require(URL(string: "file://remote-host/Users/dev/out/ab_cosyvoice_emo.wav"))
#expect(
policy.shouldAttemptCmuxFileRouting(
rawOpenURLValue: "/Users/dev/out/ab_cosyvoice_emo.wav",
target: .external(url)
) == false
)
}

@Test func absolutePathCanStillUseCmuxFileRouting() {
let url = URL(fileURLWithPath: "/Users/dev/project/README.md")
#expect(
policy.shouldAttemptCmuxFileRouting(
rawOpenURLValue: "/Users/dev/project/README.md",
target: .external(url)
)
)
}

@Test func nonFileTargetsBypassCmuxFileRouting() throws {
let url = try #require(URL(string: "https://example.com/audio.wav"))
#expect(
policy.shouldAttemptCmuxFileRouting(
rawOpenURLValue: url.absoluteString,
target: .embeddedBrowser(url)
) == false
)
}
}
18 changes: 8 additions & 10 deletions Sources/GhosttyTerminalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3086,16 +3086,14 @@ class GhosttyApp {
return true
}
#endif
// Route local file URLs into cmux when the file-routing toggle is on.
// URL fragments/queries are stripped (the panel only needs the file
// path), so links emitted by tools like Claude Code (`foo.md#L42`)
// still route into the viewer. Anything else (toggle off, hosted
// file URL, remote workspace, unreadable file, split creation
// failure) falls through to the existing NSWorkspace path below so
// URL semantics are preserved.
let fileURLHost = target.url.host
if target.url.isFileURL,
fileURLHost == nil || fileURLHost?.isEmpty == true || fileURLHost == "localhost" {
// Route local file paths into cmux when the file-routing toggle is on.
// Explicit URL schemes (including file://) stay on the URL route so
// the OS owns non-web schemes, while bare paths like `foo.md#L42`
// still route into the viewer when eligible.
if TerminalOpenURLFileRoutingPolicy().shouldAttemptCmuxFileRouting(
rawOpenURLValue: trimmedUrlString,
target: target
) {
let fileURL = target.url
let routed: Bool = performOnMain {
guard let termSurface = surfaceView.terminalSurface,
Expand Down
Loading