mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-25 00:21:46 +00:00
macos: normalize command paths as file URLs
#13319 #13748 Normalize command-line file arguments as file URLs internally while keeping the AppKit and FileManager string boundaries unchanged. This handles relative paths, URL-sensitive characters, and trailing directory separators consistently when matching duplicate open-file events.
This commit is contained in:
@@ -4,14 +4,18 @@ import Foundation
|
||||
/// `-e`. Each matching event is consumed once so later requests to open the same
|
||||
/// file are handled normally.
|
||||
final class CommandLineOpenFileFilter {
|
||||
private let workingDirectory: String
|
||||
private var filesToIgnore: Set<String>
|
||||
private let workingDirectory: URL
|
||||
private var filesToIgnore: Set<URL>
|
||||
|
||||
init(
|
||||
arguments: [String],
|
||||
workingDirectory: String,
|
||||
fileExists: (String) -> Bool
|
||||
) {
|
||||
let workingDirectory = URL(
|
||||
filePath: workingDirectory,
|
||||
directoryHint: .isDirectory
|
||||
).absoluteURL.standardizedFileURL
|
||||
self.workingDirectory = workingDirectory
|
||||
|
||||
guard let commandIndex = arguments.firstIndex(of: "-e") else {
|
||||
@@ -25,32 +29,31 @@ final class CommandLineOpenFileFilter {
|
||||
self.filesToIgnore = Set(arguments[arguments.index(after: commandIndex)...]
|
||||
.compactMap { argument in
|
||||
// Command arguments can be relative, while AppKit normally
|
||||
// reports absolute paths for the corresponding open event.
|
||||
let path = Self.absolutePath(argument, relativeTo: workingDirectory)
|
||||
// reports absolute paths for the corresponding open event. Use
|
||||
// file URLs internally so both forms have the same identity.
|
||||
let url = Self.fileURL(argument, relativeTo: workingDirectory)
|
||||
|
||||
// Ignore only paths that exist during launch. A non-path
|
||||
// argument cannot produce the duplicate event and retaining it
|
||||
// could suppress a legitimate open if that path appears later.
|
||||
return fileExists(path) ? path : nil
|
||||
return fileExists(url.path) ? url : nil
|
||||
})
|
||||
}
|
||||
|
||||
func shouldIgnore(_ filename: String) -> Bool {
|
||||
let path = Self.absolutePath(filename, relativeTo: workingDirectory)
|
||||
let url = Self.fileURL(filename, relativeTo: workingDirectory)
|
||||
|
||||
// Consume each match once. Later requests to open the same file may
|
||||
// come from Finder, the Dock, or another invocation and must proceed.
|
||||
return filesToIgnore.remove(path) != nil
|
||||
return filesToIgnore.remove(url) != nil
|
||||
}
|
||||
|
||||
private static func absolutePath(_ path: String, relativeTo workingDirectory: String) -> String {
|
||||
private static func fileURL(_ path: String, relativeTo workingDirectory: URL) -> URL {
|
||||
let expanded = (path as NSString).expandingTildeInPath
|
||||
let absolute = if (expanded as NSString).isAbsolutePath {
|
||||
expanded
|
||||
} else {
|
||||
(workingDirectory as NSString).appendingPathComponent(expanded)
|
||||
}
|
||||
|
||||
return (absolute as NSString).standardizingPath
|
||||
return URL(
|
||||
filePath: expanded,
|
||||
directoryHint: .notDirectory,
|
||||
relativeTo: workingDirectory
|
||||
).absoluteURL.standardizedFileURL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,4 +61,25 @@ struct CommandLineOpenFileFilterTests {
|
||||
#expect(!filter.shouldIgnore("/tmp/finder-file.txt"))
|
||||
#expect(filter.shouldIgnore("/tmp/command-file.txt"))
|
||||
}
|
||||
|
||||
@Test func normalizesFileURLs() {
|
||||
let existing: Set<String> = [
|
||||
"/tmp/project/file #100%.txt",
|
||||
"/tmp/project/directory",
|
||||
]
|
||||
let filter = CommandLineOpenFileFilter(
|
||||
arguments: [
|
||||
"ghostty",
|
||||
"-e",
|
||||
"command",
|
||||
"./file #100%.txt",
|
||||
"./directory/",
|
||||
],
|
||||
workingDirectory: "/tmp/project",
|
||||
fileExists: { existing.contains($0) }
|
||||
)
|
||||
|
||||
#expect(filter.shouldIgnore("/tmp/project/file #100%.txt"))
|
||||
#expect(filter.shouldIgnore("/tmp/project/directory"))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user