Skip to content

Commit 3359d1d

Browse files
committed
Merge tag 'fix-accept-suggestion' into develop
2 parents 3935801 + 7c29c9a commit 3359d1d

File tree

2 files changed

+71
-19
lines changed

2 files changed

+71
-19
lines changed

Core/Sources/SuggestionInjector/SuggestionInjector.swift

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -175,36 +175,52 @@ public struct SuggestionInjector {
175175
}
176176

177177
// appending suffix text not in range if needed.
178-
let skipAppendingDueToContinueTyping = {
178+
let leftoverCount: Int = {
179+
let maxCount = lastRemovedLine?.count ?? 0
179180
guard let first = toBeInserted.first?
180181
.dropLast((toBeInserted.first?.hasSuffix("\n") ?? false) ? 1 : 0),
181-
!first.isEmpty else { return false }
182+
!first.isEmpty else { return maxCount }
182183
guard let last = toBeInserted.last?
183184
.dropLast((toBeInserted.last?.hasSuffix("\n") ?? false) ? 1 : 0),
184-
!last.isEmpty else { return false }
185+
!last.isEmpty else { return maxCount }
185186
let droppedLast = lastRemovedLine?
186187
.dropLast((lastRemovedLine?.hasSuffix("\n") ?? false) ? 1 : 0)
187-
guard let droppedLast, !droppedLast.isEmpty else { return false }
188+
guard let droppedLast, !droppedLast.isEmpty else { return maxCount }
188189
// case 1: user keeps typing as the suggestion suggests.
189190
if first.hasPrefix(droppedLast) {
190-
return true
191+
return 0
191192
}
192193
// case 2: user also typed the suffix of the suggestion (or auto-completed by Xcode)
193-
if cursorPosition.character < droppedLast.count {
194-
let splitIndex = droppedLast.index(
195-
droppedLast.startIndex,
196-
offsetBy: cursorPosition.character
197-
)
198-
let prefix = droppedLast[..<splitIndex]
199-
let suffix = droppedLast[splitIndex...]
200-
if first.hasPrefix(prefix), last.hasSuffix(suffix) {
201-
return true
194+
if end.character < droppedLast.count {
195+
// locate the split index, the prefix of which matches the suggestion prefix.
196+
var splitIndex: String.Index? = nil
197+
for offset in end.character..<droppedLast.count {
198+
let proposedIndex = droppedLast.index(
199+
droppedLast.startIndex,
200+
offsetBy: offset
201+
)
202+
let prefix = droppedLast[..<proposedIndex]
203+
if first.hasPrefix(prefix) {
204+
splitIndex = proposedIndex
205+
}
206+
}
207+
208+
// then check how many characters are not in the suffix of the suggestion.
209+
if let splitIndex {
210+
let suffix = droppedLast[splitIndex...]
211+
if last.hasSuffix(suffix) {
212+
return 0
213+
}
214+
return suffix.count
202215
}
203216
}
204-
return false
217+
218+
return maxCount
205219
}()
220+
221+
// appending suffix text not in range if needed.
206222
if let lastRemovedLine,
207-
!skipAppendingDueToContinueTyping,
223+
leftoverCount > 0,
208224
!lastRemovedLine.isEmptyOrNewLine,
209225
end.character >= 0,
210226
end.character - 1 < lastRemovedLine.count,
@@ -218,10 +234,9 @@ public struct SuggestionInjector {
218234
if toBeInserted[toBeInserted.endIndex - 1].hasSuffix("\n") {
219235
toBeInserted[toBeInserted.endIndex - 1].removeLast(1)
220236
}
221-
let leftover = lastRemovedLine[leftoverRange]
237+
let leftover = lastRemovedLine[leftoverRange].suffix(leftoverCount)
222238

223-
toBeInserted[toBeInserted.endIndex - 1]
224-
.append(contentsOf: leftover)
239+
toBeInserted[toBeInserted.endIndex - 1].append(contentsOf: leftover)
225240
}
226241

227242
let cursorCol = toBeInserted[toBeInserted.endIndex - 1].count - 1

Core/Tests/SuggestionInjectorTests/AcceptSuggestionTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,43 @@ final class AcceptSuggestionTests: XCTestCase {
213213
""")
214214
}
215215

216+
func test_accept_suggestion_overlap_continue_typing_suggestion_in_the_middle() async throws {
217+
let content = """
218+
print("He")
219+
"""
220+
let text = """
221+
print("Hello World!
222+
"""
223+
let suggestion = CodeSuggestion(
224+
text: text,
225+
position: .init(line: 0, character: 6),
226+
uuid: "",
227+
range: .init(
228+
start: .init(line: 0, character: 0),
229+
end: .init(line: 0, character: 6)
230+
),
231+
displayText: ""
232+
)
233+
234+
var extraInfo = SuggestionInjector.ExtraInfo()
235+
var lines = content.breakLines()
236+
var cursor = CursorPosition(line: 0, character: 7)
237+
SuggestionInjector().acceptSuggestion(
238+
intoContentWithoutSuggestion: &lines,
239+
cursorPosition: &cursor,
240+
completion: suggestion,
241+
extraInfo: &extraInfo
242+
)
243+
XCTAssertTrue(extraInfo.didChangeContent)
244+
XCTAssertTrue(extraInfo.didChangeCursorPosition)
245+
XCTAssertNil(extraInfo.suggestionRange)
246+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
247+
XCTAssertEqual(cursor, .init(line: 0, character: 20))
248+
XCTAssertEqual(lines.joined(separator: ""), """
249+
print("Hello World!")
250+
""")
251+
}
252+
216253
func test_accept_suggestion_overlap_continue_typing_has_suffix_typed_suggestion_has_multiple_lines() async throws {
217254
let content = """
218255
struct Cat {}

0 commit comments

Comments
 (0)