11import Foundation
22import SuggestionModel
33
4- let suggestionStart = " /*========== Copilot Suggestion"
5- let suggestionEnd = "*///======== End of Copilot Suggestion "
6-
74// NOTE: Every lines from Xcode Extension has a line break at its end, even the last line.
85// NOTE: Copilot's completion always start at character 0, no matter where the cursor is.
96
@@ -18,116 +15,6 @@ public struct SuggestionInjector {
1815 public init ( ) { }
1916 }
2017
21- public func rejectCurrentSuggestions(
22- from content: inout [ String ] ,
23- cursorPosition: inout CursorPosition ,
24- extraInfo: inout ExtraInfo
25- ) {
26- var ranges = [ ClosedRange < Int > ] ( )
27- var suggestionStartIndex = - 1
28-
29- // find ranges of suggestion comments
30- for (index, line) in content. enumerated ( ) {
31- if line. hasPrefix ( suggestionStart) {
32- suggestionStartIndex = index
33- }
34- if suggestionStartIndex >= 0 , line. hasPrefix ( suggestionEnd) {
35- ranges. append ( . init( uncheckedBounds: ( suggestionStartIndex, index) ) )
36- suggestionStartIndex = - 1
37- }
38- }
39-
40- let reversedRanges = ranges. reversed ( )
41-
42- extraInfo. modifications. append ( contentsOf: reversedRanges. map ( Modification . deleted) )
43- extraInfo. didChangeContent = !ranges. isEmpty
44-
45- // remove the lines from bottom to top
46- for range in reversedRanges {
47- for i in stride ( from: range. upperBound, through: range. lowerBound, by: - 1 ) {
48- if i <= cursorPosition. line, cursorPosition. line >= 0 {
49- cursorPosition = . init(
50- line: cursorPosition. line - 1 ,
51- character: i == cursorPosition. line ? 0 : cursorPosition. character
52- )
53- extraInfo. didChangeCursorPosition = true
54- }
55- content. remove ( at: i)
56- }
57- }
58-
59- extraInfo. suggestionRange = nil
60- }
61-
62- public func proposeSuggestion(
63- intoContentWithoutSuggestion content: inout [ String ] ,
64- completion: CodeSuggestion ,
65- index: Int ,
66- count: Int ,
67- extraInfo: inout ExtraInfo
68- ) {
69- // assemble suggestion comment
70- let start = completion. range. start
71- let startText = " \( suggestionStart) \( index + 1 ) / \( count) "
72- var lines = [ startText + " \n " ]
73- lines. append ( contentsOf: completion. text. breakLines ( appendLineBreakToLastLine: true ) )
74- lines. append ( suggestionEnd + " \n " )
75-
76- // if suggestion is empty, returns without modifying the code
77- guard lines. count > 2 else { return }
78-
79- // replace the common prefix of the first line with space and carrot
80- let existedLine = start. line < content. endIndex ? content [ start. line] : nil
81- let commonPrefix = longestCommonPrefix ( of: lines [ 1 ] , and: existedLine ?? " " )
82-
83- if !commonPrefix. isEmpty {
84- let replacingText = {
85- switch ( commonPrefix. hasSuffix ( " \n " ) , commonPrefix. count) {
86- case ( false , let count) :
87- return String ( repeating: " " , count: count - 1 ) + " ^ "
88- case ( true , let count) where count > 1 :
89- return String ( repeating: " " , count: count - 2 ) + " ^ \n "
90- case ( true , _) :
91- return " \n "
92- }
93- } ( )
94-
95- lines [ 1 ] . replaceSubrange (
96- lines [ 1 ] . startIndex..< (
97- lines [ 1 ] . index (
98- lines [ 1 ] . startIndex,
99- offsetBy: commonPrefix. count,
100- limitedBy: lines [ 1 ] . endIndex
101- ) ?? lines [ 1 ] . endIndex
102- ) ,
103- with: replacingText
104- )
105- }
106-
107- // if the suggestion is only appending new lines and spaces, return without modification
108- if completion. text. dropFirst ( commonPrefix. count)
109- . allSatisfy ( { $0. isWhitespace || $0. isNewline } ) { return }
110-
111- // determine if it's inserted to the current line or the next line
112- let lineIndex = start. line + {
113- guard let existedLine else { return 0 }
114- if existedLine. isEmptyOrNewLine { return 1 }
115- if commonPrefix. isEmpty { return 0 }
116- return 1
117- } ( )
118- if content. endIndex < lineIndex {
119- extraInfo. didChangeContent = true
120- extraInfo. suggestionRange = content. endIndex... content. endIndex + lines. count - 1
121- extraInfo. modifications. append ( . inserted( content. endIndex, lines) )
122- content. append ( contentsOf: lines)
123- } else {
124- extraInfo. didChangeContent = true
125- extraInfo. suggestionRange = lineIndex... lineIndex + lines. count - 1
126- extraInfo. modifications. append ( . inserted( lineIndex, lines) )
127- content. insert ( contentsOf: lines, at: lineIndex)
128- }
129- }
130-
13118 public func acceptSuggestion(
13219 intoContentWithoutSuggestion content: inout [ String ] ,
13320 cursorPosition: inout CursorPosition ,
@@ -140,6 +27,11 @@ public struct SuggestionInjector {
14027 let start = completion. range. start
14128 let end = completion. range. end
14229 let suggestionContent = completion. text
30+ let lineEnding = if let ending = content. first? . last, ending. isNewline {
31+ String ( ending)
32+ } else {
33+ " \n "
34+ }
14335
14436 let firstRemovedLine = content [ safe: start. line]
14537 let lastRemovedLine = content [ safe: end. line]
@@ -150,7 +42,10 @@ public struct SuggestionInjector {
15042 content. removeSubrange ( startLine... endLine)
15143 }
15244
153- var toBeInserted = suggestionContent. breakLines ( appendLineBreakToLastLine: true )
45+ var toBeInserted = suggestionContent. breakLines (
46+ proposedLineEnding: lineEnding,
47+ appendLineBreakToLastLine: true
48+ )
15449
15550 // prepending prefix text not in range if needed.
15651 if let firstRemovedLine,
@@ -165,7 +60,7 @@ public struct SuggestionInjector {
16560 limitedBy: firstRemovedLine. endIndex
16661 ) ?? firstRemovedLine. endIndex)
16762 var leftover = firstRemovedLine [ leftoverRange]
168- if leftover. hasSuffix ( " \n " ) {
63+ if leftover. last ? . isNewline ?? false {
16964 leftover. removeLast ( 1 )
17065 }
17166 toBeInserted [ 0 ] . insert (
@@ -177,7 +72,8 @@ public struct SuggestionInjector {
17772 let recoveredSuffixLength = recoverSuffixIfNeeded (
17873 endOfReplacedContent: end,
17974 toBeInserted: & toBeInserted,
180- lastRemovedLine: lastRemovedLine
75+ lastRemovedLine: lastRemovedLine,
76+ lineEnding: lineEnding
18177 )
18278
18379 let cursorCol = toBeInserted [ toBeInserted. endIndex - 1 ] . count - 1 - recoveredSuffixLength
@@ -193,7 +89,8 @@ public struct SuggestionInjector {
19389 func recoverSuffixIfNeeded(
19490 endOfReplacedContent end: CursorPosition ,
19591 toBeInserted: inout [ String ] ,
196- lastRemovedLine: String ?
92+ lastRemovedLine: String ? ,
93+ lineEnding: String
19794 ) -> Int {
19895 // If there is no line removed, there is no need to recover anything.
19996 guard let lastRemovedLine, !lastRemovedLine. isEmptyOrNewLine else { return 0 }
@@ -255,7 +152,7 @@ public struct SuggestionInjector {
255152 let lastInsertingLine = toBeInserted [ toBeInserted. endIndex - 1 ]
256153 . droppedLineBreak ( )
257154 . appending ( suffix)
258- . recoveredLineBreak ( )
155+ . recoveredLineBreak ( lineEnding : lineEnding )
259156
260157 toBeInserted [ toBeInserted. endIndex - 1 ] = lastInsertingLine
261158
@@ -280,36 +177,22 @@ public struct SuggestionAnalyzer {
280177}
281178
282179extension String {
283- /// Break a string into lines.
284- func breakLines( appendLineBreakToLastLine: Bool = false ) -> [ String ] {
285- let lines = split ( separator: " \n " , omittingEmptySubsequences: false )
286- var all = [ String] ( )
287- for (index, line) in lines. enumerated ( ) {
288- if !appendLineBreakToLastLine, index == lines. endIndex - 1 {
289- all. append ( String ( line) )
290- } else {
291- all. append ( String ( line) + " \n " )
292- }
293- }
294- return all
295- }
296-
297180 var isEmptyOrNewLine : Bool {
298- isEmpty || self == " \n "
181+ isEmpty || self == " \n " || self == " \r \n " || self == " \r "
299182 }
300183
301184 func droppedLineBreak( ) -> String {
302- if hasSuffix ( " \n " ) {
185+ if last ? . isNewline ?? false {
303186 return String ( dropLast ( 1 ) )
304187 }
305188 return self
306189 }
307190
308- func recoveredLineBreak( ) -> String {
309- if hasSuffix ( " \n " ) {
191+ func recoveredLineBreak( lineEnding : String ) -> String {
192+ if hasSuffix ( lineEnding ) {
310193 return self
311194 }
312- return self + " \n "
195+ return self + lineEnding
313196 }
314197}
315198
0 commit comments