@@ -174,97 +174,77 @@ public struct SuggestionInjector {
174174 )
175175 }
176176
177- #warning(
178- " TODO: I feel like the implementation is doing a lot of receptive work to recover suffix. "
177+ let recoveredSuffixLength = recoverSuffixIfNeeded (
178+ endOfReplacedContent: end,
179+ toBeInserted: & toBeInserted,
180+ lastRemovedLine: lastRemovedLine
179181 )
180182
181- /// The number of character that is in the last modified line but not included.
182- let leftoverCount : Int = {
183- let maxCount = lastRemovedLine? . count ?? 0
184- guard let first = toBeInserted. first?
185- . dropLast ( ( toBeInserted. first? . hasSuffix ( " \n " ) ?? false ) ? 1 : 0 ) ,
186- !first. isEmpty else { return maxCount }
187- guard let last = toBeInserted. last?
188- . dropLast ( ( toBeInserted. last? . hasSuffix ( " \n " ) ?? false ) ? 1 : 0 ) ,
189- !last. isEmpty else { return maxCount }
190- let droppedLast = lastRemovedLine?
191- . dropLast ( ( lastRemovedLine? . hasSuffix ( " \n " ) ?? false ) ? 1 : 0 )
192- guard let droppedLast, !droppedLast. isEmpty else { return maxCount }
193- // case 1: user keeps typing as the suggestion suggests.
194- if first. hasPrefix ( droppedLast) {
195- return 0
196- }
197- // case 2: user also typed the suffix of the suggestion (or auto-completed by Xcode)
198- if end. character < droppedLast. count {
199- // locate the split index, the prefix of which matches the suggestion prefix.
200- var splitIndex : String . Index ?
201- for offset in end. character..< droppedLast. count {
202- let proposedIndex = droppedLast. index (
203- droppedLast. startIndex,
204- offsetBy: offset
205- )
206- let prefix = droppedLast [ ..< proposedIndex]
207- if first. hasPrefix ( prefix) {
208- splitIndex = proposedIndex
209- }
210- }
183+ let cursorCol = toBeInserted [ toBeInserted. endIndex - 1 ] . count - 1 - recoveredSuffixLength
184+ let insertingIndex = min ( start. line, content. endIndex)
185+ content. insert ( contentsOf: toBeInserted, at: insertingIndex)
186+ extraInfo. modifications. append ( . inserted( insertingIndex, toBeInserted) )
187+ cursorPosition = . init(
188+ line: startLine + toBeInserted. count - 1 ,
189+ character: max ( 0 , cursorCol)
190+ )
191+ }
211192
212- // then check how many characters are not in the suffix of the suggestion.
213- if let splitIndex {
214- let suffix = droppedLast [ splitIndex... ]
215- if last. hasSuffix ( suffix) {
216- return 0
217- }
218- return suffix. count
219- }
220- }
193+ func recoverSuffixIfNeeded(
194+ endOfReplacedContent end: CursorPosition ,
195+ toBeInserted: inout [ String ] ,
196+ lastRemovedLine: String ?
197+ ) -> Int {
198+ // If there is no line removed, there is no need to recover anything.
199+ guard let lastRemovedLine, !lastRemovedLine. isEmptyOrNewLine else { return 0 }
221200
222- return maxCount
223- } ( )
201+ let lastRemovedLineCleaned = lastRemovedLine. droppedLineBreak ( )
224202
225- /// The number of characters appended to the last line.
226- var appenderCount = 0
203+ // If the replaced range covers the whole line, return immediately .
204+ guard end . character > = 0 , end . character - 1 < lastRemovedLineCleaned . count else { return 0 }
227205
228- // appending suffix text not in range if needed.
229- if let lastRemovedLine,
230- leftoverCount > 0 ,
231- !lastRemovedLine. isEmptyOrNewLine,
232- end. character >= 0 ,
233- end. character - 1 < lastRemovedLine. count,
234- !toBeInserted. isEmpty
235- {
236- let leftoverRange = ( lastRemovedLine. index (
237- lastRemovedLine. startIndex,
238- offsetBy: end. character,
239- limitedBy: lastRemovedLine. endIndex
240- ) ?? lastRemovedLine. endIndex) ..< lastRemovedLine. endIndex
241- if toBeInserted [ toBeInserted. endIndex - 1 ] . hasSuffix ( " \n " ) {
242- toBeInserted [ toBeInserted. endIndex - 1 ] . removeLast ( 1 )
243- }
244- let leftover = {
245- if lastRemovedLine. hasSuffix ( " \n " ) {
246- return lastRemovedLine [ leftoverRange] . dropLast ( 1 ) . suffix ( leftoverCount)
247- }
248- return lastRemovedLine [ leftoverRange] . suffix ( leftoverCount)
249- } ( )
206+ // if we are not inserting anything, return immediately.
207+ guard !toBeInserted. isEmpty,
208+ let first = toBeInserted. first? . droppedLineBreak ( ) , !first. isEmpty,
209+ let last = toBeInserted. last? . droppedLineBreak ( ) , !last. isEmpty
210+ else { return 0 }
250211
251- toBeInserted [ toBeInserted . endIndex - 1 ] . append ( contentsOf : leftover )
212+ // case 1: user keeps typing as the suggestion suggests.
252213
253- appenderCount = leftover. count
214+ if first. hasPrefix ( lastRemovedLineCleaned) {
215+ return 0
216+ }
254217
255- if !toBeInserted[ toBeInserted. endIndex - 1 ] . hasSuffix ( " \n " ) {
256- toBeInserted [ toBeInserted. endIndex - 1 ] . append ( " \n " )
218+ // case 2: user also typed the suffix of the suggestion (or auto-completed by Xcode)
219+
220+ // locate the split index, the prefix of which matches the suggestion prefix.
221+ var splitIndex : String . Index ?
222+
223+ for offset in end. character..< lastRemovedLineCleaned. count {
224+ let proposedIndex = lastRemovedLineCleaned. index (
225+ lastRemovedLineCleaned. startIndex,
226+ offsetBy: offset
227+ )
228+ let prefix = lastRemovedLineCleaned [ ..< proposedIndex]
229+ if first. hasPrefix ( prefix) {
230+ splitIndex = proposedIndex
257231 }
258232 }
259233
260- let cursorCol = toBeInserted [ toBeInserted. endIndex - 1 ] . count - 1 - appenderCount
261- let insertingIndex = min ( start. line, content. endIndex)
262- content. insert ( contentsOf: toBeInserted, at: insertingIndex)
263- extraInfo. modifications. append ( . inserted( insertingIndex, toBeInserted) )
264- cursorPosition = . init(
265- line: startLine + toBeInserted. count - 1 ,
266- character: max ( 0 , cursorCol)
267- )
234+ // then check how many characters are not in the suffix of the suggestion.
235+ guard let splitIndex else { return 0 }
236+
237+ let suffix = lastRemovedLineCleaned [ splitIndex... ]
238+ if last. hasSuffix ( suffix) { return 0 }
239+
240+ let lastInsertingLine = toBeInserted [ toBeInserted. endIndex - 1 ]
241+ . droppedLineBreak ( )
242+ . appending ( suffix)
243+ . recoveredLineBreak ( )
244+
245+ toBeInserted [ toBeInserted. endIndex - 1 ] = lastInsertingLine
246+
247+ return suffix. count
268248 }
269249}
270250
@@ -302,6 +282,20 @@ extension String {
302282 var isEmptyOrNewLine : Bool {
303283 isEmpty || self == " \n "
304284 }
285+
286+ func droppedLineBreak( ) -> String {
287+ if hasSuffix ( " \n " ) {
288+ return String ( dropLast ( 1 ) )
289+ }
290+ return self
291+ }
292+
293+ func recoveredLineBreak( ) -> String {
294+ if hasSuffix ( " \n " ) {
295+ return self
296+ }
297+ return self + " \n "
298+ }
305299}
306300
307301func longestCommonPrefix( of a: String , and b: String ) -> String {
0 commit comments