diff --git a/Resources/Info.plist b/Resources/Info.plist
index f1beb4f9..708488ce 100644
--- a/Resources/Info.plist
+++ b/Resources/Info.plist
@@ -12,6 +12,21 @@
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
+ CFBundleDocumentTypes
+
+
+ CFBundleTypeName
+ Folder
+ CFBundleTypeRole
+ Viewer
+ LSHandlerRank
+ Alternate
+ LSItemContentTypes
+
+ public.folder
+
+
+
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift
index fbf3fad3..674b9d5c 100644
--- a/Sources/AppDelegate.swift
+++ b/Sources/AppDelegate.swift
@@ -368,13 +368,24 @@ enum FinderServicePathResolver {
return canonical
}
+ private static func resolvedDirectoryURL(from url: URL) -> URL {
+ let standardized = url.standardizedFileURL
+ if standardized.hasDirectoryPath {
+ return standardized
+ }
+ if let resourceValues = try? standardized.resourceValues(forKeys: [.isDirectoryKey]),
+ resourceValues.isDirectory == true {
+ return standardized
+ }
+ return standardized.deletingLastPathComponent()
+ }
+
static func orderedUniqueDirectories(from pathURLs: [URL]) -> [String] {
var seen: Set = []
var directories: [String] = []
for url in pathURLs {
- let standardized = url.standardizedFileURL
- let directoryURL = standardized.hasDirectoryPath ? standardized : standardized.deletingLastPathComponent()
+ let directoryURL = resolvedDirectoryURL(from: url)
let path = canonicalDirectoryPath(directoryURL.path(percentEncoded: false))
guard !path.isEmpty else { continue }
if seen.insert(path).inserted {
@@ -2154,6 +2165,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
Self.shared = self
}
+ func application(_ application: NSApplication, open urls: [URL]) {
+ let directories = externalOpenDirectories(from: urls)
+ guard !directories.isEmpty else { return }
+
+ prepareForExplicitOpenIntentAtStartup()
+ for directory in directories {
+ openWorkspaceForExternalDirectory(
+ workingDirectory: directory,
+ debugSource: "application.openURLs"
+ )
+ }
+ }
+
func applicationDidFinishLaunching(_ notification: Notification) {
let env = ProcessInfo.processInfo.environment
let isRunningUnderXCTest = isRunningUnderXCTest(env)
@@ -5077,11 +5101,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
target: ServiceOpenTarget,
error: AutoreleasingUnsafeMutablePointer
) {
- didHandleExplicitOpenIntentAtStartup = true
- if !didAttemptStartupSessionRestore {
- startupSessionSnapshot = nil
- didAttemptStartupSessionRestore = true
- }
+ prepareForExplicitOpenIntentAtStartup()
let pathURLs = servicePathURLs(from: pasteboard)
guard !pathURLs.isEmpty else {
@@ -5089,7 +5109,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
return
}
- let directories = FinderServicePathResolver.orderedUniqueDirectories(from: pathURLs)
+ let directories = externalOpenDirectories(from: pathURLs)
guard !directories.isEmpty else {
error.pointee = Self.serviceErrorNoPath
return
@@ -5134,10 +5154,32 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
}
private func openWorkspaceFromService(workingDirectory: String) {
+ openWorkspaceForExternalDirectory(
+ workingDirectory: workingDirectory,
+ debugSource: "service.openTab"
+ )
+ }
+
+ private func prepareForExplicitOpenIntentAtStartup() {
+ didHandleExplicitOpenIntentAtStartup = true
+ if !didAttemptStartupSessionRestore {
+ startupSessionSnapshot = nil
+ didAttemptStartupSessionRestore = true
+ }
+ }
+
+ private func externalOpenDirectories(from urls: [URL]) -> [String] {
+ FinderServicePathResolver.orderedUniqueDirectories(from: urls.filter { $0.isFileURL })
+ }
+
+ private func openWorkspaceForExternalDirectory(
+ workingDirectory: String,
+ debugSource: String
+ ) {
if addWorkspaceInPreferredMainWindow(
workingDirectory: workingDirectory,
shouldBringToFront: true,
- debugSource: "service.openTab"
+ debugSource: debugSource
) != nil {
return
}
diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift
index 5c0ad1af..c787a69a 100644
--- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift
+++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift
@@ -1197,6 +1197,55 @@ final class AppDelegateWindowContextRoutingTests: XCTestCase {
XCTAssertEqual(managerB.tabs.count, originalTabCountB + 1)
XCTAssertTrue(managerB.tabs.contains(where: { $0.id == createdWorkspaceId }))
}
+
+ func testApplicationOpenURLsAddsWorkspaceForDroppedFolderURL() throws {
+ _ = NSApplication.shared
+ let app = AppDelegate()
+
+ let windowId = UUID()
+ let window = makeMainWindow(id: windowId)
+ defer { window.orderOut(nil) }
+
+ let manager = TabManager()
+ app.registerMainWindow(
+ window,
+ windowId: windowId,
+ tabManager: manager,
+ sidebarState: SidebarState(),
+ sidebarSelectionState: SidebarSelectionState()
+ )
+
+ window.makeKeyAndOrderFront(nil)
+ _ = app.synchronizeActiveMainWindowContext(preferredWindow: window)
+
+ let defaults = UserDefaults.standard
+ let previousWelcomeShown = defaults.object(forKey: WelcomeSettings.shownKey)
+ defaults.set(true, forKey: WelcomeSettings.shownKey)
+ defer {
+ if let previousWelcomeShown {
+ defaults.set(previousWelcomeShown, forKey: WelcomeSettings.shownKey)
+ } else {
+ defaults.removeObject(forKey: WelcomeSettings.shownKey)
+ }
+ }
+
+ let rootDirectory = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
+ .appendingPathComponent(UUID().uuidString, isDirectory: true)
+ let droppedDirectory = rootDirectory.appendingPathComponent("project", isDirectory: true)
+ try FileManager.default.createDirectory(at: droppedDirectory, withIntermediateDirectories: true)
+ defer { try? FileManager.default.removeItem(at: rootDirectory) }
+
+ let existingWorkspaceIds = Set(manager.tabs.map(\.id))
+
+ app.application(
+ NSApplication.shared,
+ open: [URL(fileURLWithPath: droppedDirectory.path)]
+ )
+
+ let createdWorkspace = manager.tabs.first { !existingWorkspaceIds.contains($0.id) }
+ XCTAssertNotNil(createdWorkspace)
+ XCTAssertEqual(createdWorkspace?.currentDirectory, droppedDirectory.path)
+ }
}
@MainActor