@@ -46,6 +46,7 @@ final class Workspace {
4646
4747 var filespaces = [ URL: Filespace] ( )
4848 var isRealtimeSuggestionEnabled = false
49+ var realtimeSuggestionFulfillmentTasks = Set < Task < Void , Error > > ( )
4950
5051 private lazy var service : CopilotSuggestionServiceType = Environment
5152 . createSuggestionService ( projectRootURL)
@@ -61,23 +62,104 @@ final class Workspace {
6162 ) -> Bool {
6263 guard isRealtimeSuggestionEnabled else { return false }
6364 guard let filespace = filespaces [ fileURL] else { return true }
64- if let range = filespace. currentSuggestionLineRange,
65- range. contains ( cursorPosition. line)
66- { return false }
6765 if lines. hashValue != filespace. suggestionSourceSnapshot. linesHash { return true }
6866 if cursorPosition != filespace. suggestionSourceSnapshot. cursorPosition { return true }
6967 return false
7068 }
7169
72- func getSuggestedCode (
70+ func getRealtimeSuggestedCode (
7371 forFileAt fileURL: URL ,
7472 content: String ,
7573 lines: [ String ] ,
7674 cursorPosition: CursorPosition ,
7775 tabSize: Int ,
7876 indentSize: Int ,
7977 usesTabsForIndentation: Bool
80- ) async throws -> UpdatedContent {
78+ ) -> UpdatedContent ? {
79+ cancelAllRealtimeSuggestionFulfillmentTasks ( )
80+ guard isRealtimeSuggestionEnabled else { return nil }
81+
82+ let filespace = filespaces [ fileURL] ?? . init( fileURL: fileURL)
83+ if filespaces [ fileURL] == nil {
84+ filespaces [ fileURL] = filespace
85+ }
86+
87+ let injector = SuggestionInjector ( )
88+ var extraInfo = SuggestionInjector . ExtraInfo ( )
89+ var lines = lines
90+ var cursorPosition = cursorPosition
91+
92+ injector. rejectCurrentSuggestions (
93+ from: & lines,
94+ cursorPosition: & cursorPosition,
95+ extraInfo: & extraInfo
96+ )
97+
98+ let snapshot = Filespace . Snapshot ( linesHash: lines. hashValue, cursorPosition: cursorPosition)
99+
100+ if snapshot != filespace. suggestionSourceSnapshot {
101+ let task = Task {
102+ let result = try await getSuggestedCode (
103+ forFileAt: fileURL,
104+ content: content,
105+ lines: lines,
106+ cursorPosition: cursorPosition,
107+ tabSize: tabSize,
108+ indentSize: indentSize,
109+ usesTabsForIndentation: usesTabsForIndentation,
110+ shouldCancelAllRealtimeSuggestionFulfillmentTasks: false
111+ )
112+ try Task . checkCancellation ( )
113+ if result != nil {
114+ try ? await Environment . triggerAction ( " Realtime Suggestions " )
115+ }
116+ }
117+
118+ realtimeSuggestionFulfillmentTasks. insert ( task)
119+
120+ return UpdatedContent (
121+ content: String ( lines. joined ( separator: " " ) ) ,
122+ newCursor: cursorPosition,
123+ modifications: extraInfo. modifications
124+ )
125+ }
126+
127+ if filespace. suggestions. isEmpty || snapshot != filespace. suggestionSourceSnapshot {
128+ return . init(
129+ content: content,
130+ newCursor: cursorPosition,
131+ modifications: extraInfo. modifications
132+ )
133+ }
134+
135+ injector. proposeSuggestion (
136+ intoContentWithoutSuggestion: & lines,
137+ completion: filespace. suggestions [ filespace. suggestionIndex] ,
138+ index: filespace. suggestionIndex,
139+ count: filespace. suggestions. count,
140+ extraInfo: & extraInfo
141+ )
142+
143+ return . init(
144+ content: String ( lines. joined ( separator: " " ) ) ,
145+ newCursor: cursorPosition,
146+ modifications: extraInfo. modifications
147+ )
148+ }
149+
150+ func getSuggestedCode(
151+ forFileAt fileURL: URL ,
152+ content: String ,
153+ lines: [ String ] ,
154+ cursorPosition: CursorPosition ,
155+ tabSize: Int ,
156+ indentSize: Int ,
157+ usesTabsForIndentation: Bool ,
158+ shouldCancelAllRealtimeSuggestionFulfillmentTasks: Bool = true
159+ ) async throws -> UpdatedContent ? {
160+ if shouldCancelAllRealtimeSuggestionFulfillmentTasks {
161+ cancelAllRealtimeSuggestionFulfillmentTasks ( )
162+ }
81163 lastTriggerDate = Environment . now ( )
82164 let injector = SuggestionInjector ( )
83165 var lines = lines
@@ -88,17 +170,18 @@ final class Workspace {
88170 filespaces [ fileURL] = filespace
89171 }
90172 var extraInfo = SuggestionInjector . ExtraInfo ( )
173+ let snapshot = Filespace . Snapshot (
174+ linesHash: lines. hashValue,
175+ cursorPosition: cursorPosition
176+ )
91177
92178 injector. rejectCurrentSuggestions (
93179 from: & lines,
94180 cursorPosition: & cursorPosition,
95181 extraInfo: & extraInfo
96182 )
97183
98- filespace. suggestionSourceSnapshot = . init(
99- linesHash: lines. hashValue,
100- cursorPosition: cursorPosition
101- )
184+ filespace. suggestionSourceSnapshot = snapshot
102185
103186 let completions = try await service. getCompletions (
104187 fileURL: fileURL,
@@ -108,6 +191,9 @@ final class Workspace {
108191 indentSize: indentSize,
109192 usesTabsForIndentation: usesTabsForIndentation
110193 )
194+
195+ guard filespace. suggestionSourceSnapshot == snapshot else { return nil }
196+
111197 if completions. isEmpty {
112198 return . init(
113199 content: content,
@@ -136,14 +222,15 @@ final class Workspace {
136222
137223 func getNextSuggestedCode(
138224 forFileAt fileURL: URL ,
139- content: String ,
225+ content _ : String ,
140226 lines: [ String ] ,
141227 cursorPosition: CursorPosition
142- ) -> UpdatedContent {
228+ ) -> UpdatedContent ? {
229+ cancelAllRealtimeSuggestionFulfillmentTasks ( )
143230 lastTriggerDate = Environment . now ( )
144231 guard let filespace = filespaces [ fileURL] ,
145232 filespace. suggestions. count > 1
146- else { return . init ( content : content , modifications : [ ] ) }
233+ else { return nil }
147234 var cursorPosition = cursorPosition
148235 filespace. suggestionIndex += 1
149236 if filespace. suggestionIndex >= filespace. suggestions. endIndex {
@@ -178,14 +265,15 @@ final class Workspace {
178265
179266 func getPreviousSuggestedCode(
180267 forFileAt fileURL: URL ,
181- content: String ,
268+ content _ : String ,
182269 lines: [ String ] ,
183270 cursorPosition: CursorPosition
184- ) -> UpdatedContent {
271+ ) -> UpdatedContent ? {
272+ cancelAllRealtimeSuggestionFulfillmentTasks ( )
185273 lastTriggerDate = Environment . now ( )
186274 guard let filespace = filespaces [ fileURL] ,
187275 filespace. suggestions. count > 1
188- else { return . init ( content : content , modifications : [ ] ) }
276+ else { return nil }
189277 var cursorPosition = cursorPosition
190278 filespace. suggestionIndex -= 1
191279 if filespace. suggestionIndex < 0 {
@@ -219,16 +307,17 @@ final class Workspace {
219307
220308 func getSuggestionAcceptedCode(
221309 forFileAt fileURL: URL ,
222- content: String ,
310+ content _ : String ,
223311 lines: [ String ] ,
224312 cursorPosition: CursorPosition
225- ) -> UpdatedContent {
313+ ) -> UpdatedContent ? {
314+ cancelAllRealtimeSuggestionFulfillmentTasks ( )
226315 lastTriggerDate = Environment . now ( )
227316 guard let filespace = filespaces [ fileURL] ,
228317 !filespace. suggestions. isEmpty,
229318 filespace. suggestionIndex >= 0 ,
230319 filespace. suggestionIndex < filespace. suggestions. endIndex
231- else { return . init ( content : content , modifications : [ ] ) }
320+ else { return nil }
232321
233322 var cursorPosition = cursorPosition
234323 var extraInfo = SuggestionInjector . ExtraInfo ( )
@@ -267,6 +356,7 @@ final class Workspace {
267356 lines: [ String ] ,
268357 cursorPosition: CursorPosition
269358 ) -> UpdatedContent {
359+ cancelAllRealtimeSuggestionFulfillmentTasks ( )
270360 lastTriggerDate = Environment . now ( )
271361 let injector = SuggestionInjector ( )
272362 var lines = lines
@@ -299,4 +389,11 @@ extension Workspace {
299389 }
300390 }
301391 }
392+
393+ func cancelAllRealtimeSuggestionFulfillmentTasks( ) {
394+ for task in realtimeSuggestionFulfillmentTasks {
395+ task. cancel ( )
396+ }
397+ realtimeSuggestionFulfillmentTasks = [ ]
398+ }
302399}
0 commit comments