@@ -14,6 +14,9 @@ import CopilotForXcodeKit
1414import BuiltinExtension
1515import GitHubCopilotService
1616
17+ import Combine
18+
19+
1720protocol BenchmarkManager {
1821
1922}
@@ -43,9 +46,10 @@ class RealtimeSuggestionControllerBenchmarkManager: BenchmarkManager {
4346
4447 func getCodeSuggestions( at benchmarkDirectory: BenchmarkDirectory ) async throws {
4548 let taskPaths : [ URL ] = getTaskFolders ( in: benchmarkDirectory. url)
46- for taskPath in taskPaths. prefix ( 1 ) {
49+ for (index , taskPath) in taskPaths. prefix ( 1 ) . enumerated ( ) {
4750 if let suggestion = await getCodeSuggestionFromService ( at: taskPath, from: benchmarkDirectory. url) {
4851 await applyCodeSuggestion ( suggestion: suggestion. suggestion, at: suggestion. fileURL)
52+ await storeContentInOutputDirectory ( suggestion, for: index+ 1 )
4953 }
5054
5155 }
@@ -135,6 +139,40 @@ class RealtimeSuggestionControllerBenchmarkManager: BenchmarkManager {
135139 }
136140 }
137141
142+ func storeContentInOutputDirectory( _ suggestion: SuggestionResponse , for taskNumber: Int ) async {
143+ guard let outputDirPath = await benchmarkSettingsRepository. outputDirectory. firstValue ( ) else {
144+ print ( " Could not retrieve output directory. " )
145+ return
146+ }
147+ let fileManager = FileManager . default
148+
149+ let resolvedOutputDirPath = outputDirPath. replacingOccurrences ( of: " ~ " , with: fileManager. homeDirectoryForCurrentUser. path)
150+ let outputDir = URL ( fileURLWithPath: resolvedOutputDirPath)
151+
152+ var isDirectory : ObjCBool = false
153+ if !fileManager. fileExists ( atPath: outputDir. path, isDirectory: & isDirectory) || !isDirectory. boolValue {
154+ do {
155+ try fileManager. createDirectory ( at: outputDir, withIntermediateDirectories: true , attributes: nil )
156+ } catch {
157+ print ( " Failed to create output directory: " , error)
158+ return
159+ }
160+ }
161+
162+ let timestamp = ISO8601DateFormatter ( ) . string ( from: Date ( ) )
163+ let reformattedTimestamp = timestamp. replacingOccurrences ( of: " : " , with: " - " )
164+ let outputFileURL = outputDir. appendingPathComponent ( " Task- \( taskNumber) - \( reformattedTimestamp) .json " )
165+
166+ do {
167+ let dto = suggestion. toStoredDTO ( timestamp: timestamp)
168+ let data = try JSONEncoder ( ) . encode ( dto)
169+ try data. write ( to: outputFileURL)
170+ print ( " Stored suggestion at: \( outputFileURL. path) " )
171+ } catch {
172+ print ( " Failed to write suggestion file: " , error)
173+ }
174+ }
175+
138176 func readMetadata( at taskFolder: URL ) -> MetadataDTO ? {
139177 let metadataURL = taskFolder. appendingPathComponent ( " metadata.json " )
140178 do {
@@ -272,3 +310,62 @@ struct SuggestionResponse {
272310 let suggestion : SuggestionBasic . CodeSuggestion
273311 let fileURL : URL
274312}
313+
314+
315+ extension AnyPublisher where Failure == Never {
316+ /// Awaits the first emitted value of the publisher (only for Failure == Never)
317+ func firstValue( ) async -> Output ? {
318+ await withCheckedContinuation { continuation in
319+ var cancellable : AnyCancellable ?
320+ cancellable = self . first ( ) . sink { value in
321+ continuation. resume ( returning: value)
322+ cancellable? . cancel ( )
323+ }
324+ }
325+ }
326+ }
327+
328+
329+ struct StoredSuggestionDTO : Codable {
330+ let fileURL : String
331+ let id : String
332+ let suggestionText : String
333+ let position : CursorPositionDTO
334+ let range : CursorRangeDTO
335+ let createdAt : String
336+
337+ struct CursorPositionDTO : Codable {
338+ let line : Int
339+ let character : Int
340+ }
341+
342+ struct CursorRangeDTO : Codable {
343+ let start : CursorPositionDTO
344+ let end : CursorPositionDTO
345+ }
346+ }
347+
348+ extension SuggestionResponse {
349+ func toStoredDTO( timestamp: String ) -> StoredSuggestionDTO {
350+ StoredSuggestionDTO (
351+ fileURL: fileURL. path,
352+ id: suggestion. id,
353+ suggestionText: suggestion. text,
354+ position: . init(
355+ line: suggestion. position. line,
356+ character: suggestion. position. character
357+ ) ,
358+ range: . init(
359+ start: . init(
360+ line: suggestion. range. start. line,
361+ character: suggestion. range. start. character
362+ ) ,
363+ end: . init(
364+ line: suggestion. range. end. line,
365+ character: suggestion. range. end. character
366+ )
367+ ) ,
368+ createdAt: timestamp
369+ )
370+ }
371+ }
0 commit comments