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
16 changes: 11 additions & 5 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11087,10 +11087,14 @@ struct ContentView: View {

var openedCount = 0
if BrowserLinkOpenSettings.openSidebarPullRequestLinksInCmuxBrowser() {
let externalNavigationHandler = BrowserExternalNavigationHandler()
for pullRequest in pullRequests {
if tabManager.openBrowser(url: pullRequest.url, insertAtEnd: true) != nil {
openedCount += 1
} else if NSWorkspace.shared.open(pullRequest.url) {
let destination = externalNavigationHandler.sidebarLinkDestination(
for: pullRequest.url, prefersEmbeddedBrowser: true
)
let openedEmbedded = destination == .embeddedBrowser
&& tabManager.openBrowser(url: pullRequest.url, insertAtEnd: true) != nil
if openedEmbedded || NSWorkspace.shared.open(pullRequest.url) {
openedCount += 1
}
}
Expand Down Expand Up @@ -12749,7 +12753,8 @@ struct VerticalTabsSidebar: View, Equatable {
snapshotProvider: { [snapshot = input.workspace] in snapshot }
)
let openInBrowser: @MainActor (URL, Bool) -> Void = { [weak tabManager, workspaceId = tab.id] url, preferBrowser in
if preferBrowser,
if BrowserExternalNavigationHandler()
.sidebarLinkDestination(for: url, prefersEmbeddedBrowser: preferBrowser) == .embeddedBrowser,
let tabManager,
tabManager.openBrowser(
inWorkspace: workspaceId,
Expand Down Expand Up @@ -14992,7 +14997,8 @@ struct VerticalTabsSidebar: View, Equatable {
opensInCmuxBrowser: Bool
) {
selectWorkspaceRow(workspace, index: index, modifiers: NSEvent.modifierFlags)
if opensInCmuxBrowser,
if BrowserExternalNavigationHandler()
.sidebarLinkDestination(for: url, prefersEmbeddedBrowser: opensInCmuxBrowser) == .embeddedBrowser,
tabManager.openBrowser(
inWorkspace: workspace.id,
url: url,
Expand Down
17 changes: 17 additions & 0 deletions Sources/Panels/BrowserExternalNavigationHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@ struct BrowserExternalNavigationHandler {
return policyCache.currentPolicy().matches(target)
}

/// Where a pull-request or port link chosen in the sidebar opens.
enum SidebarLinkDestination: Equatable {
case embeddedBrowser
case systemBrowser
}

/// The destination for a link the user chose in the sidebar.
/// `prefersEmbeddedBrowser` is the "open sidebar links in the cmux browser"
/// preference. A matching external-open rule outranks it: a site listed there
/// cannot work in the embedded web view at all. The rules are about web pages,
/// so other schemes keep following the preference.
func sidebarLinkDestination(for url: URL, prefersEmbeddedBrowser: Bool) -> SidebarLinkDestination {
guard prefersEmbeddedBrowser else { return .systemBrowser }
if Self.isWebNavigationURL(url), shouldOpenExternally(url) { return .systemBrowser }
return .embeddedBrowser
}

/// Returns whether a user-activated main-frame navigation should be external.
func shouldOpenExternally(
_ url: URL,
Expand Down
52 changes: 52 additions & 0 deletions cmuxTests/BrowserConfigTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5614,6 +5614,58 @@ final class BrowserLinkOpenSettingsTests: XCTestCase {
XCTAssertTrue(BrowserLinkOpenSettings.initialInterceptTerminalOpenCommandInCmuxBrowserValue(defaults: defaults))
}

// MARK: - Sidebar links

/// A pull-request or port link chosen in the sidebar follows the "open in the
/// cmux browser" preference when no rule names its site.
func testSidebarLinkWithNoMatchingRuleFollowsThePreference() throws {
defaults.set("billing.example.com", forKey: BrowserLinkOpenSettings.browserExternalOpenPatternsKey)
let handler = BrowserExternalNavigationHandler(defaults: defaults)
let url = try XCTUnwrap(URL(string: "https://github.com/manaflow-ai/cmux/pull/1"))
XCTAssertEqual(handler.sidebarLinkDestination(for: url, prefersEmbeddedBrowser: true), .embeddedBrowser)
XCTAssertEqual(handler.sidebarLinkDestination(for: url, prefersEmbeddedBrowser: false), .systemBrowser)
}

/// A site listed in the external-open rules cannot work in the embedded web
/// view, so the rule outranks the preference for sidebar links too.
func testSidebarLinkMatchingAnExternalRuleGoesToTheSystemBrowser() throws {
defaults.set("github.example.com", forKey: BrowserLinkOpenSettings.browserExternalOpenPatternsKey)
let handler = BrowserExternalNavigationHandler(defaults: defaults)
let pullRequest = try XCTUnwrap(URL(string: "https://github.example.com/org/repo/pull/42"))
XCTAssertEqual(
handler.sidebarLinkDestination(for: pullRequest, prefersEmbeddedBrowser: true),
.systemBrowser
)
XCTAssertEqual(
handler.sidebarLinkDestination(for: pullRequest, prefersEmbeddedBrowser: false),
.systemBrowser
)
}

/// The same holds for a port link, which is a plain http URL on a host.
func testSidebarPortLinkMatchingAnExternalRuleGoesToTheSystemBrowser() throws {
defaults.set(
"re:^https?://dashboard\\.example\\.com:[0-9]+/",
forKey: BrowserLinkOpenSettings.browserExternalOpenPatternsKey
)
let handler = BrowserExternalNavigationHandler(defaults: defaults)
let port = try XCTUnwrap(URL(string: "http://dashboard.example.com:8080/"))
let other = try XCTUnwrap(URL(string: "http://localhost:8080/"))
XCTAssertEqual(handler.sidebarLinkDestination(for: port, prefersEmbeddedBrowser: true), .systemBrowser)
XCTAssertEqual(handler.sidebarLinkDestination(for: other, prefersEmbeddedBrowser: true), .embeddedBrowser)
}

/// The rules are about web pages. A link with another scheme keeps following
/// the preference even when a rule's text happens to match it.
func testSidebarLinkRuleAppliesOnlyToWebSchemes() throws {
defaults.set("example.com", forKey: BrowserLinkOpenSettings.browserExternalOpenPatternsKey)
let handler = BrowserExternalNavigationHandler(defaults: defaults)
let web = try XCTUnwrap(URL(string: "https://example.com/pull/7"))
let notWeb = try XCTUnwrap(URL(string: "ssh://example.com/repo"))
XCTAssertEqual(handler.sidebarLinkDestination(for: web, prefersEmbeddedBrowser: true), .systemBrowser)
XCTAssertEqual(handler.sidebarLinkDestination(for: notWeb, prefersEmbeddedBrowser: true), .embeddedBrowser)
}

func testExternalOpenPatternsDefaultToEmpty() {
XCTAssertTrue(BrowserExternalURLPolicy(defaults: defaults).patterns.isEmpty)
}
Expand Down
Loading