Skip to content

Commit 587a05f

Browse files
committed
Update accepting suggestion to handle the case where the suffix of a suggestion is typed
1 parent cc39712 commit 587a05f

File tree

2 files changed

+60
-7
lines changed

2 files changed

+60
-7
lines changed

Core/Sources/SuggestionInjector/SuggestionInjector.swift

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import SuggestionModel
21
import Foundation
2+
import SuggestionModel
33

44
let suggestionStart = "/*========== Copilot Suggestion"
55
let suggestionEnd = "*///======== End of Copilot Suggestion"
@@ -141,8 +141,6 @@ public struct SuggestionInjector {
141141
let end = completion.range.end
142142
let suggestionContent = completion.text
143143

144-
let _ = start.line < content.endIndex ? content[start.line] : nil
145-
146144
let firstRemovedLine = content[safe: start.line]
147145
let lastRemovedLine = content[safe: end.line]
148146
let startLine = max(0, start.line)
@@ -177,12 +175,24 @@ public struct SuggestionInjector {
177175
}
178176

179177
// appending suffix text not in range if needed.
180-
let cursorCol = toBeInserted[toBeInserted.endIndex - 1].count - 1
181178
let skipAppendingDueToContinueTyping = {
182179
guard let first = toBeInserted.first?.dropLast(1), !first.isEmpty else { return false }
183180
let droppedLast = lastRemovedLine?.dropLast(1)
184181
guard let droppedLast, !droppedLast.isEmpty else { return false }
185-
return first.hasPrefix(droppedLast)
182+
// case 1: user keeps typing as the suggestion suggests.
183+
if first.hasPrefix(droppedLast) {
184+
return true
185+
}
186+
// case 2: user also typed the suffix of the suggestion (or auto-completed by Xcode)
187+
if end.character < droppedLast.count - 1 {
188+
let splitIndex = droppedLast.index(droppedLast.startIndex, offsetBy: end.character)
189+
let prefix = droppedLast[..<splitIndex]
190+
let suffix = droppedLast[splitIndex...]
191+
if first.hasPrefix(prefix), first.hasSuffix(suffix) {
192+
return true
193+
}
194+
}
195+
return false
186196
}()
187197
if let lastRemovedLine,
188198
!skipAppendingDueToContinueTyping,
@@ -200,11 +210,12 @@ public struct SuggestionInjector {
200210
toBeInserted[toBeInserted.endIndex - 1].removeLast(1)
201211
}
202212
let leftover = lastRemovedLine[leftoverRange]
203-
213+
204214
toBeInserted[toBeInserted.endIndex - 1]
205215
.append(contentsOf: leftover)
206216
}
207217

218+
let cursorCol = toBeInserted[toBeInserted.endIndex - 1].count - 1
208219
let insertingIndex = min(start.line, content.endIndex)
209220
content.insert(contentsOf: toBeInserted, at: insertingIndex)
210221
extraInfo.modifications.append(.inserted(insertingIndex, toBeInserted))
@@ -270,3 +281,4 @@ extension Array {
270281
indices.contains(index) ? self[index] : nil
271282
}
272283
}
284+

Core/Tests/SuggestionInjectorTests/AcceptSuggestionTests.swift

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,47 @@ final class AcceptSuggestionTests: XCTestCase {
174174
}
175175
""")
176176
}
177+
178+
func test_accept_suggestion_overlap_continue_typing_has_suffix_typed() async throws {
179+
let content = """
180+
struct Cat {
181+
var n: String
182+
}
183+
"""
184+
let text = """
185+
var name: String
186+
"""
187+
let suggestion = CodeSuggestion(
188+
text: text,
189+
position: .init(line: 1, character: 9),
190+
uuid: "",
191+
range: .init(
192+
start: .init(line: 1, character: 0),
193+
end: .init(line: 1, character: 9)
194+
),
195+
displayText: ""
196+
)
197+
198+
var extraInfo = SuggestionInjector.ExtraInfo()
199+
var lines = content.breakLines()
200+
var cursor = CursorPosition(line: 0, character: 0)
201+
SuggestionInjector().acceptSuggestion(
202+
intoContentWithoutSuggestion: &lines,
203+
cursorPosition: &cursor,
204+
completion: suggestion,
205+
extraInfo: &extraInfo
206+
)
207+
XCTAssertTrue(extraInfo.didChangeContent)
208+
XCTAssertTrue(extraInfo.didChangeCursorPosition)
209+
XCTAssertNil(extraInfo.suggestionRange)
210+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
211+
XCTAssertEqual(cursor, .init(line: 1, character: 20))
212+
XCTAssertEqual(lines.joined(separator: ""), """
213+
struct Cat {
214+
var name: String
215+
}
216+
""")
217+
}
177218

178219
func test_propose_suggestion_partial_overlap() async throws {
179220
let content = "func quickSort() {}}\n"
@@ -308,7 +349,7 @@ final class AcceptSuggestionTests: XCTestCase {
308349
XCTAssertTrue(extraInfo.didChangeCursorPosition)
309350
XCTAssertNil(extraInfo.suggestionRange)
310351
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
311-
XCTAssertEqual(cursor, .init(line: 4, character: 1))
352+
XCTAssertEqual(cursor, .init(line: 4, character: 0))
312353
XCTAssertEqual(lines.joined(separator: ""), text)
313354
}
314355

0 commit comments

Comments
 (0)