@@ -417,46 +417,64 @@ class CodeCompletionSession {
417417 newText: String ,
418418 snapshot: DocumentSnapshot
419419 ) -> TextEdit {
420- let textEditRangeStart : Position
420+ let textEditRangeStart = computeCompletionTextEditStart (
421+ completionPos: completionPos,
422+ requestPosition: requestPosition,
423+ utf8CodeUnitsToErase: utf8CodeUnitsToErase,
424+ snapshot: snapshot
425+ )
426+ return TextEdit ( range: textEditRangeStart..< requestPosition, newText: newText)
427+ }
421428
429+ private func computeCompletionTextEditStart(
430+ completionPos: Position ,
431+ requestPosition: Position ,
432+ utf8CodeUnitsToErase: Int ,
433+ snapshot: DocumentSnapshot
434+ ) -> Position {
422435 // Compute the TextEdit
423436 if utf8CodeUnitsToErase == 0 {
424437 // Nothing to delete. Fast path and avoid UTF-8/UTF-16 conversions
425- textEditRangeStart = completionPos
438+ return completionPos
426439 } else if utf8CodeUnitsToErase == 1 {
427440 // Fast path: Erasing a single UTF-8 byte code unit means we are also need to erase exactly one UTF-16 code unit, meaning we don't need to process the file contents
428441 if completionPos. utf16index >= 1 {
429442 // We can delete the character.
430- textEditRangeStart = Position ( line: completionPos. line, utf16index: completionPos. utf16index - 1 )
431- } else {
432- // Deleting the character would cross line boundaries. This is not supported by LSP.
433- // Fall back to ignoring utf8CodeUnitsToErase.
434- // If we discover that multi-lines replacements are often needed, we can add an LSP extension to support multi-line edits.
435- textEditRangeStart = completionPos
436- }
437- } else {
438- // We need to delete more than one text character. Do the UTF-8/UTF-16 dance.
439- assert ( completionPos. line == requestPosition. line)
440- // Construct a string index for the edit range start by subtracting the UTF-8 code units to erase from the completion position.
441- let line = snapshot. lineTable [ completionPos. line]
442- let deletionStartStringIndex = line. utf8. index ( snapshot. index ( of: completionPos) , offsetBy: - utf8CodeUnitsToErase)
443-
444- // Compute the UTF-16 offset of the deletion start range. If the start lies in a previous line, this will be negative
445- let deletionStartUtf16Offset = line. utf16. distance ( from: line. startIndex, to: deletionStartStringIndex)
446-
447- // Check if we are only deleting on one line. LSP does not support deleting over multiple lines.
448- if deletionStartUtf16Offset >= 0 {
449- // We are only deleting characters on the same line. Construct the corresponding text edit.
450- textEditRangeStart = Position ( line: completionPos. line, utf16index: deletionStartUtf16Offset)
443+ return Position ( line: completionPos. line, utf16index: completionPos. utf16index - 1 )
451444 } else {
452445 // Deleting the character would cross line boundaries. This is not supported by LSP.
453446 // Fall back to ignoring utf8CodeUnitsToErase.
454447 // If we discover that multi-lines replacements are often needed, we can add an LSP extension to support multi-line edits.
455- textEditRangeStart = completionPos
448+ return completionPos
456449 }
457450 }
458451
459- return TextEdit ( range: textEditRangeStart..< requestPosition, newText: newText)
452+ // We need to delete more than one text character. Do the UTF-8/UTF-16 dance.
453+ assert ( completionPos. line == requestPosition. line)
454+ // Construct a string index for the edit range start by subtracting the UTF-8 code units to erase from the completion position.
455+ guard let line = snapshot. lineTable. line ( at: completionPos. line) else {
456+ logger. fault ( " Code completion position is in out-of-range line \( completionPos. line) " )
457+ return completionPos
458+ }
459+ guard
460+ let deletionStartStringIndex = line. utf8. index (
461+ snapshot. index ( of: completionPos) ,
462+ offsetBy: - utf8CodeUnitsToErase,
463+ limitedBy: line. utf8. startIndex
464+ )
465+ else {
466+ // Deleting the character would cross line boundaries. This is not supported by LSP.
467+ // Fall back to ignoring utf8CodeUnitsToErase.
468+ // If we discover that multi-lines replacements are often needed, we can add an LSP extension to support multi-line edits.
469+ logger. fault ( " UTF-8 code units to erase \( utf8CodeUnitsToErase) is before start of line " )
470+ return completionPos
471+ }
472+
473+ // Compute the UTF-16 offset of the deletion start range.
474+ let deletionStartUtf16Offset = line. utf16. distance ( from: line. startIndex, to: deletionStartStringIndex)
475+ precondition ( deletionStartUtf16Offset >= 0 )
476+
477+ return Position ( line: completionPos. line, utf16index: deletionStartUtf16Offset)
460478 }
461479}
462480
0 commit comments