@@ -17,7 +17,6 @@ final class Filespace {
1717 }
1818
1919 var suggestionIndex : Int = 0
20- var currentSuggestionLineRange : ClosedRange < Int > ?
2120 var suggestionSourceSnapshot : Snapshot = . init( linesHash: - 1 , cursorPosition: . outOfScope)
2221 var presentingSuggestion : CopilotCompletion ? {
2322 guard suggestions. endIndex > suggestionIndex, suggestionIndex >= 0 else { return nil }
@@ -36,7 +35,6 @@ final class Filespace {
3635 func reset( ) {
3736 suggestions = [ ]
3837 suggestionIndex = 0
39- currentSuggestionLineRange = nil
4038 }
4139}
4240
@@ -48,7 +46,7 @@ final class Workspace {
4846 Environment . now ( ) . timeIntervalSince ( lastTriggerDate) > 60 * 60 * 8
4947 }
5048
51- var filespaces = [ URL: Filespace] ( )
49+ private ( set ) var filespaces = [ URL: Filespace] ( )
5250 var isRealtimeSuggestionEnabled : Bool {
5351 UserDefaults . shared. bool ( forKey: SettingsKey . realtimeSuggestionToggle)
5452 }
@@ -74,6 +72,125 @@ final class Workspace {
7472 return false
7573 }
7674
75+ static func fetchOrCreateWorkspaceIfNeeded( fileURL: URL ) async throws -> Workspace {
76+ let projectURL = try await Environment . fetchCurrentProjectRootURL ( fileURL)
77+ let workspaceURL = projectURL ?? fileURL
78+ let workspace = workspaces [ workspaceURL] ?? Workspace ( projectRootURL: workspaceURL)
79+ workspaces [ workspaceURL] = workspace
80+ return workspace
81+ }
82+ }
83+
84+ extension Workspace {
85+ @discardableResult
86+ func generateSuggestions(
87+ forFileAt fileURL: URL ,
88+ content: String ,
89+ lines: [ String ] ,
90+ cursorPosition: CursorPosition ,
91+ tabSize: Int ,
92+ indentSize: Int ,
93+ usesTabsForIndentation: Bool ,
94+ shouldcancelInFlightRealtimeSuggestionRequests: Bool = true
95+ ) async throws -> [ CopilotCompletion ] {
96+ if shouldcancelInFlightRealtimeSuggestionRequests {
97+ cancelInFlightRealtimeSuggestionRequests ( )
98+ }
99+ lastTriggerDate = Environment . now ( )
100+
101+ let filespace = filespaces [ fileURL] ?? . init( fileURL: fileURL)
102+ if filespaces [ fileURL] == nil {
103+ filespaces [ fileURL] = filespace
104+ }
105+
106+ let snapshot = Filespace . Snapshot (
107+ linesHash: lines. hashValue,
108+ cursorPosition: cursorPosition
109+ )
110+
111+ filespace. suggestionSourceSnapshot = snapshot
112+
113+ let completions = try await service. getCompletions (
114+ fileURL: fileURL,
115+ content: lines. joined ( separator: " " ) ,
116+ cursorPosition: cursorPosition,
117+ tabSize: tabSize,
118+ indentSize: indentSize,
119+ usesTabsForIndentation: usesTabsForIndentation,
120+ ignoreSpaceOnlySuggestions: true
121+ )
122+
123+ filespace. suggestions = completions
124+
125+ return completions
126+ }
127+
128+ func selectNextSuggestion(
129+ forFileAt fileURL: URL ,
130+ content _: String ,
131+ lines: [ String ]
132+ ) {
133+ cancelInFlightRealtimeSuggestionRequests ( )
134+ lastTriggerDate = Environment . now ( )
135+ guard let filespace = filespaces [ fileURL] ,
136+ filespace. suggestions. count > 1
137+ else { return }
138+ filespace. suggestionIndex += 1
139+ if filespace. suggestionIndex >= filespace. suggestions. endIndex {
140+ filespace. suggestionIndex = 0
141+ }
142+ }
143+
144+ func selectPreviousSuggestion(
145+ forFileAt fileURL: URL ,
146+ content _: String ,
147+ lines: [ String ]
148+ ) {
149+ cancelInFlightRealtimeSuggestionRequests ( )
150+ lastTriggerDate = Environment . now ( )
151+ guard let filespace = filespaces [ fileURL] ,
152+ filespace. suggestions. count > 1
153+ else { return }
154+ filespace. suggestionIndex -= 1
155+ if filespace. suggestionIndex < 0 {
156+ filespace. suggestionIndex = filespace. suggestions. endIndex - 1
157+ }
158+ }
159+
160+ func rejectSuggestion( forFileAt fileURL: URL ) {
161+ cancelInFlightRealtimeSuggestionRequests ( )
162+ lastTriggerDate = Environment . now ( )
163+ Task {
164+ await service. notifyRejected ( filespaces [ fileURL] ? . suggestions ?? [ ] )
165+ }
166+ filespaces [ fileURL] ? . reset ( )
167+ }
168+
169+ func acceptSuggestion( forFileAt fileURL: URL ) -> CopilotCompletion ? {
170+ cancelInFlightRealtimeSuggestionRequests ( )
171+ lastTriggerDate = Environment . now ( )
172+ guard let filespace = filespaces [ fileURL] ,
173+ !filespace. suggestions. isEmpty,
174+ filespace. suggestionIndex >= 0 ,
175+ filespace. suggestionIndex < filespace. suggestions. endIndex
176+ else { return nil }
177+
178+ var allSuggestions = filespace. suggestions
179+ let suggestion = allSuggestions. remove ( at: filespace. suggestionIndex)
180+
181+ Task {
182+ await service. notifyAccepted ( suggestion)
183+ await service. notifyRejected ( allSuggestions)
184+ }
185+
186+ filespaces [ fileURL] ? . reset ( )
187+
188+ return suggestion
189+ }
190+ }
191+
192+ @available ( * , deprecated, message: " These should be replaced. " )
193+ extension Workspace {
77194 func getRealtimeSuggestedCode(
78195 forFileAt fileURL: URL ,
79196 content: String ,
@@ -222,8 +339,6 @@ final class Workspace {
222339 extraInfo: & extraInfo
223340 )
224341
225- filespace. currentSuggestionLineRange = extraInfo. suggestionRange
226-
227342 return . init(
228343 content: String ( lines. joined ( separator: " " ) ) ,
229344 newCursor: cursorPosition,
@@ -265,8 +380,6 @@ final class Workspace {
265380 extraInfo: & extraInfo
266381 )
267382
268- filespace. currentSuggestionLineRange = extraInfo. suggestionRange
269-
270383 return . init(
271384 content: String ( lines. joined ( separator: " " ) ) ,
272385 newCursor: cursorPosition,
@@ -307,8 +420,6 @@ final class Workspace {
307420 extraInfo: & extraInfo
308421 )
309422
310- filespace. currentSuggestionLineRange = extraInfo. suggestionRange
311-
312423 return . init(
313424 content: String ( lines. joined ( separator: " " ) ) ,
314425 newCursor: cursorPosition,
0 commit comments