Skip to content

Commit a20a307

Browse files
committed
Support save notification
1 parent e36406a commit a20a307

2 files changed

Lines changed: 82 additions & 20 deletions

File tree

Core/Sources/CopilotService/CopilotService.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ public final class CopilotSuggestionService: CopilotBaseService, CopilotSuggesti
261261
) async throws {
262262
let languageId = languageIdentifierFromFileURL(fileURL)
263263
let uri = "file://\(fileURL.path)"
264-
Logger.service.debug("Open \(uri)")
264+
// Logger.service.debug("Open \(uri)")
265265
try await server.sendNotification(
266266
.didOpenTextDocument(
267267
DidOpenTextDocumentParams(
@@ -279,7 +279,7 @@ public final class CopilotSuggestionService: CopilotBaseService, CopilotSuggesti
279279
public func notifyChangeTextDocument(fileURL: URL, content: String) async throws {
280280
let languageId = languageIdentifierFromFileURL(fileURL)
281281
let uri = "file://\(fileURL.path)"
282-
Logger.service.debug("Change \(uri)")
282+
// Logger.service.debug("Change \(uri)")
283283
try await server.sendNotification(
284284
.didOpenTextDocument(
285285
DidOpenTextDocumentParams(
@@ -312,3 +312,4 @@ extension InitializingServer: CopilotLSP {
312312
try await sendRequest(endpoint.request)
313313
}
314314
}
315+

Core/Sources/Service/Workspace.swift

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import CopilotModel
33
import CopilotService
44
import Environment
55
import Foundation
6+
import Logger
67
import Preferences
78
import SuggestionInjector
89
import XPCShared
9-
import Logger
1010

1111
@ServiceActor
1212
final class Filespace {
@@ -40,8 +40,15 @@ final class Filespace {
4040
Environment.now().timeIntervalSince(lastSuggestionUpdateTime) > 60 * 60 * 8
4141
}
4242

43-
fileprivate init(fileURL: URL) {
43+
let fileSaveWatcher: FileSaveWatcher
44+
45+
fileprivate init(fileURL: URL, onSave: @escaping (Filespace) -> Void) {
4446
self.fileURL = fileURL
47+
fileSaveWatcher = .init(fileURL: fileURL)
48+
fileSaveWatcher.changeHandler = { [weak self] in
49+
guard let self else { return }
50+
onSave(self)
51+
}
4552
}
4653

4754
func reset(resetSnapshot: Bool = true) {
@@ -51,7 +58,7 @@ final class Filespace {
5158
suggestionSourceSnapshot = .init(linesHash: -1, cursorPosition: .outOfScope)
5259
}
5360
}
54-
61+
5562
func refreshUpdateTime() {
5663
lastSuggestionUpdateTime = Environment.now()
5764
}
@@ -71,7 +78,7 @@ final class Workspace {
7178
onChange?()
7279
}
7380
}
74-
81+
7582
struct SuggestionFeatureDisabledError: Error, LocalizedError {
7683
var errorDescription: String? {
7784
"Suggestion feature is disabled for this project."
@@ -112,7 +119,7 @@ final class Workspace {
112119
}
113120
return _copilotSuggestionService
114121
}
115-
122+
116123
var isSuggestionFeatureEnabled: Bool {
117124
let isSuggestionDisabledGlobally = UserDefaults.shared
118125
.value(for: \.disableSuggestionFeatureGlobally)
@@ -127,7 +134,7 @@ final class Workspace {
127134

128135
private init(projectRootURL: URL) {
129136
self.projectRootURL = projectRootURL
130-
137+
131138
Task {
132139
userDefaultsObserver.onChange = { [weak self] in
133140
guard let self else { return }
@@ -140,7 +147,7 @@ final class Workspace {
140147
options: .new,
141148
context: nil
142149
)
143-
150+
144151
UserDefaults.shared.addObserver(
145152
userDefaultsObserver,
146153
forKeyPath: UserDefaultPreferenceKeys().disableSuggestionFeatureGlobally.key,
@@ -171,22 +178,31 @@ final class Workspace {
171178
return (workspace, filespace)
172179
}
173180
}
174-
181+
175182
let projectURL = try await Environment.fetchCurrentProjectRootURL(fileURL)
176183
let workspaceURL = projectURL ?? fileURL
177184
let workspace = workspaces[workspaceURL] ?? Workspace(projectRootURL: workspaceURL)
178-
let existedFilespace = workspace.filespaces[fileURL]
179-
let filespace = existedFilespace ?? .init(fileURL: fileURL)
180-
if workspace.filespaces[fileURL] == nil {
181-
workspace.filespaces[fileURL] = filespace
182-
}
185+
let filespace = workspace.createFilespaceIfNeeded(fileURL: fileURL)
183186
workspaces[workspaceURL] = workspace
187+
return (workspace, filespace)
188+
}
189+
190+
func createFilespaceIfNeeded(fileURL: URL) -> Filespace {
191+
let existedFilespace = filespaces[fileURL]
192+
let filespace = existedFilespace ?? .init(fileURL: fileURL, onSave: { [weak self]
193+
filespace in
194+
guard let self else { return }
195+
notifySaveFile(filespace: filespace)
196+
})
197+
if filespaces[fileURL] == nil {
198+
filespaces[fileURL] = filespace
199+
}
184200
if existedFilespace == nil {
185-
workspace.notifyOpenFile(filespace: filespace)
201+
notifyOpenFile(filespace: filespace)
186202
} else {
187203
filespace.refreshUpdateTime()
188204
}
189-
return (workspace, filespace)
205+
return filespace
190206
}
191207
}
192208

@@ -202,7 +218,8 @@ extension Workspace {
202218
}
203219
lastTriggerDate = Environment.now()
204220

205-
let filespace = filespaces[fileURL] ?? .init(fileURL: fileURL)
221+
let filespace = createFilespaceIfNeeded(fileURL: fileURL)
222+
206223
if filespaces[fileURL] == nil {
207224
filespaces[fileURL] = filespace
208225
}
@@ -306,7 +323,7 @@ extension Workspace {
306323

307324
return suggestion
308325
}
309-
326+
310327
func notifyOpenFile(filespace: Filespace) {
311328
Task {
312329
try await copilotSuggestionService?.notifyOpenTextDocument(
@@ -315,7 +332,7 @@ extension Workspace {
315332
)
316333
}
317334
}
318-
335+
319336
func notifyUpdateFile(filespace: Filespace, content: String) {
320337
Task {
321338
try await copilotSuggestionService?.notifyChangeTextDocument(
@@ -324,6 +341,12 @@ extension Workspace {
324341
)
325342
}
326343
}
344+
345+
func notifySaveFile(filespace: Filespace) {
346+
Task {
347+
try await copilotSuggestionService?.notifySaveTextDocument(fileURL: filespace.fileURL)
348+
}
349+
}
327350
}
328351

329352
extension Workspace {
@@ -345,3 +368,41 @@ extension Workspace {
345368
realtimeSuggestionRequests = []
346369
}
347370
}
371+
372+
final class FileSaveWatcher {
373+
let url: URL
374+
var fileHandle: FileHandle?
375+
var source: DispatchSourceFileSystemObject?
376+
var changeHandler: () -> Void = {}
377+
378+
init(fileURL: URL) {
379+
url = fileURL
380+
startup()
381+
}
382+
383+
deinit {
384+
source?.cancel()
385+
}
386+
387+
func startup() {
388+
if let source = source {
389+
source.cancel()
390+
}
391+
392+
fileHandle = try? FileHandle(forReadingFrom: url)
393+
if let fileHandle {
394+
source = DispatchSource.makeFileSystemObjectSource(
395+
fileDescriptor: fileHandle.fileDescriptor,
396+
eventMask: .link,
397+
queue: .main
398+
)
399+
400+
source?.setEventHandler { [weak self] in
401+
self?.changeHandler()
402+
self?.startup()
403+
}
404+
405+
source?.resume()
406+
}
407+
}
408+
}

0 commit comments

Comments
 (0)