Skip to content

Commit 87ae2d7

Browse files
committed
Merge branch 'feature/recover-opened-filespaces' into develop
2 parents 0e55bf4 + 61872ab commit 87ae2d7

7 files changed

Lines changed: 176 additions & 95 deletions

File tree

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
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import Foundation
2+
3+
final class FileSaveWatcher {
4+
let url: URL
5+
var fileHandle: FileHandle?
6+
var source: DispatchSourceFileSystemObject?
7+
var changeHandler: () -> Void = {}
8+
9+
init(fileURL: URL) {
10+
url = fileURL
11+
startup()
12+
}
13+
14+
deinit {
15+
source?.cancel()
16+
}
17+
18+
func startup() {
19+
if let source = source {
20+
source.cancel()
21+
}
22+
23+
fileHandle = try? FileHandle(forReadingFrom: url)
24+
if let fileHandle = fileHandle {
25+
source = DispatchSource.makeFileSystemObjectSource(
26+
fileDescriptor: fileHandle.fileDescriptor,
27+
eventMask: .link,
28+
queue: .main
29+
)
30+
31+
source?.setEventHandler { [weak self] in
32+
self?.changeHandler()
33+
self?.startup()
34+
}
35+
36+
source?.resume()
37+
}
38+
}
39+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Foundation
2+
import Preferences
3+
4+
@ServiceActor
5+
final class OpenedFileRecoverableStorage {
6+
let projectRootURL: URL
7+
let userDefault = UserDefaults.shared
8+
let key = "OpenedFileRecoverableStorage"
9+
10+
init(projectRootURL: URL) {
11+
self.projectRootURL = projectRootURL
12+
}
13+
14+
func openFile(fileURL: URL) {
15+
var dict = userDefault.dictionary(forKey: key) ?? [:]
16+
var openedFiles = Set(dict[projectRootURL.path] as? [String] ?? [])
17+
openedFiles.insert(fileURL.path)
18+
dict[projectRootURL.path] = Array(openedFiles)
19+
userDefault.set(dict, forKey: key)
20+
}
21+
22+
func closeFile(fileURL: URL) {
23+
var dict = userDefault.dictionary(forKey: key) ?? [:]
24+
var openedFiles = dict[projectRootURL.path] as? [String] ?? []
25+
openedFiles.removeAll(where: { $0 == fileURL.path })
26+
dict[projectRootURL.path] = openedFiles
27+
userDefault.set(dict, forKey: key)
28+
}
29+
30+
var openedFiles: [URL] {
31+
let dict = userDefault.dictionary(forKey: key) ?? [:]
32+
let openedFiles = dict[projectRootURL.path] as? [String] ?? []
33+
return openedFiles.map { URL(fileURLWithPath: $0) }
34+
}
35+
}
36+

0 commit comments

Comments
 (0)