Skip to content

Commit a2e7145

Browse files
author
zombie
committed
feat: support opening individual files and improve terminal launch reliability via CLI and optimized AppleScript
1 parent c5eb7e3 commit a2e7145

2 files changed

Lines changed: 37 additions & 18 deletions

File tree

BetterMenu/ExternalAppLauncher.swift

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,17 @@ struct ExternalAppLauncher {
2929
}
3030
}
3131

32-
/// 在指定的应用程序中打开目标目录
32+
/// 在指定的应用程序中打开目标路径(支持文件和目录)
3333
func openInApplication(atPath path: String, bundleIdentifiers: [String], appName: String) {
34-
let directoryUrl = URL(fileURLWithPath: path, isDirectory: true)
35-
guard FileManager.default.fileExists(atPath: directoryUrl.path) else {
34+
var isDirectory: ObjCBool = false
35+
guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) else {
3636
logger.error(
3737
"\(appName, privacy: .public) open path does not exist: \(path, privacy: .public)")
3838
return
3939
}
4040

41+
let targetUrl = URL(fileURLWithPath: path, isDirectory: isDirectory.boolValue)
42+
4143
var appUrl: URL? = nil
4244
var usedBundleId: String? = nil
4345
for bid in bundleIdentifiers {
@@ -49,7 +51,7 @@ struct ExternalAppLauncher {
4951
}
5052

5153
if appUrl != nil, let bundleId = usedBundleId {
52-
openDirectoryUsingWorkspace(directoryUrl, bundleIdentifier: bundleId)
54+
openDirectoryUsingWorkspace(targetUrl, bundleIdentifier: bundleId)
5355
} else {
5456
logger.warning("\(appName, privacy: .public) is not installed on this machine")
5557
missingApplicationHandler(appName)
@@ -112,8 +114,18 @@ struct ExternalAppLauncher {
112114
private func fallbackToNativeTerminal(directoryUrl: URL) {
113115
let success = runTerminalAppleScript(path: directoryUrl.path)
114116
if !success {
115-
logger.warning("Terminal AppleScript failed, fallback to workspace open")
116-
openDirectoryUsingWorkspace(directoryUrl, bundleIdentifier: "com.apple.Terminal")
117+
logger.warning("Terminal AppleScript failed, fallback to open command")
118+
// NSWorkspace.open([directoryUrl], withApplicationAt:) 在 Terminal 未运行时
119+
// 会创建两个窗口(启动默认窗口 + 打开目录窗口)。
120+
// 使用 `open -a Terminal /path` 命令则始终只创建一个窗口。
121+
let process = Process()
122+
process.executableURL = URL(fileURLWithPath: "/usr/bin/open")
123+
process.arguments = ["-a", "Terminal", directoryUrl.path]
124+
do {
125+
try process.run()
126+
} catch {
127+
logger.error("open command failed: \(error.localizedDescription, privacy: .public)")
128+
}
117129
}
118130
}
119131

@@ -139,18 +151,14 @@ struct ExternalAppLauncher {
139151
end tell
140152
"""
141153
} else {
154+
// Terminal 未运行时,直接用 do script(不带 in 参数),
155+
// 它会隐式启动 Terminal 并创建唯一的窗口来执行命令。
156+
// 不能先 activate,否则 Terminal 会先创建默认窗口,
157+
// do script 再创建一个窗口,导致出现两个终端。
142158
scriptText = """
143159
tell application "Terminal"
160+
do script "cd " & quoted form of "\(path)"
144161
activate
145-
repeat 50 times
146-
if (count of windows) > 0 then exit repeat
147-
delay 0.1
148-
end repeat
149-
if (count of windows) > 0 then
150-
do script "cd " & quoted form of "\(path)" in front window
151-
else
152-
do script "cd " & quoted form of "\(path)"
153-
end if
154162
end tell
155163
"""
156164
}

BetterMenuFinderSync/FinderSync.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,16 @@ final class FinderSync: FIFinderSync {
270270
}
271271

272272
private func executeDelegatedAction(_ action: FinderAction) {
273-
guard let directory = resolveFinderLocation(for: .openSelectionContainer) else {
274-
showError(message: "无法确定目标文件夹。")
273+
// 终端动作需要获取所在文件夹路径,编辑器动作则直接获取选中的文件/文件夹本身路径
274+
let targetUrl: URL?
275+
if action.id == "terminal" {
276+
targetUrl = resolveFinderLocation(for: .openSelectionContainer)
277+
} else {
278+
targetUrl = resolveFinderLocation(for: .openSelection)
279+
}
280+
281+
guard let resolvedUrl = targetUrl else {
282+
showError(message: "无法确定目标路径。")
275283
return
276284
}
277285

@@ -280,7 +288,7 @@ final class FinderSync: FIFinderSync {
280288
components.host = "run-action"
281289
components.queryItems = [
282290
URLQueryItem(name: "id", value: action.id),
283-
URLQueryItem(name: "path", value: directory.path),
291+
URLQueryItem(name: "path", value: resolvedUrl.path),
284292
]
285293

286294
guard let requestUrl = components.url else {
@@ -334,6 +342,8 @@ final class FinderSync: FIFinderSync {
334342
return (targetedUrl ?? selectedUrl)?.directoryForFinderAction() ?? desktopUrl
335343
case .openSelectionContainer:
336344
return (selectedUrl ?? targetedUrl)?.directoryForFinderAction() ?? desktopUrl
345+
case .openSelection:
346+
return selectedUrl ?? targetedUrl ?? desktopUrl
337347
}
338348
}
339349

@@ -379,6 +389,7 @@ private enum FinderLocationIntent {
379389
case createFileInContainer
380390
case copyCurrentPath
381391
case openSelectionContainer
392+
case openSelection
382393
}
383394

384395
extension URL {

0 commit comments

Comments
 (0)