Skip to content

Commit 934034b

Browse files
committed
Merge branch 'release/0.2.0'
2 parents 3c8ffe3 + fd90ba1 commit 934034b

16 files changed

Lines changed: 418 additions & 127 deletions

File tree

Copilot for Xcode.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@
843843
CODE_SIGN_ENTITLEMENTS = "Copilot for Xcode/Copilot_for_Xcode.entitlements";
844844
CODE_SIGN_STYLE = Automatic;
845845
COMBINE_HIDPI_IMAGES = YES;
846-
CURRENT_PROJECT_VERSION = 2;
846+
CURRENT_PROJECT_VERSION = 4;
847847
DEVELOPMENT_ASSET_PATHS = "\"Copilot for Xcode/Preview Content\"";
848848
DEVELOPMENT_TEAM = 5YKZ4Y3DAW;
849849
ENABLE_HARDENED_RUNTIME = YES;
@@ -856,7 +856,7 @@
856856
"@executable_path/../Frameworks",
857857
);
858858
MACOSX_DEPLOYMENT_TARGET = 12.0;
859-
MARKETING_VERSION = 0.1.0;
859+
MARKETING_VERSION = 0.2.0;
860860
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE)";
861861
PRODUCT_MODULE_NAME = Copilot_for_Xcode;
862862
PRODUCT_NAME = "Copilot for Xcode Dev";
@@ -874,7 +874,7 @@
874874
CODE_SIGN_ENTITLEMENTS = "Copilot for Xcode/Copilot_for_Xcode.entitlements";
875875
CODE_SIGN_STYLE = Automatic;
876876
COMBINE_HIDPI_IMAGES = YES;
877-
CURRENT_PROJECT_VERSION = 2;
877+
CURRENT_PROJECT_VERSION = 4;
878878
DEVELOPMENT_ASSET_PATHS = "\"Copilot for Xcode/Preview Content\"";
879879
DEVELOPMENT_TEAM = 5YKZ4Y3DAW;
880880
ENABLE_HARDENED_RUNTIME = YES;
@@ -887,7 +887,7 @@
887887
"@executable_path/../Frameworks",
888888
);
889889
MACOSX_DEPLOYMENT_TARGET = 12.0;
890-
MARKETING_VERSION = 0.1.0;
890+
MARKETING_VERSION = 0.2.0;
891891
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE)";
892892
PRODUCT_NAME = "Copilot for Xcode";
893893
SWIFT_EMIT_LOC_STRINGS = YES;

Core/Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ let package = Package(
3636
name: "CopilotModel",
3737
dependencies: ["LanguageClient"]
3838
),
39+
.testTarget(
40+
name: "CopilotModelTests",
41+
dependencies: ["CopilotModel"]
42+
),
3943
.target(
4044
name: "SuggestionInjector",
4145
dependencies: ["CopilotModel"]
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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: Swift.min(endIndex, index))
18+
}
19+
}
20+
}
21+
22+
func applying(_ modifications: [Modification]) -> Array {
23+
var newArray = self
24+
newArray.apply(modifications)
25+
return newArray
26+
}
27+
}
28+
29+
public extension NSMutableArray {
30+
func apply(_ modifications: [Modification]) {
31+
for modification in modifications {
32+
switch modification {
33+
case let .deleted(range):
34+
if count == 0 { break }
35+
let newRange = range.clamped(to: 0 ... (count - 1))
36+
removeObjects(in: NSRange(newRange))
37+
case let .inserted(index, strings):
38+
for string in strings.reversed() {
39+
insert(string, at: Swift.min(count, index))
40+
}
41+
}
42+
}
43+
}
44+
}

Core/Sources/CopilotService/CopilotRequest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ enum CopilotRequest {
164164

165165
var request: ClientRequest {
166166
.custom("notifyRejected", .hash([
167-
"uuid": .array(completionUUIDs.map(JSONValue.string)),
167+
"uuids": .array(completionUUIDs.map(JSONValue.string)),
168168
]))
169169
}
170170
}

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,39 +95,53 @@ 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

94122
let existedLine = start.line < content.endIndex ? content[start.line] : nil
95123
let commonPrefix = longestCommonPrefix(of: suggestionContent, and: existedLine ?? "")
96124

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

105135
let toBeInserted = suggestionContent.breakLines(appendLineBreakToLastLine: true)
106136
if content.endIndex < start.line {
137+
extraInfo.modifications.append(.inserted(content.endIndex, toBeInserted))
107138
content.append(contentsOf: 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
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import XCTest
2+
3+
@testable import CopilotModel
4+
5+
final class ModificationTests: XCTestCase {
6+
func test_nsmutablearray_deleting_an_element() {
7+
let a = NSMutableArray(array: ["a", "b", "c"])
8+
a.apply([.deleted(0 ... 0)])
9+
XCTAssertEqual(a as! [String], ["b", "c"])
10+
}
11+
12+
func test_nsmutablearray_deleting_all_element() {
13+
let a = NSMutableArray(array: ["a", "b", "c"])
14+
a.apply([.deleted(0 ... 2)])
15+
XCTAssertEqual(a as! [String], [])
16+
}
17+
18+
func test_nsmutablearray_deleting_too_much_element() {
19+
let a = NSMutableArray(array: ["a", "b", "c"])
20+
a.apply([.deleted(0 ... 100)])
21+
XCTAssertEqual(a as! [String], [])
22+
}
23+
24+
func test_nsmutablearray_inserting_elements() {
25+
let a = NSMutableArray(array: ["a", "b", "c"])
26+
a.apply([.inserted(0, ["y", "z"])])
27+
XCTAssertEqual(a as! [String], ["y", "z", "a", "b", "c"])
28+
a.apply([.inserted(1, ["0", "1"])])
29+
XCTAssertEqual(a as! [String], ["y", "0", "1", "z", "a", "b", "c"])
30+
}
31+
32+
func test_nsmutablearray_inserting_elements_at_index_out_of_range() {
33+
let a = NSMutableArray(array: ["a", "b", "c"])
34+
a.apply([.inserted(1000, ["z"])])
35+
XCTAssertEqual(a as! [String], ["a", "b", "c", "z"])
36+
}
37+
}

0 commit comments

Comments
 (0)