|
11 | 11 | //===----------------------------------------------------------------------===//
|
12 | 12 |
|
13 | 13 | import Foundation
|
| 14 | +import LSPLogging |
14 | 15 | import LanguageServerProtocol
|
| 16 | +import SwiftParser |
| 17 | +import SwiftSyntax |
15 | 18 |
|
16 | 19 | import struct TSCBasic.AbsolutePath
|
17 | 20 | import class TSCBasic.Process
|
@@ -97,74 +100,38 @@ extension CollectionDifference.Change {
|
97 | 100 |
|
98 | 101 | /// Compute the text edits that need to be made to transform `original` into `edited`.
|
99 | 102 | private func edits(from original: DocumentSnapshot, to edited: String) -> [TextEdit] {
|
100 |
| - let difference = edited.difference(from: original.text) |
101 |
| - |
102 |
| - // `Collection.difference` returns sequential edits that are expected to be applied on-by-one. Offsets reference |
103 |
| - // the string that results if all previous edits are applied. |
104 |
| - // LSP expects concurrent edits that are applied simultaneously. Translate between them. |
105 |
| - |
106 |
| - struct StringBasedEdit { |
107 |
| - /// Offset into the collection originalString. |
108 |
| - /// Ie. to get a string index out of this, run `original(original.startIndex, offsetBy: range.lowerBound)`. |
109 |
| - var range: Range<Int> |
110 |
| - /// The string the range is being replaced with. |
111 |
| - var replacement: String |
112 |
| - } |
| 103 | + let difference = edited.utf8.difference(from: original.text.utf8) |
113 | 104 |
|
114 |
| - var edits: [StringBasedEdit] = [] |
115 |
| - for change in difference { |
116 |
| - // Adjust the index offset based on changes that `Collection.difference` expects to already have been applied. |
117 |
| - var adjustment: Int = 0 |
118 |
| - for edit in edits { |
119 |
| - if edit.range.upperBound < change.offset + adjustment { |
120 |
| - adjustment = adjustment + edit.range.count - edit.replacement.count |
121 |
| - } |
122 |
| - } |
123 |
| - let adjustedOffset = change.offset + adjustment |
124 |
| - let edit = |
125 |
| - switch change { |
126 |
| - case .insert(offset: _, element: let element, associatedWith: _): |
127 |
| - StringBasedEdit(range: adjustedOffset..<adjustedOffset, replacement: String(element)) |
128 |
| - case .remove(offset: _, element: _, associatedWith: _): |
129 |
| - StringBasedEdit(range: adjustedOffset..<(adjustedOffset + 1), replacement: "") |
130 |
| - } |
131 |
| - |
132 |
| - // If we have an existing edit that is adjacent to this one, merge them. |
133 |
| - // Otherwise, just append them. |
134 |
| - if let mergableEditIndex = edits.firstIndex(where: { |
135 |
| - $0.range.upperBound == edit.range.lowerBound || edit.range.upperBound == $0.range.lowerBound |
136 |
| - }) { |
137 |
| - let mergableEdit = edits[mergableEditIndex] |
138 |
| - if mergableEdit.range.upperBound == edit.range.lowerBound { |
139 |
| - edits[mergableEditIndex] = StringBasedEdit( |
140 |
| - range: mergableEdit.range.lowerBound..<edit.range.upperBound, |
141 |
| - replacement: mergableEdit.replacement + edit.replacement |
142 |
| - ) |
143 |
| - } else { |
144 |
| - precondition(edit.range.upperBound == mergableEdit.range.lowerBound) |
145 |
| - edits[mergableEditIndex] = StringBasedEdit( |
146 |
| - range: edit.range.lowerBound..<mergableEdit.range.upperBound, |
147 |
| - replacement: edit.replacement + mergableEdit.replacement |
148 |
| - ) |
149 |
| - } |
150 |
| - } else { |
151 |
| - edits.append(edit) |
| 105 | + let sequentialEdits = difference.map { change in |
| 106 | + switch change { |
| 107 | + case .insert(offset: let offset, element: let element, associatedWith: _): |
| 108 | + IncrementalEdit(offset: offset, length: 0, replacement: [element]) |
| 109 | + case .remove(offset: let offset, element: _, associatedWith: _): |
| 110 | + IncrementalEdit(offset: offset, length: 1, replacement: []) |
152 | 111 | }
|
153 | 112 | }
|
154 | 113 |
|
155 |
| - // Map the string-based edits to line-column based edits to be consumed by LSP |
| 114 | + let concurrentEdits = ConcurrentEdits(fromSequential: sequentialEdits) |
156 | 115 |
|
157 |
| - return edits.map { edit in |
158 |
| - let (startLine, startColumn) = original.lineTable.lineAndUTF16ColumnOf( |
159 |
| - original.text.index(original.text.startIndex, offsetBy: edit.range.lowerBound) |
160 |
| - ) |
161 |
| - let (endLine, endColumn) = original.lineTable.lineAndUTF16ColumnOf( |
162 |
| - original.text.index(original.text.startIndex, offsetBy: edit.range.upperBound) |
163 |
| - ) |
| 116 | + // Map the offset-based edits to line-column based edits to be consumed by LSP |
| 117 | + |
| 118 | + return concurrentEdits.edits.compactMap { (edit) -> TextEdit? in |
| 119 | + guard let (startLine, startColumn) = original.lineTable.lineAndUTF16ColumnOf(utf8Offset: edit.offset) else { |
| 120 | + logger.fault("Failed to convert offset \(edit.offset) into line:column") |
| 121 | + return nil |
| 122 | + } |
| 123 | + guard let (endLine, endColumn) = original.lineTable.lineAndUTF16ColumnOf(utf8Offset: edit.endOffset) else { |
| 124 | + logger.fault("Failed to convert offset \(edit.endOffset) into line:column") |
| 125 | + return nil |
| 126 | + } |
| 127 | + guard let newText = String(bytes: edit.replacement, encoding: .utf8) else { |
| 128 | + logger.fault("Failed to get String from UTF-8 bytes \(edit.replacement)") |
| 129 | + return nil |
| 130 | + } |
164 | 131 |
|
165 | 132 | return TextEdit(
|
166 | 133 | range: Position(line: startLine, utf16index: startColumn)..<Position(line: endLine, utf16index: endColumn),
|
167 |
| - newText: edit.replacement |
| 134 | + newText: newText |
168 | 135 | )
|
169 | 136 | }
|
170 | 137 | }
|
|
0 commit comments