Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Update accepting suggestion to handle the case where the suffix of a …
…suggestion is typed
  • Loading branch information
intitni committed Jul 18, 2023
commit 587a05f677667cb2ef03394ccda0b276ff8143f5
24 changes: 18 additions & 6 deletions Core/Sources/SuggestionInjector/SuggestionInjector.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import SuggestionModel
import Foundation
import SuggestionModel

let suggestionStart = "/*========== Copilot Suggestion"
let suggestionEnd = "*///======== End of Copilot Suggestion"
Expand Down Expand Up @@ -141,8 +141,6 @@ public struct SuggestionInjector {
let end = completion.range.end
let suggestionContent = completion.text

let _ = start.line < content.endIndex ? content[start.line] : nil

let firstRemovedLine = content[safe: start.line]
let lastRemovedLine = content[safe: end.line]
let startLine = max(0, start.line)
Expand Down Expand Up @@ -177,12 +175,24 @@ public struct SuggestionInjector {
}

// appending suffix text not in range if needed.
let cursorCol = toBeInserted[toBeInserted.endIndex - 1].count - 1
let skipAppendingDueToContinueTyping = {
guard let first = toBeInserted.first?.dropLast(1), !first.isEmpty else { return false }
let droppedLast = lastRemovedLine?.dropLast(1)
guard let droppedLast, !droppedLast.isEmpty else { return false }
return first.hasPrefix(droppedLast)
// case 1: user keeps typing as the suggestion suggests.
if first.hasPrefix(droppedLast) {
return true
}
// case 2: user also typed the suffix of the suggestion (or auto-completed by Xcode)
if end.character < droppedLast.count - 1 {
let splitIndex = droppedLast.index(droppedLast.startIndex, offsetBy: end.character)
let prefix = droppedLast[..<splitIndex]
let suffix = droppedLast[splitIndex...]
if first.hasPrefix(prefix), first.hasSuffix(suffix) {
return true
}
}
return false
}()
if let lastRemovedLine,
!skipAppendingDueToContinueTyping,
Expand All @@ -200,11 +210,12 @@ public struct SuggestionInjector {
toBeInserted[toBeInserted.endIndex - 1].removeLast(1)
}
let leftover = lastRemovedLine[leftoverRange]

toBeInserted[toBeInserted.endIndex - 1]
.append(contentsOf: leftover)
}

let cursorCol = toBeInserted[toBeInserted.endIndex - 1].count - 1
let insertingIndex = min(start.line, content.endIndex)
content.insert(contentsOf: toBeInserted, at: insertingIndex)
extraInfo.modifications.append(.inserted(insertingIndex, toBeInserted))
Expand Down Expand Up @@ -270,3 +281,4 @@ extension Array {
indices.contains(index) ? self[index] : nil
}
}

43 changes: 42 additions & 1 deletion Core/Tests/SuggestionInjectorTests/AcceptSuggestionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,47 @@ final class AcceptSuggestionTests: XCTestCase {
}
""")
}

func test_accept_suggestion_overlap_continue_typing_has_suffix_typed() async throws {
let content = """
struct Cat {
var n: String
}
"""
let text = """
var name: String
"""
let suggestion = CodeSuggestion(
text: text,
position: .init(line: 1, character: 9),
uuid: "",
range: .init(
start: .init(line: 1, character: 0),
end: .init(line: 1, character: 9)
),
displayText: ""
)

var extraInfo = SuggestionInjector.ExtraInfo()
var lines = content.breakLines()
var cursor = CursorPosition(line: 0, character: 0)
SuggestionInjector().acceptSuggestion(
intoContentWithoutSuggestion: &lines,
cursorPosition: &cursor,
completion: suggestion,
extraInfo: &extraInfo
)
XCTAssertTrue(extraInfo.didChangeContent)
XCTAssertTrue(extraInfo.didChangeCursorPosition)
XCTAssertNil(extraInfo.suggestionRange)
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
XCTAssertEqual(cursor, .init(line: 1, character: 20))
XCTAssertEqual(lines.joined(separator: ""), """
struct Cat {
var name: String
}
""")
}

func test_propose_suggestion_partial_overlap() async throws {
let content = "func quickSort() {}}\n"
Expand Down Expand Up @@ -308,7 +349,7 @@ final class AcceptSuggestionTests: XCTestCase {
XCTAssertTrue(extraInfo.didChangeCursorPosition)
XCTAssertNil(extraInfo.suggestionRange)
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
XCTAssertEqual(cursor, .init(line: 4, character: 1))
XCTAssertEqual(cursor, .init(line: 4, character: 0))
XCTAssertEqual(lines.joined(separator: ""), text)
}

Expand Down