Skip to content

Commit 2a686d3

Browse files
committed
Track workspaceURL
1 parent 85872fa commit 2a686d3

File tree

10 files changed

+141
-57
lines changed

10 files changed

+141
-57
lines changed

Core/Sources/PromptToCodeService/OpenAIPromptToCodeService.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public final class OpenAIPromptToCodeService: PromptToCodeServiceType {
4242
selectedContent: code,
4343
selectedLines: [],
4444
documentURL: source.documentURL,
45-
projectURL: source.projectRootURL,
45+
workspaceURL: source.projectRootURL,
46+
projectRootURL: source.projectRootURL,
4647
relativePath: "",
4748
language: source.language
4849
)

Core/Sources/Service/Service.swift

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import Dependencies
12
import Foundation
3+
import Workspace
4+
25
#if canImport(KeyBindingManager)
36
import EnhancedWorkspace
47
import KeyBindingManager
58
#endif
6-
import Workspace
79

810
@globalActor public enum ServiceActor {
911
public actor TheActor {}
@@ -15,15 +17,7 @@ public final class Service {
1517
public static let shared = Service()
1618

1719
@WorkspaceActor
18-
let workspacePool = {
19-
let it = WorkspacePool()
20-
it.registerPlugin { SuggestionServiceWorkspacePlugin(workspace: $0) }
21-
#if canImport(EnhancedWorkspace)
22-
it.registerPlugin { EnhancedWorkspacePlugin(workspace: $0) }
23-
#endif
24-
return it
25-
}()
26-
20+
let workspacePool: WorkspacePool
2721
@MainActor
2822
public let guiController = GraphicalUserInterfaceController()
2923
public let realtimeSuggestionController = RealtimeSuggestionController()
@@ -33,6 +27,8 @@ public final class Service {
3327
#endif
3428

3529
private init() {
30+
@Dependency(\.workspacePool) var workspacePool
31+
3632
scheduledCleaner = .init(workspacePool: workspacePool, guiController: guiController)
3733
#if canImport(KeyBindingManager)
3834
keyBindingManager = .init(
@@ -44,6 +40,13 @@ public final class Service {
4440
}
4541
)
4642
#endif
43+
44+
workspacePool.registerPlugin { SuggestionServiceWorkspacePlugin(workspace: $0) }
45+
#if canImport(EnhancedWorkspace)
46+
workspacePool.registerPlugin { EnhancedWorkspacePlugin(workspace: $0) }
47+
#endif
48+
49+
self.workspacePool = workspacePool
4750
}
4851

4952
@MainActor

Core/Sources/SuggestionWidget/WidgetView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ extension WidgetContextMenu {
216216
@ViewBuilder
217217
var enableSuggestionForProject: some View {
218218
WithViewStore(store) { _ in
219-
let projectPath = xcodeInspector.activeProjectURL.path
219+
let projectPath = xcodeInspector.activeProjectRootURL.path
220220
if disableSuggestionFeatureGlobally {
221221
let matchedPath = suggestionFeatureEnabledProjectList.first { path in
222222
projectPath.hasPrefix(path)

ExtensionService/AppDelegate+Menu.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ extension AppDelegate: NSMenuDelegate {
9595
case xcodeInspectorDebugMenuIdentifier:
9696
let inspector = XcodeInspector.shared
9797
menu.items.removeAll()
98-
menu.items.append(.text("Active Project: \(inspector.activeProjectURL)"))
98+
menu.items.append(.text("Active Project: \(inspector.activeProjectRootURL)"))
99+
menu.items.append(.text("Active Workspace: \(inspector.activeWorkspaceURL)"))
99100
menu.items.append(.text("Active Document: \(inspector.activeDocumentURL)"))
100101
for xcode in inspector.xcodes {
101102
let item = NSMenuItem(
@@ -107,7 +108,8 @@ extension AppDelegate: NSMenuDelegate {
107108
let xcodeMenu = NSMenu()
108109
item.submenu = xcodeMenu
109110
xcodeMenu.items.append(.text("Is Active: \(xcode.isActive)"))
110-
xcodeMenu.items.append(.text("Active Project: \(xcode.projectURL)"))
111+
xcodeMenu.items.append(.text("Active Project: \(xcode.projectRootURL)"))
112+
xcodeMenu.items.append(.text("Active Workspace: \(xcode.workspaceURL)"))
111113
xcodeMenu.items.append(.text("Active Document: \(xcode.documentURL)"))
112114

113115
for (key, workspace) in xcode.realtimeWorkspaces {

Tool/Sources/ActiveApplicationMonitor/ActiveApplicationMonitor.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public final class ActiveApplicationMonitor {
2020
private var continuations: [UUID: AsyncStream<NSRunningApplication?>.Continuation] = [:]
2121

2222
private init() {
23+
activeApplication = NSWorkspace.shared.runningApplications.first(where: \.isActive)
24+
2325
Task {
2426
let sequence = NSWorkspace.shared.notificationCenter
2527
.notifications(named: NSWorkspace.didActivateApplicationNotification)

Tool/Sources/Environment/Environment.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,7 @@ public enum Environment {
6363

6464
public static var fetchCurrentProjectRootURLFromXcode: () async throws -> URL? = {
6565
if var url = try await fetchCurrentWorkspaceURLFromXcode() {
66-
while !FileManager.default.fileIsDirectory(atPath: url.path) ||
67-
!url.pathExtension.isEmpty
68-
{
69-
url = url.deletingLastPathComponent()
70-
}
71-
return url
66+
return try await guessProjectRootURLForFile(url)
7267
}
7368

7469
return nil
@@ -78,17 +73,18 @@ public enum Environment {
7873
fileURL in
7974
var currentURL = fileURL
8075
var firstDirectoryURL: URL?
76+
var lastGitDirectoryURL: URL?
8177
while currentURL.pathComponents.count > 1 {
8278
defer { currentURL.deleteLastPathComponent() }
8379
guard FileManager.default.fileIsDirectory(atPath: currentURL.path) else { continue }
8480
if firstDirectoryURL == nil { firstDirectoryURL = currentURL }
8581
let gitURL = currentURL.appendingPathComponent(".git")
8682
if FileManager.default.fileIsDirectory(atPath: gitURL.path) {
87-
return currentURL
83+
lastGitDirectoryURL = currentURL
8884
}
8985
}
9086

91-
return firstDirectoryURL ?? fileURL
87+
return lastGitDirectoryURL ?? firstDirectoryURL ?? fileURL
9288
}
9389

9490
public static var fetchCurrentFileURL: () async throws -> URL = {

Tool/Sources/SuggestionModel/EditorInformation.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ public struct EditorInformation {
5555
public let selectedContent: String
5656
public let selectedLines: [String]
5757
public let documentURL: URL
58-
public let projectURL: URL
58+
public let workspaceURL: URL
59+
public let projectRootURL: URL
5960
public let relativePath: String
6061
public let language: CodeLanguage
6162

@@ -64,15 +65,17 @@ public struct EditorInformation {
6465
selectedContent: String,
6566
selectedLines: [String],
6667
documentURL: URL,
67-
projectURL: URL,
68+
workspaceURL: URL,
69+
projectRootURL: URL,
6870
relativePath: String,
6971
language: CodeLanguage
7072
) {
7173
self.editorContent = editorContent
7274
self.selectedContent = selectedContent
7375
self.selectedLines = selectedLines
7476
self.documentURL = documentURL
75-
self.projectURL = projectURL
77+
self.workspaceURL = workspaceURL
78+
self.projectRootURL = projectRootURL
7679
self.relativePath = relativePath
7780
self.language = language
7881
}

Tool/Sources/Workspace/WorkspacePool.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,35 @@
11
import Environment
22
import Foundation
3+
import Dependencies
4+
5+
public struct WorkspacePoolDependencyKey: DependencyKey {
6+
public static var liveValue: WorkspacePool = .init()
7+
}
8+
9+
public extension DependencyValues {
10+
var workspacePool: WorkspacePool {
11+
get { self[WorkspacePoolDependencyKey.self] }
12+
set { self[WorkspacePoolDependencyKey.self] = newValue }
13+
}
14+
}
315

416
@globalActor public enum WorkspaceActor {
517
public actor TheActor {}
618
public static let shared = TheActor()
719
}
820

921
public class WorkspacePool {
22+
public enum Error: Swift.Error, LocalizedError {
23+
case invalidWorkspaceURL(URL)
24+
25+
public var errorDescription: String? {
26+
switch self {
27+
case .invalidWorkspaceURL(let url):
28+
return "Invalid workspace URL: \(url)"
29+
}
30+
}
31+
}
32+
1033
public internal(set) var workspaces: [URL: Workspace] = [:]
1134
var plugins = [ObjectIdentifier: (Workspace) -> WorkspacePlugin]()
1235

@@ -45,6 +68,21 @@ public class WorkspacePool {
4568
}
4669
return nil
4770
}
71+
72+
@WorkspaceActor
73+
public func fetchOrCreateWorkspace(workspaceURL: URL) async throws -> Workspace {
74+
guard workspaceURL != URL(fileURLWithPath: "/") else {
75+
throw Error.invalidWorkspaceURL(workspaceURL)
76+
}
77+
78+
if let existed = workspaces[workspaceURL] {
79+
return existed
80+
}
81+
82+
let new = createNewWorkspace(workspaceURL: workspaceURL)
83+
workspaces[workspaceURL] = new
84+
return new
85+
}
4886

4987
@WorkspaceActor
5088
public func fetchOrCreateWorkspaceAndFilespace(fileURL: URL) async throws

Tool/Sources/XcodeInspector/XcodeInspector.swift

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@ public final class XcodeInspector: ObservableObject {
1717
@Published public internal(set) var activeXcode: XcodeAppInstanceInspector?
1818
@Published public internal(set) var latestActiveXcode: XcodeAppInstanceInspector?
1919
@Published public internal(set) var xcodes: [XcodeAppInstanceInspector] = []
20-
@Published public internal(set) var activeProjectURL = URL(fileURLWithPath: "/")
20+
@Published public internal(set) var activeProjectRootURL = URL(fileURLWithPath: "/")
2121
@Published public internal(set) var activeDocumentURL = URL(fileURLWithPath: "/")
22+
@Published public internal(set) var activeWorkspaceURL = URL(fileURLWithPath: "/")
2223
@Published public internal(set) var focusedWindow: XcodeWindowInspector?
2324
@Published public internal(set) var focusedEditor: SourceEditor?
2425
@Published public internal(set) var focusedElement: AXUIElement?
2526
@Published public internal(set) var completionPanel: AXUIElement?
2627

2728
public var focusedEditorContent: EditorInformation? {
2829
let editorContent = XcodeInspector.shared.focusedEditor?.content
29-
let documentURL = XcodeInspector.shared.activeDocumentURL
30-
let projectURL = XcodeInspector.shared.activeProjectURL
30+
let documentURL = XcodeInspector.shared.realtimeActiveDocumentURL
31+
let workspaceURL = XcodeInspector.shared.realtimeActiveWorkspaceURL
32+
let projectURL = XcodeInspector.shared.activeProjectRootURL
3133
let language = languageIdentifierFromFileURL(documentURL)
3234
let relativePath = documentURL.path
3335
.replacingOccurrences(of: projectURL.path, with: "")
@@ -42,7 +44,8 @@ public final class XcodeInspector: ObservableObject {
4244
selectedContent: selectedContent,
4345
selectedLines: selectedLines,
4446
documentURL: documentURL,
45-
projectURL: projectURL,
47+
workspaceURL: workspaceURL,
48+
projectRootURL: projectURL,
4649
relativePath: relativePath,
4750
language: language
4851
)
@@ -53,14 +56,19 @@ public final class XcodeInspector: ObservableObject {
5356
selectedContent: "",
5457
selectedLines: [],
5558
documentURL: documentURL,
56-
projectURL: projectURL,
59+
workspaceURL: workspaceURL,
60+
projectRootURL: projectURL,
5761
relativePath: relativePath,
5862
language: language
5963
)
6064
}
6165

6266
public var realtimeActiveDocumentURL: URL {
63-
latestActiveXcode?.realtimeDocumentURL ?? URL(fileURLWithPath: "/")
67+
latestActiveXcode?.realtimeDocumentURL ?? activeDocumentURL
68+
}
69+
70+
public var realtimeActiveWorkspaceURL: URL {
71+
latestActiveXcode?.realtimeWorkspaceURL ?? activeWorkspaceURL
6472
}
6573

6674
init() {
@@ -141,7 +149,8 @@ public final class XcodeInspector: ObservableObject {
141149
activeDocumentURL = xcode.documentURL
142150
focusedWindow = xcode.focusedWindow
143151
completionPanel = xcode.completionPanel
144-
activeProjectURL = xcode.projectURL
152+
activeProjectRootURL = xcode.projectRootURL
153+
activeWorkspaceURL = xcode.workspaceURL
145154
focusedWindow = xcode.focusedWindow
146155

147156
let setFocusedElement = { [weak self] in
@@ -178,9 +187,13 @@ public final class XcodeInspector: ObservableObject {
178187
xcode.$documentURL.sink { [weak self] url in
179188
self?.activeDocumentURL = url
180189
}.store(in: &activeXcodeCancellable)
190+
191+
xcode.$workspaceURL.sink { [weak self] url in
192+
self?.activeWorkspaceURL = url
193+
}.store(in: &activeXcodeCancellable)
181194

182-
xcode.$projectURL.sink { [weak self] url in
183-
self?.activeProjectURL = url
195+
xcode.$projectRootURL.sink { [weak self] url in
196+
self?.activeProjectRootURL = url
184197
}.store(in: &activeXcodeCancellable)
185198

186199
xcode.$focusedWindow.sink { [weak self] window in
@@ -207,7 +220,8 @@ public class AppInstanceInspector: ObservableObject {
207220
public final class XcodeAppInstanceInspector: AppInstanceInspector {
208221
@Published public var focusedWindow: XcodeWindowInspector?
209222
@Published public var documentURL: URL = .init(fileURLWithPath: "/")
210-
@Published public var projectURL: URL = .init(fileURLWithPath: "/")
223+
@Published public var workspaceURL: URL = .init(fileURLWithPath: "/")
224+
@Published public var projectRootURL: URL = .init(fileURLWithPath: "/")
211225
@Published public var workspaces = [WorkspaceIdentifier: Workspace]()
212226
public var realtimeWorkspaces: [WorkspaceIdentifier: WorkspaceInfo] {
213227
updateWorkspaceInfo()
@@ -226,6 +240,17 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
226240
return WorkspaceXcodeWindowInspector.extractDocumentURL(windowElement: window)
227241
?? URL(fileURLWithPath: "/")
228242
}
243+
244+
public var realtimeWorkspaceURL: URL {
245+
guard let window = appElement.focusedWindow,
246+
window.identifier == "Xcode.WorkspaceWindow"
247+
else {
248+
return URL(fileURLWithPath: "/")
249+
}
250+
251+
return WorkspaceXcodeWindowInspector.extractWorkspaceURL(windowElement: window)
252+
?? URL(fileURLWithPath: "/")
253+
}
229254

230255
var _version: String?
231256
public var version: String? {
@@ -284,17 +309,23 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
284309
focusedWindowObservations.removeAll()
285310

286311
documentURL = window.documentURL
287-
projectURL = window.projectURL
312+
workspaceURL = window.workspaceURL
313+
projectRootURL = window.projectRootURL
288314

289315
window.$documentURL
290316
.filter { $0 != .init(fileURLWithPath: "/") }
291317
.sink { [weak self] url in
292318
self?.documentURL = url
293319
}.store(in: &focusedWindowObservations)
294-
window.$projectURL
320+
window.$workspaceURL
321+
.filter { $0 != .init(fileURLWithPath: "/") }
322+
.sink { [weak self] url in
323+
self?.workspaceURL = url
324+
}.store(in: &focusedWindowObservations)
325+
window.$projectRootURL
295326
.filter { $0 != .init(fileURLWithPath: "/") }
296327
.sink { [weak self] url in
297-
self?.projectURL = url
328+
self?.projectRootURL = url
298329
}.store(in: &focusedWindowObservations)
299330
}
300331
} else {

0 commit comments

Comments
 (0)