Skip to content

Commit ccfa376

Browse files
committed
Fix GitHub Copilot suggestion generation
1 parent 22c9624 commit ccfa376

File tree

2 files changed

+65
-51
lines changed

2 files changed

+65
-51
lines changed

Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest.swift

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,12 @@ enum GitHubCopilotRequest {
264264
}
265265
}
266266

267-
var doc: GitHubCopilotDoc
267+
var doc: Input
268268

269269
struct Input: Codable {
270270
var textDocument: _TextDocument; struct _TextDocument: Codable {
271271
var uri: String
272+
var version: Int
272273
}
273274

274275
var position: Position
@@ -284,12 +285,7 @@ enum GitHubCopilotRequest {
284285
}
285286

286287
var request: ClientRequest {
287-
let data = (try? JSONEncoder().encode(Input(
288-
textDocument: .init(uri: doc.uri),
289-
position: doc.position,
290-
formattingOptions: .init(tabSize: doc.tabSize, insertSpaces: doc.insertSpaces),
291-
context: .init(triggerKind: .invoked)
292-
))) ?? Data()
288+
let data = (try? JSONEncoder().encode(doc)) ?? Data()
293289
let dict = (try? JSONDecoder().decode(JSONValue.self, from: data)) ?? .hash([:])
294290
return .custom("textDocument/inlineCompletion", dict)
295291
}

Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift

Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public protocol GitHubCopilotSuggestionServiceType {
2828
func notifyAccepted(_ completion: CodeSuggestion) async
2929
func notifyRejected(_ completions: [CodeSuggestion]) async
3030
func notifyOpenTextDocument(fileURL: URL, content: String) async throws
31-
func notifyChangeTextDocument(fileURL: URL, content: String) async throws
31+
func notifyChangeTextDocument(fileURL: URL, content: String, version: Int) async throws
3232
func notifyCloseTextDocument(fileURL: URL) async throws
3333
func notifySaveTextDocument(fileURL: URL) async throws
3434
func cancelRequest() async
@@ -364,54 +364,21 @@ public final class GitHubCopilotService: GitHubCopilotBaseService,
364364
indentSize: Int,
365365
usesTabsForIndentation: Bool
366366
) async throws -> [CodeSuggestion] {
367-
let languageId = languageIdentifierFromFileURL(fileURL)
368-
369-
let relativePath = {
370-
let filePath = fileURL.path
371-
let rootPath = projectRootURL.path
372-
if let range = filePath.range(of: rootPath),
373-
range.lowerBound == filePath.startIndex
374-
{
375-
let relativePath = filePath.replacingCharacters(
376-
in: filePath.startIndex..<range.upperBound,
377-
with: ""
378-
)
379-
return relativePath
380-
}
381-
return filePath
382-
}()
383-
384367
ongoingTasks.forEach { $0.cancel() }
385368
ongoingTasks.removeAll()
386369
await localProcessServer?.cancelOngoingTasks()
387370

388-
let task = Task {
389-
// since when the language server is no longer using the passed in content to generate
390-
// suggestions, therefore, we will need to update the content to the file before we
391-
// do any request.
392-
//
393-
// And sometimes the language server's content was not up to date and may generate
394-
// weird result when the cursor position exceeds the line.
395-
try? await notifyChangeTextDocument(fileURL: fileURL, content: content)
396-
defer {
397-
// recover the content.
398-
Task {
399-
try? await notifyChangeTextDocument(fileURL: fileURL, content: originalContent)
400-
}
401-
}
402-
try Task.checkCancellation()
371+
func sendRequest(maxTry: Int = 5) async throws -> [CodeSuggestion] {
403372
do {
404373
let completions = try await server
405374
.sendRequest(GitHubCopilotRequest.InlineCompletion(doc: .init(
406-
source: content,
407-
tabSize: tabSize,
408-
indentSize: indentSize,
409-
insertSpaces: !usesTabsForIndentation,
410-
path: fileURL.path,
411-
uri: fileURL.path,
412-
relativePath: relativePath,
413-
languageId: languageId,
414-
position: cursorPosition
375+
textDocument: .init(uri: fileURL.path, version: 1),
376+
position: cursorPosition,
377+
formattingOptions: .init(
378+
tabSize: tabSize,
379+
insertSpaces: !usesTabsForIndentation
380+
),
381+
context: .init(triggerKind: .invoked)
415382
)))
416383
.items
417384
.compactMap { (item: _) -> CodeSuggestion? in
@@ -427,11 +394,56 @@ public final class GitHubCopilotService: GitHubCopilotBaseService,
427394
try Task.checkCancellation()
428395
return completions
429396
} catch let error as ServerError {
397+
switch error {
398+
case .serverError:
399+
if maxTry <= 0 { break }
400+
Logger.gitHubCopilot.error(
401+
"Try getting suggestions again: \(GitHubCopilotError.languageServerError(error).localizedDescription)"
402+
)
403+
try await Task.sleep(nanoseconds: 400_000_000)
404+
return try await sendRequest(maxTry: maxTry - 1)
405+
default:
406+
break
407+
}
430408
throw GitHubCopilotError.languageServerError(error)
431409
} catch {
432410
throw error
433411
}
434412
}
413+
414+
func recoverContent() async {
415+
try? await notifyChangeTextDocument(
416+
fileURL: fileURL,
417+
content: originalContent,
418+
version: 0
419+
)
420+
}
421+
422+
// since when the language server is no longer using the passed in content to generate
423+
// suggestions, we will need to update the content to the file before we do any request.
424+
//
425+
// And sometimes the language server's content was not up to date and may generate
426+
// weird result when the cursor position exceeds the line.
427+
let task = Task { @GitHubCopilotSuggestionActor in
428+
try? await notifyChangeTextDocument(
429+
fileURL: fileURL,
430+
content: content,
431+
version: 1
432+
)
433+
434+
do {
435+
try Task.checkCancellation()
436+
return try await sendRequest()
437+
} catch let error as CancellationError {
438+
if ongoingTasks.isEmpty {
439+
await recoverContent()
440+
}
441+
throw error
442+
} catch {
443+
await recoverContent()
444+
throw error
445+
}
446+
}
435447

436448
ongoingTasks.insert(task)
437449

@@ -440,6 +452,8 @@ public final class GitHubCopilotService: GitHubCopilotBaseService,
440452

441453
@GitHubCopilotSuggestionActor
442454
public func cancelRequest() async {
455+
ongoingTasks.forEach { $0.cancel() }
456+
ongoingTasks.removeAll()
443457
await localProcessServer?.cancelOngoingTasks()
444458
}
445459

@@ -480,14 +494,18 @@ public final class GitHubCopilotService: GitHubCopilotBaseService,
480494
}
481495

482496
@GitHubCopilotSuggestionActor
483-
public func notifyChangeTextDocument(fileURL: URL, content: String) async throws {
497+
public func notifyChangeTextDocument(
498+
fileURL: URL,
499+
content: String,
500+
version: Int
501+
) async throws {
484502
let uri = "file://\(fileURL.path)"
485503
// Logger.service.debug("Change \(uri), \(content.count)")
486504
try await server.sendNotification(
487505
.didChangeTextDocument(
488506
DidChangeTextDocumentParams(
489507
uri: uri,
490-
version: 0,
508+
version: version,
491509
contentChange: .init(
492510
range: nil,
493511
rangeLength: nil,

0 commit comments

Comments
 (0)