Skip to content

Commit c059562

Browse files
committed
Adjust workspace creation to be more accurate
1 parent 0e55bf4 commit c059562

File tree

5 files changed

+92
-45
lines changed

5 files changed

+92
-45
lines changed

Core/Sources/ChatPlugins/TerminalChatPlugin.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,34 @@ public actor TerminalChatPlugin: ChatPlugin {
3535

3636
do {
3737
let fileURL = try await Environment.fetchCurrentFileURL()
38-
let projectURL = try await Environment.fetchCurrentProjectRootURL(fileURL)
38+
let projectURL = try await {
39+
if let url = try await Environment.fetchCurrentProjectRootURLFromXcode() {
40+
return url
41+
}
42+
return try await Environment.guessProjectRootURLForFile(fileURL)
43+
}()
3944

4045
await chatGPTService.mutateHistory { history in
41-
history.append(.init(
42-
role: .user,
43-
content: originalMessage,
44-
summary: "Run command: \(content)")
46+
history.append(
47+
.init(
48+
role: .user,
49+
content: originalMessage,
50+
summary: "Run command: \(content)"
51+
)
4552
)
4653
}
4754

4855
if isCancelled { throw CancellationError() }
4956

5057
let env = ProcessInfo.processInfo.environment
5158
let shell = env["SHELL"] ?? "/bin/bash"
52-
59+
5360
let output = terminal.streamCommand(
5461
shell,
5562
arguments: ["-i", "-l", "-c", content],
56-
currentDirectoryPath: projectURL?.path ?? fileURL.path,
63+
currentDirectoryPath: projectURL.path,
5764
environment: [
58-
"PROJECT_ROOT": projectURL?.path ?? fileURL.path,
65+
"PROJECT_ROOT": projectURL.path,
5966
"FILE_PATH": fileURL.path,
6067
]
6168
)
@@ -103,9 +110,10 @@ public actor TerminalChatPlugin: ChatPlugin {
103110
isCancelled = true
104111
await terminal.terminate()
105112
}
106-
113+
107114
public func stopResponding() async {
108115
isCancelled = true
109116
await terminal.terminate()
110117
}
111118
}
119+

Core/Sources/Environment/Environment.swift

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -43,43 +43,47 @@ public enum Environment {
4343
}
4444
}
4545

46-
public static var fetchCurrentProjectRootURL: (_ fileURL: URL?) async throws
47-
-> URL? = { fileURL in
48-
if let xcode = ActiveApplicationMonitor.activeXcode
49-
?? ActiveApplicationMonitor.latestXcode
50-
{
51-
let application = AXUIElementCreateApplication(xcode.processIdentifier)
52-
let focusedWindow = application.focusedWindow
53-
for child in focusedWindow?.children ?? [] {
54-
if child.description.starts(with: "/"), child.description.count > 1 {
55-
let path = child.description
56-
let trimmedNewLine = path.trimmingCharacters(in: .newlines)
57-
var url = URL(fileURLWithPath: trimmedNewLine)
58-
while !FileManager.default.fileIsDirectory(atPath: url.path) ||
59-
!url.pathExtension.isEmpty
60-
{
61-
url = url.deletingLastPathComponent()
62-
}
63-
return url
46+
public static var fetchCurrentProjectRootURLFromXcode: () async throws -> URL? = {
47+
if let xcode = ActiveApplicationMonitor.activeXcode
48+
?? ActiveApplicationMonitor.latestXcode
49+
{
50+
let application = AXUIElementCreateApplication(xcode.processIdentifier)
51+
let focusedWindow = application.focusedWindow
52+
for child in focusedWindow?.children ?? [] {
53+
if child.description.starts(with: "/"), child.description.count > 1 {
54+
let path = child.description
55+
let trimmedNewLine = path.trimmingCharacters(in: .newlines)
56+
var url = URL(fileURLWithPath: trimmedNewLine)
57+
while !FileManager.default.fileIsDirectory(atPath: url.path) ||
58+
!url.pathExtension.isEmpty
59+
{
60+
url = url.deletingLastPathComponent()
6461
}
62+
return url
6563
}
6664
}
65+
}
6766

68-
guard var currentURL = fileURL else { return nil }
69-
var firstDirectoryURL: URL?
70-
while currentURL.pathComponents.count > 1 {
71-
defer { currentURL.deleteLastPathComponent() }
72-
guard FileManager.default.fileIsDirectory(atPath: currentURL.path) else { continue }
73-
if firstDirectoryURL == nil { firstDirectoryURL = currentURL }
74-
let gitURL = currentURL.appendingPathComponent(".git")
75-
if FileManager.default.fileIsDirectory(atPath: gitURL.path) {
76-
return currentURL
77-
}
78-
}
67+
return nil
68+
}
7969

80-
return firstDirectoryURL ?? fileURL
70+
public static var guessProjectRootURLForFile: (_ fileURL: URL) async throws -> URL = {
71+
fileURL in
72+
var currentURL = fileURL
73+
var firstDirectoryURL: URL?
74+
while currentURL.pathComponents.count > 1 {
75+
defer { currentURL.deleteLastPathComponent() }
76+
guard FileManager.default.fileIsDirectory(atPath: currentURL.path) else { continue }
77+
if firstDirectoryURL == nil { firstDirectoryURL = currentURL }
78+
let gitURL = currentURL.appendingPathComponent(".git")
79+
if FileManager.default.fileIsDirectory(atPath: gitURL.path) {
80+
return currentURL
81+
}
8182
}
8283

84+
return firstDirectoryURL ?? fileURL
85+
}
86+
8387
public static var fetchCurrentFileURL: () async throws -> URL = {
8488
guard let xcode = ActiveApplicationMonitor.activeXcode
8589
?? ActiveApplicationMonitor.latestXcode

Core/Sources/Service/Workspace.swift

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ import SuggestionInjector
88
import SuggestionModel
99
import SuggestionService
1010
import UserDefaultsObserver
11+
import XcodeInspector
1112
import XPCShared
1213

14+
// MARK: - Filespace
15+
1316
@ServiceActor
1417
final class Filespace {
1518
struct Snapshot: Equatable {
@@ -66,6 +69,8 @@ final class Filespace {
6669
}
6770
}
6871

72+
// MARK: - Workspace
73+
6974
@ServiceActor
7075
final class Workspace {
7176
struct SuggestionFeatureDisabledError: Error, LocalizedError {
@@ -167,18 +172,36 @@ final class Workspace {
167172
return false
168173
}
169174

175+
/// This is the only way to create a workspace and a filespace.
170176
static func fetchOrCreateWorkspaceIfNeeded(fileURL: URL) async throws
171177
-> (workspace: Workspace, filespace: Filespace)
172178
{
173-
// never create duplicated filespaces
179+
// If we know which project is opened.
180+
if let currentProjectURL = try await Environment.fetchCurrentProjectRootURLFromXcode() {
181+
if let existed = workspaces[currentProjectURL] {
182+
let filespace = existed.createFilespaceIfNeeded(fileURL: fileURL)
183+
return (existed, filespace)
184+
}
185+
186+
let new = Workspace(projectRootURL: currentProjectURL)
187+
let filespace = new.createFilespaceIfNeeded(fileURL: fileURL)
188+
return (new, filespace)
189+
}
190+
191+
// If not, we try to reuse a filespace if found.
192+
//
193+
// Sometimes, we can't get the project root path from Xcode window, for example, when the
194+
// quick open window in displayed.
174195
for workspace in workspaces.values {
175196
if let filespace = workspace.filespaces[fileURL] {
176197
return (workspace, filespace)
177198
}
178199
}
179200

180-
let projectURL = try await Environment.fetchCurrentProjectRootURL(fileURL)
181-
let workspaceURL = projectURL ?? fileURL
201+
// If we can't find an existed one, we will try to guess it.
202+
// Most of the time we won't enter this branch, just incase.
203+
204+
let workspaceURL = try await Environment.guessProjectRootURLForFile(fileURL)
182205

183206
let workspace = {
184207
if let existed = workspaces[workspaceURL] {
@@ -199,7 +222,7 @@ final class Workspace {
199222
return (workspace, filespace)
200223
}
201224

202-
func createFilespaceIfNeeded(fileURL: URL) -> Filespace {
225+
private func createFilespaceIfNeeded(fileURL: URL) -> Filespace {
203226
let existedFilespace = filespaces[fileURL]
204227
let filespace = existedFilespace ?? .init(fileURL: fileURL, onSave: { [weak self]
205228
filespace in
@@ -218,6 +241,8 @@ final class Workspace {
218241
}
219242
}
220243

244+
// MARK: - Suggestion
245+
221246
extension Workspace {
222247
@discardableResult
223248
func generateSuggestions(
@@ -366,6 +391,8 @@ extension Workspace {
366391
}
367392
}
368393

394+
// MARK: - Cleanup
395+
369396
extension Workspace {
370397
func cleanUp(availableTabs: Set<String>) {
371398
for (fileURL, _) in filespaces {
@@ -393,6 +420,8 @@ extension Workspace {
393420
}
394421
}
395422

423+
// MARK: - Helper
424+
396425
final class FileSaveWatcher {
397426
let url: URL
398427
var fileHandle: FileHandle?

Core/Sources/SuggestionWidget/WidgetView.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,13 @@ struct WidgetContextMenu: View {
228228

229229
func updateProjectPath(fileURL: URL?) {
230230
Task {
231-
let projectURL = try? await Environment.fetchCurrentProjectRootURL(fileURL)
231+
let projectURL: URL? = await {
232+
if let url = try? await Environment.fetchCurrentProjectRootURLFromXcode() {
233+
return url
234+
}
235+
guard let fileURL else { return nil }
236+
return try? await Environment.guessProjectRootURLForFile(fileURL)
237+
}()
232238
if let projectURL {
233239
Task { @MainActor in
234240
self.fileURL = fileURL

Core/Tests/ServiceTests/Environment.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import XPCShared
1414

1515
Environment.now = { Date() }
1616

17-
Environment.fetchCurrentProjectRootURL = { _ in
17+
Environment.fetchCurrentProjectRootURLFromXcode = {
1818
URL(fileURLWithPath: "/path/to/project")
1919
}
2020

0 commit comments

Comments
 (0)