Skip to content

Commit a78effe

Browse files
committed
Fix that ranges are incorrect after accepting multiple suggestions
1 parent c15858d commit a78effe

2 files changed

Lines changed: 168 additions & 35 deletions

File tree

Tool/Sources/SuggestionInjector/SuggestionInjector.swift

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,39 @@ public struct SuggestionInjector {
9393
completions: [CodeSuggestion],
9494
extraInfo: inout ExtraInfo
9595
) {
96-
var previousCompletion: CodeSuggestion?
97-
98-
let sortedCompletions = completions.sorted { $0.range.start.line < $1.range.start.line }
96+
let sortedCompletions = completions.sorted {
97+
if $0.range.start.line < $1.range.start.line {
98+
true
99+
} else if $0.range.start.line == $1.range.start.line {
100+
$0.range.start.character < $1.range.start.character
101+
} else {
102+
false
103+
}
104+
}
99105

100106
for var completion in sortedCompletions {
101-
defer { previousCompletion = completion }
102-
103-
// Adjust the position of the completion by the accumulated line count change
104-
if let previousCompletionId = previousCompletion?.id,
105-
let previousRange = extraInfo.modificationRanges[previousCompletionId]
106-
{
107-
let lineCountChange = previousRange
108-
.lineCount - (completion.range.start.line - previousRange.start.line)
107+
let lineCountChange: Int = {
108+
var accumulation = 0
109+
let endIndex = completion.range.start.line
110+
for modification in extraInfo.modifications {
111+
switch modification {
112+
case let .deleted(range):
113+
if range.lowerBound <= endIndex {
114+
accumulation -= range.count
115+
if range.upperBound >= endIndex {
116+
accumulation += range.upperBound - endIndex
117+
}
118+
}
119+
case let .inserted(index, lines):
120+
if index <= endIndex {
121+
accumulation += lines.count
122+
}
123+
}
124+
}
125+
return accumulation
126+
}()
127+
128+
if lineCountChange != 0 {
109129
completion.position = CursorPosition(
110130
line: completion.position.line + lineCountChange,
111131
character: completion.position.character
@@ -122,6 +142,18 @@ public struct SuggestionInjector {
122142
)
123143
}
124144

145+
completion.replacingLines = {
146+
let start = completion.range.start.line
147+
let end = completion.range.end.line
148+
if start >= content.endIndex {
149+
return []
150+
}
151+
if end < content.endIndex {
152+
return Array(content[start...end])
153+
}
154+
return Array(content[start...])
155+
}()
156+
125157
// Accept the suggestion
126158
acceptSuggestion(
127159
intoContentWithoutSuggestion: &content,

Tool/Tests/SuggestionInjectorTests/AcceptSuggestionTests.swift

Lines changed: 125 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,40 +1073,75 @@ final class AcceptSuggestionTests: XCTestCase {
10731073
10741074
""")
10751075
}
1076-
1076+
10771077
func test_accepting_multiple_suggestions_at_a_time() async throws {
10781078
let content = """
1079-
let foobar = 1
1080-
let zooKoo = 2
1079+
protocol Definition {
1080+
var id: String
1081+
var name: String
1082+
}
1083+
1084+
struct Foo {
1085+
1086+
}
1087+
1088+
struct Bar {
1089+
1090+
}
1091+
1092+
let foo = Foo()
1093+
1094+
struct Baz {}
10811095
"""
10821096
let text1 = """
1083-
let fooBar = 1
1084-
let fooBar = 2
1097+
struct Foo: Definition {
1098+
var id: String
1099+
var name: String
1100+
}
10851101
"""
10861102
let suggestion1 = CodeSuggestion(
10871103
id: "1",
10881104
text: text1,
1089-
position: .init(line: 0, character: 0),
1105+
position: .init(line: 5, character: 0),
10901106
range: .init(
1091-
start: .init(line: 0, character: 0),
1092-
end: .init(line: 0, character: 14)
1107+
start: .init(line: 5, character: 0),
1108+
end: .init(line: 7, character: 1)
10931109
),
1094-
replacingLines: content.breakLines(appendLineBreakToLastLine: true)
1110+
replacingLines: Array(content.breakLines(appendLineBreakToLastLine: true)[5...7])
10951111
)
10961112

10971113
let text2 = """
1098-
let zooKoo = 2
1099-
let zooKoo = 3
1114+
struct Bar: Definition {
1115+
var id: String
1116+
var name: String
1117+
}
11001118
"""
11011119
let suggestion2 = CodeSuggestion(
11021120
id: "2",
11031121
text: text2,
1104-
position: .init(line: 1, character: 0),
1122+
position: .init(line: 9, character: 0),
11051123
range: .init(
1106-
start: .init(line: 1, character: 0),
1107-
end: .init(line: 1, character: 14)
1124+
start: .init(line: 9, character: 0),
1125+
end: .init(line: 11, character: 1)
11081126
),
1109-
replacingLines: content.breakLines(appendLineBreakToLastLine: true)
1127+
replacingLines: Array(content.breakLines(appendLineBreakToLastLine: true)[9...11])
1128+
)
1129+
1130+
let text3 = """
1131+
struct Baz: Definition {
1132+
var id: String
1133+
var name: String
1134+
}
1135+
"""
1136+
let suggestion3 = CodeSuggestion(
1137+
id: "3",
1138+
text: text3,
1139+
position: .init(line: 15, character: 0),
1140+
range: .init(
1141+
start: .init(line: 15, character: 0),
1142+
end: .init(line: 15, character: 13)
1143+
),
1144+
replacingLines: Array(content.breakLines(appendLineBreakToLastLine: true)[15...15])
11101145
)
11111146

11121147
var extraInfo = SuggestionInjector.ExtraInfo()
@@ -1115,23 +1150,89 @@ final class AcceptSuggestionTests: XCTestCase {
11151150
SuggestionInjector().acceptSuggestions(
11161151
intoContentWithoutSuggestion: &lines,
11171152
cursorPosition: &cursor,
1118-
completions: [suggestion1, suggestion2],
1153+
completions: [suggestion1, suggestion2, suggestion3],
11191154
extraInfo: &extraInfo
11201155
)
11211156
XCTAssertTrue(extraInfo.didChangeContent)
11221157
XCTAssertTrue(extraInfo.didChangeCursorPosition)
11231158
XCTAssertEqual(lines, content.breakIntoEditorStyleLines().applying(extraInfo.modifications))
1124-
XCTAssertEqual(cursor, .init(line: 3, character: 14))
1159+
XCTAssertEqual(cursor, .init(line: 20, character: 1))
11251160
XCTAssertEqual(lines.joined(separator: ""), """
1126-
let fooBar = 1
1127-
let fooBar = 2
1128-
let zooKoo = 2
1129-
let zooKoo = 3
1130-
1161+
protocol Definition {
1162+
var id: String
1163+
var name: String
1164+
}
1165+
1166+
struct Foo: Definition {
1167+
var id: String
1168+
var name: String
1169+
}
1170+
1171+
struct Bar: Definition {
1172+
var id: String
1173+
var name: String
1174+
}
1175+
1176+
let foo = Foo()
1177+
1178+
struct Baz: Definition {
1179+
var id: String
1180+
var name: String
1181+
}
1182+
11311183
""")
11321184
XCTAssertEqual(extraInfo.modificationRanges, [
1133-
"1": .init(start: .init(line: 0, character: 0), end: .init(line: 1, character: 14)),
1134-
"2": .init(start: .init(line: 2, character: 0), end: .init(line: 3, character: 14))
1185+
"1": .init(start: .init(line: 5, character: 0), end: .init(line: 8, character: 1)),
1186+
"2": .init(start: .init(line: 10, character: 0), end: .init(line: 13, character: 1)),
1187+
"3": .init(start: .init(line: 17, character: 0), end: .init(line: 20, character: 1)),
1188+
])
1189+
}
1190+
1191+
func test_accepting_multiple_same_line_suggestions_at_a_time() async throws {
1192+
let content = "let foo = 1\n"
1193+
let text1 = "berry"
1194+
let suggestion1 = CodeSuggestion(
1195+
id: "1",
1196+
text: text1,
1197+
position: .init(line: 0, character: 4),
1198+
range: .init(
1199+
start: .init(line: 0, character: 4),
1200+
end: .init(line: 0, character: 7)
1201+
),
1202+
replacingLines: [content]
1203+
)
1204+
1205+
let text2 = """
1206+
200
1207+
"""
1208+
let suggestion2 = CodeSuggestion(
1209+
id: "2",
1210+
text: text2,
1211+
position: .init(line: 0, character: 10),
1212+
range: .init(
1213+
start: .init(line: 0, character: 10),
1214+
end: .init(line: 0, character: 11)
1215+
),
1216+
replacingLines: [content]
1217+
)
1218+
1219+
var extraInfo = SuggestionInjector.ExtraInfo()
1220+
var lines = content.breakIntoEditorStyleLines()
1221+
var cursor = CursorPosition(line: 0, character: 0)
1222+
SuggestionInjector().acceptSuggestions(
1223+
intoContentWithoutSuggestion: &lines,
1224+
cursorPosition: &cursor,
1225+
completions: [suggestion1, suggestion2],
1226+
extraInfo: &extraInfo
1227+
)
1228+
XCTAssertTrue(extraInfo.didChangeContent)
1229+
XCTAssertTrue(extraInfo.didChangeCursorPosition)
1230+
XCTAssertEqual(lines, content.breakIntoEditorStyleLines().applying(extraInfo.modifications))
1231+
XCTAssertEqual(cursor, .init(line: 0, character: 15))
1232+
XCTAssertEqual(lines.joined(separator: ""), "let berry = 200\n")
1233+
XCTAssertEqual(extraInfo.modificationRanges, [
1234+
"1": .init(start: .init(line: 0, character: 4), end: .init(line: 0, character: 9)),
1235+
"2": .init(start: .init(line: 0, character: 12), end: .init(line: 0, character: 15)),
11351236
])
11361237
}
11371238
}

0 commit comments

Comments
 (0)