Skip to content

Commit 0a67c60

Browse files
committed
Add extra info to SuggestionInjector
1 parent a0b951f commit 0a67c60

File tree

5 files changed

+161
-30
lines changed

5 files changed

+161
-30
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Foundation
2+
3+
public enum Modification: Codable, Equatable {
4+
case deleted(ClosedRange<Int>)
5+
case inserted(Int, [String])
6+
}
7+
8+
public extension Array where Element == String {
9+
mutating func apply(_ modifications: [Modification]) {
10+
for modification in modifications {
11+
switch modification {
12+
case let .deleted(range):
13+
if isEmpty { break }
14+
let removingRange = range.lowerBound ..< (range.upperBound + 1)
15+
removeSubrange(removingRange.clamped(to: 0 ..< endIndex))
16+
case let .inserted(index, strings):
17+
insert(contentsOf: strings, at: index)
18+
}
19+
}
20+
}
21+
22+
func applying(_ modifications: [Modification]) -> Array {
23+
var newArray = self
24+
newArray.apply(modifications)
25+
return newArray
26+
}
27+
}

Core/Sources/SuggestionInjector/SuggestionInjector.swift

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,20 @@ let suggestionEnd = "*///======== End of Copilot Suggestion"
1111
public struct SuggestionInjector {
1212
public init() {}
1313

14+
public struct ExtraInfo {
15+
public var didChangeContent = false
16+
public var didChangeCursorPosition = false
17+
public var suggestionRange: ClosedRange<Int>?
18+
public var modifications: [Modification] = []
19+
public init() {}
20+
}
21+
1422
public func rejectCurrentSuggestions(
1523
from content: inout [String],
16-
cursorPosition: inout CursorPosition
24+
cursorPosition: inout CursorPosition,
25+
extraInfo: inout ExtraInfo
1726
) {
18-
var ranges = [Range<Int>]()
27+
var ranges = [ClosedRange<Int>]()
1928
var suggestionStartIndex = -1
2029

2130
for (index, line) in content.enumerated() {
@@ -27,25 +36,34 @@ public struct SuggestionInjector {
2736
suggestionStartIndex = -1
2837
}
2938
}
39+
40+
let reversedRanges = ranges.reversed()
3041

31-
for range in ranges.lazy.reversed() {
32-
for i in stride(from: range.endIndex, through: range.startIndex, by: -1) {
42+
extraInfo.modifications.append(contentsOf: reversedRanges.map(Modification.deleted))
43+
extraInfo.didChangeContent = !ranges.isEmpty
44+
45+
for range in reversedRanges {
46+
for i in stride(from: range.upperBound, through: range.lowerBound, by: -1) {
3347
if i <= cursorPosition.line, cursorPosition.line >= 0 {
3448
cursorPosition = .init(
3549
line: cursorPosition.line - 1,
3650
character: i == cursorPosition.line ? 0 : cursorPosition.character
3751
)
52+
extraInfo.didChangeCursorPosition = true
3853
}
3954
content.remove(at: i)
4055
}
4156
}
57+
58+
extraInfo.suggestionRange = nil
4259
}
4360

4461
public func proposeSuggestion(
4562
intoContentWithoutSuggestion content: inout [String],
4663
completion: CopilotCompletion,
4764
index: Int,
48-
count: Int
65+
count: Int,
66+
extraInfo: inout ExtraInfo
4967
) {
5068
let start = completion.range.start
5169
let startText = "\(suggestionStart) \(index + 1)/\(count)"
@@ -77,17 +95,27 @@ public struct SuggestionInjector {
7795
return 0
7896
}()
7997
if content.endIndex < lineIndex {
98+
extraInfo.didChangeContent = true
99+
extraInfo.suggestionRange = content.endIndex ... content.endIndex + lines.count - 1
100+
extraInfo.modifications.append(.inserted(content.endIndex, lines))
80101
content.append(contentsOf: lines)
81102
} else {
103+
extraInfo.didChangeContent = true
104+
extraInfo.suggestionRange = lineIndex ... lineIndex + lines.count - 1
105+
extraInfo.modifications.append(.inserted(lineIndex, lines))
82106
content.insert(contentsOf: lines, at: lineIndex)
83107
}
84108
}
85109

86110
public func acceptSuggestion(
87111
intoContentWithoutSuggestion content: inout [String],
88112
cursorPosition: inout CursorPosition,
89-
completion: CopilotCompletion
113+
completion: CopilotCompletion,
114+
extraInfo: inout ExtraInfo
90115
) {
116+
extraInfo.didChangeContent = true
117+
extraInfo.didChangeCursorPosition = true
118+
extraInfo.suggestionRange = nil
91119
let start = completion.range.start
92120
let suggestionContent = completion.text
93121

@@ -96,20 +124,24 @@ public struct SuggestionInjector {
96124

97125
if let existedLine, existedLine.count > 1, !commonPrefix.isEmpty {
98126
content.remove(at: start.line)
127+
extraInfo.modifications.append(.deleted(start.line...start.line))
99128
} else if content.count > start.line,
100129
content[start.line].isEmpty || content[start.line] == "\n"
101130
{
102131
content.remove(at: start.line)
132+
extraInfo.modifications.append(.deleted(start.line...start.line))
103133
}
104134

105135
let toBeInserted = suggestionContent.breakLines(appendLineBreakToLastLine: true)
106136
if content.endIndex < start.line {
107137
content.append(contentsOf: toBeInserted)
138+
extraInfo.modifications.append(.inserted(content.endIndex, toBeInserted))
108139
cursorPosition = .init(
109140
line: toBeInserted.endIndex,
110141
character: (toBeInserted.last?.count ?? 1) - 1
111142
)
112143
} else {
144+
extraInfo.modifications.append(.inserted(start.line, toBeInserted))
113145
content.insert(
114146
contentsOf: toBeInserted,
115147
at: start.line

Core/Tests/SuggestionInjectorTests/AcceptSuggestionTests.swift

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,19 @@ final class AcceptSuggestionTests: XCTestCase {
2424
),
2525
displayText: ""
2626
)
27-
27+
var extraInfo = SuggestionInjector.ExtraInfo()
2828
var lines = content.breakLines()
2929
var cursor = CursorPosition(line: 0, character: 0)
3030
SuggestionInjector().acceptSuggestion(
3131
intoContentWithoutSuggestion: &lines,
3232
cursorPosition: &cursor,
33-
completion: suggestion
33+
completion: suggestion,
34+
extraInfo: &extraInfo
3435
)
36+
XCTAssertTrue(extraInfo.didChangeContent)
37+
XCTAssertTrue(extraInfo.didChangeCursorPosition)
38+
XCTAssertNil(extraInfo.suggestionRange)
39+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
3540
XCTAssertEqual(cursor, .init(line: 2, character: 19))
3641
XCTAssertEqual(lines.joined(separator: ""), """
3742
struct Cat {
@@ -40,7 +45,7 @@ final class AcceptSuggestionTests: XCTestCase {
4045
}
4146
""")
4247
}
43-
48+
4449
func test_accept_suggestion_start_from_previous_line() async throws {
4550
let content = """
4651
struct Cat {
@@ -61,13 +66,19 @@ final class AcceptSuggestionTests: XCTestCase {
6166
displayText: ""
6267
)
6368

69+
var extraInfo = SuggestionInjector.ExtraInfo()
6470
var lines = content.breakLines()
6571
var cursor = CursorPosition(line: 0, character: 0)
6672
SuggestionInjector().acceptSuggestion(
6773
intoContentWithoutSuggestion: &lines,
6874
cursorPosition: &cursor,
69-
completion: suggestion
75+
completion: suggestion,
76+
extraInfo: &extraInfo
7077
)
78+
XCTAssertTrue(extraInfo.didChangeContent)
79+
XCTAssertTrue(extraInfo.didChangeCursorPosition)
80+
XCTAssertNil(extraInfo.suggestionRange)
81+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
7182
XCTAssertEqual(cursor, .init(line: 2, character: 19))
7283
XCTAssertEqual(lines.joined(separator: ""), """
7384
struct Cat {
@@ -98,13 +109,19 @@ final class AcceptSuggestionTests: XCTestCase {
98109
displayText: ""
99110
)
100111

112+
var extraInfo = SuggestionInjector.ExtraInfo()
101113
var lines = content.breakLines()
102114
var cursor = CursorPosition(line: 0, character: 0)
103115
SuggestionInjector().acceptSuggestion(
104116
intoContentWithoutSuggestion: &lines,
105117
cursorPosition: &cursor,
106-
completion: suggestion
118+
completion: suggestion,
119+
extraInfo: &extraInfo
107120
)
121+
XCTAssertTrue(extraInfo.didChangeContent)
122+
XCTAssertTrue(extraInfo.didChangeCursorPosition)
123+
XCTAssertNil(extraInfo.suggestionRange)
124+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
108125
XCTAssertEqual(cursor, .init(line: 2, character: 19))
109126
XCTAssertEqual(lines.joined(separator: ""), """
110127
struct Cat {
@@ -113,7 +130,7 @@ final class AcceptSuggestionTests: XCTestCase {
113130
}
114131
""")
115132
}
116-
133+
117134
func test_propose_suggestion_partial_overlap() async throws {
118135
let content = "func quickSort() {}}\n"
119136
let text = """
@@ -136,13 +153,19 @@ final class AcceptSuggestionTests: XCTestCase {
136153
displayText: ""
137154
)
138155

156+
var extraInfo = SuggestionInjector.ExtraInfo()
139157
var lines = content.breakLines()
140158
var cursor = CursorPosition(line: 0, character: 0)
141159
SuggestionInjector().acceptSuggestion(
142160
intoContentWithoutSuggestion: &lines,
143161
cursorPosition: &cursor,
144-
completion: suggestion
162+
completion: suggestion,
163+
extraInfo: &extraInfo
145164
)
165+
XCTAssertTrue(extraInfo.didChangeContent)
166+
XCTAssertTrue(extraInfo.didChangeCursorPosition)
167+
XCTAssertNil(extraInfo.suggestionRange)
168+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
146169
XCTAssertEqual(cursor, .init(line: 6, character: 1))
147170
XCTAssertEqual(lines.joined(separator: ""), """
148171
func quickSort() {
@@ -152,10 +175,10 @@ final class AcceptSuggestionTests: XCTestCase {
152175
quickSort(&array, left, right)
153176
print(array)
154177
}
155-
178+
156179
""")
157180
}
158-
181+
159182
func test_no_overlap_append_to_the_end() async throws {
160183
let content = "func quickSort() {\n"
161184
let text = """
@@ -177,13 +200,19 @@ final class AcceptSuggestionTests: XCTestCase {
177200
displayText: ""
178201
)
179202

203+
var extraInfo = SuggestionInjector.ExtraInfo()
180204
var lines = content.breakLines()
181205
var cursor = CursorPosition(line: 0, character: 0)
182206
SuggestionInjector().acceptSuggestion(
183207
intoContentWithoutSuggestion: &lines,
184208
cursorPosition: &cursor,
185-
completion: suggestion
209+
completion: suggestion,
210+
extraInfo: &extraInfo
186211
)
212+
XCTAssertTrue(extraInfo.didChangeContent)
213+
XCTAssertTrue(extraInfo.didChangeCursorPosition)
214+
XCTAssertNil(extraInfo.suggestionRange)
215+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
187216
XCTAssertEqual(cursor, .init(line: 6, character: 1))
188217
XCTAssertEqual(lines.joined(separator: ""), """
189218
func quickSort() {
@@ -193,7 +222,7 @@ final class AcceptSuggestionTests: XCTestCase {
193222
quickSort(&array, left, right)
194223
print(array)
195224
}
196-
225+
197226
""")
198227
}
199228
}

Core/Tests/SuggestionInjectorTests/ProposeSuggestionTests.swift

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,19 @@ final class ProposeSuggestionTests: XCTestCase {
2424
),
2525
displayText: ""
2626
)
27-
27+
var extraInfo = SuggestionInjector.ExtraInfo()
2828
var lines = content.breakLines()
2929
SuggestionInjector().proposeSuggestion(
3030
intoContentWithoutSuggestion: &lines,
3131
completion: suggestion,
3232
index: 0,
33-
count: 10
33+
count: 10,
34+
extraInfo: &extraInfo
3435
)
36+
XCTAssertTrue(extraInfo.didChangeContent)
37+
XCTAssertFalse(extraInfo.didChangeCursorPosition)
38+
XCTAssertEqual(extraInfo.suggestionRange, 2...5)
39+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
3540
XCTAssertEqual(lines.joined(separator: ""), """
3641
struct Cat {
3742
@@ -62,14 +67,19 @@ final class ProposeSuggestionTests: XCTestCase {
6267
),
6368
displayText: ""
6469
)
65-
70+
var extraInfo = SuggestionInjector.ExtraInfo()
6671
var lines = content.breakLines()
6772
SuggestionInjector().proposeSuggestion(
6873
intoContentWithoutSuggestion: &lines,
6974
completion: suggestion,
7075
index: 0,
71-
count: 10
76+
count: 10,
77+
extraInfo: &extraInfo
7278
)
79+
XCTAssertTrue(extraInfo.didChangeContent)
80+
XCTAssertFalse(extraInfo.didChangeCursorPosition)
81+
XCTAssertEqual(extraInfo.suggestionRange, 1...4)
82+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
7383
XCTAssertEqual(lines.joined(separator: ""), """
7484
struct Cat {
7585
/*========== Copilot Suggestion 1/10
@@ -100,14 +110,19 @@ final class ProposeSuggestionTests: XCTestCase {
100110
),
101111
displayText: ""
102112
)
103-
113+
var extraInfo = SuggestionInjector.ExtraInfo()
104114
var lines = content.breakLines()
105115
SuggestionInjector().proposeSuggestion(
106116
intoContentWithoutSuggestion: &lines,
107117
completion: suggestion,
108118
index: 0,
109-
count: 10
119+
count: 10,
120+
extraInfo: &extraInfo
110121
)
122+
XCTAssertTrue(extraInfo.didChangeContent)
123+
XCTAssertFalse(extraInfo.didChangeCursorPosition)
124+
XCTAssertEqual(extraInfo.suggestionRange, 2...5)
125+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
111126
XCTAssertEqual(lines.joined(separator: ""), """
112127
struct Cat {
113128
var name
@@ -143,14 +158,19 @@ final class ProposeSuggestionTests: XCTestCase {
143158
),
144159
displayText: ""
145160
)
146-
161+
var extraInfo = SuggestionInjector.ExtraInfo()
147162
var lines = content.breakLines()
148163
SuggestionInjector().proposeSuggestion(
149164
intoContentWithoutSuggestion: &lines,
150165
completion: suggestion,
151166
index: 0,
152-
count: 10
167+
count: 10,
168+
extraInfo: &extraInfo
153169
)
170+
XCTAssertTrue(extraInfo.didChangeContent)
171+
XCTAssertFalse(extraInfo.didChangeCursorPosition)
172+
XCTAssertEqual(extraInfo.suggestionRange, 2...8)
173+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
154174
XCTAssertEqual(lines.joined(separator: ""), """
155175
func quickSort() {
156176
@@ -188,14 +208,19 @@ final class ProposeSuggestionTests: XCTestCase {
188208
),
189209
displayText: ""
190210
)
191-
211+
var extraInfo = SuggestionInjector.ExtraInfo()
192212
var lines = content.breakLines()
193213
SuggestionInjector().proposeSuggestion(
194214
intoContentWithoutSuggestion: &lines,
195215
completion: suggestion,
196216
index: 0,
197-
count: 10
217+
count: 10,
218+
extraInfo: &extraInfo
198219
)
220+
XCTAssertTrue(extraInfo.didChangeContent)
221+
XCTAssertFalse(extraInfo.didChangeCursorPosition)
222+
XCTAssertEqual(extraInfo.suggestionRange, 1...9)
223+
XCTAssertEqual(lines, content.breakLines().applying(extraInfo.modifications))
199224
XCTAssertEqual(lines.joined(separator: ""), """
200225
func quickSort() {}}
201226
/*========== Copilot Suggestion 1/10

0 commit comments

Comments
 (0)