OLD | NEW |
(Empty) | |
| 1 /** |
| 2 * @constructor |
| 3 * @param {!Map.<string, string>} sources |
| 4 * @param {!WebInspector.SourceMapEditorFlavor} flavor |
| 5 */ |
| 6 WebInspector.SourceMapEditor = function(sources, flavor) |
| 7 { |
| 8 this._sources = sources; |
| 9 this._editFlavor = flavor; |
| 10 } |
| 11 /** |
| 12 * @param {string} sourceURL |
| 13 * @param {string} contentV1 |
| 14 * @param {string} contentV2 |
| 15 * @return {!Array.<!WebInspector.SourceMapEdit>} |
| 16 */ |
| 17 WebInspector.SourceMapEditor.computeEdits = function(sourceURL, contentV1, conte
ntV2) |
| 18 { |
| 19 var differ = new diff_match_patch(); |
| 20 var diff = differ.diff_main(contentV1, contentV2); |
| 21 differ.diff_cleanupEfficiency(diff); |
| 22 var oldSourceOffset = 0; |
| 23 var edits = []; |
| 24 var i = 0; |
| 25 while (i < diff.length) { |
| 26 var token = diff[i]; |
| 27 if (token[0] === 0) { |
| 28 oldSourceOffset += token[1].length; |
| 29 ++i; |
| 30 continue; |
| 31 } |
| 32 |
| 33 var nextToken = diff[i + 1]; |
| 34 if (nextToken && nextToken[0] === -token[0]) { |
| 35 // If this is an EDIT command. |
| 36 var add, remove; |
| 37 if (token[0] === 1) { |
| 38 add = token; |
| 39 remove = nextToken; |
| 40 } else { |
| 41 add = nextToken; |
| 42 remove = token; |
| 43 } |
| 44 var sourceRange = new WebInspector.SourceRange(oldSourceOffset, remo
ve[1].length); |
| 45 var oldRange = WebInspector.TextRange.createFromSourceRange(contentV
1, sourceRange); |
| 46 edits.push(new WebInspector.SourceMapEdit(sourceURL, oldRange, remov
e[1], add[1])); |
| 47 oldSourceOffset += remove[1].length; |
| 48 i += 2; |
| 49 } else if (token[0] === 1) { |
| 50 // If this is an ADD command. |
| 51 var sourceRange = new WebInspector.SourceRange(oldSourceOffset, 0); |
| 52 var oldRange = WebInspector.TextRange.createFromSourceRange(contentV
1, sourceRange); |
| 53 edits.push(new WebInspector.SourceMapEdit(sourceURL, oldRange, "", t
oken[1])); |
| 54 i += 1; |
| 55 } else { |
| 56 // If this is a REMOVE command. |
| 57 var sourceRange = new WebInspector.SourceRange(oldSourceOffset, toke
n[1].length); |
| 58 var oldRange = WebInspector.TextRange.createFromSourceRange(contentV
1, sourceRange); |
| 59 edits.push(new WebInspector.SourceMapEdit(sourceURL, oldRange, token
[1], "")); |
| 60 oldSourceOffset += token[1].length; |
| 61 i += 1; |
| 62 } |
| 63 } |
| 64 return edits; |
| 65 } |
| 66 |
| 67 WebInspector.SourceMapEditor.prototype = { |
| 68 /** |
| 69 * @param {!WebInspector.SourceMap} map |
| 70 * @param {!Array<!WebInspector.SourceMapEdit>} edits |
| 71 * @return {!Set<string, string>} |
| 72 */ |
| 73 handleCompiledEdits: function(map, edits) |
| 74 { |
| 75 var backEditInfos = []; |
| 76 for (var i = 0; i < edits.length; ++i) |
| 77 backEditInfos.push(this._doCompiledEdit(map, edits[i])); |
| 78 |
| 79 var didBackEdits = false; |
| 80 for (var i = 0; i < edits.length; ++i) |
| 81 didBackEdits = this._doBackEdits(map, edits[i], backEditInfos[i]) ||
didBackEdits; |
| 82 |
| 83 var result = new Set(); |
| 84 if (didBackEdits) |
| 85 result.add(edits[0].sourceURL); |
| 86 for (var i = 0; i < backEditInfos.length; ++i) { |
| 87 var sourceURL = backEditInfos[i].compiledBaseMapping.sourceURL; |
| 88 result.add(sourceURL); |
| 89 } |
| 90 return result; |
| 91 }, |
| 92 |
| 93 /** |
| 94 * @param {!WebInspector.SourceMap} map |
| 95 * @param {!WebInspector.SourceMapEdit} edit |
| 96 * @return {!WebInspector.SourceMapEditor.BackEditInfo} |
| 97 */ |
| 98 _doCompiledEdit: function(map, edit) |
| 99 { |
| 100 var startMapping = map.findEntry(edit.oldRange.startLine, edit.oldRange.
startColumn); |
| 101 var endMapping = map.findEntry(edit.oldRange.endLine, edit.oldRange.endC
olumn); |
| 102 if (startMapping.sourceURL !== endMapping.sourceURL) |
| 103 throw new Error("An edit spans multiple source files."); |
| 104 |
| 105 var sourceURL = startMapping.sourceURL; |
| 106 var compiledText = this._sources.get(edit.sourceURL); |
| 107 var sourceText = this._sources.get(sourceURL); |
| 108 |
| 109 // Return if we don't have source for mapped entries. |
| 110 if (!compiledText || !sourceText) |
| 111 throw new Error("Source of " + sourceURL + " is missing"); |
| 112 |
| 113 var startLocation = this._mapCompiledLocation(startMapping, edit.oldRang
e.startLine, edit.oldRange.startColumn); |
| 114 var endLocation = this._mapCompiledLocation(endMapping, edit.oldRange.en
dLine, edit.oldRange.endColumn); |
| 115 var sourceOldRange = new WebInspector.TextRange(startLocation.line, star
tLocation.column, endLocation.line, endLocation.column); |
| 116 var sourceOldText = sourceOldRange.extract(sourceText); |
| 117 // If the text in compiled and original differs, then we cannot edit it. |
| 118 var sourceNewText = this._editFlavor.sourceEditText(edit.oldText, source
OldText, edit.newText) |
| 119 if (sourceNewText === null) |
| 120 throw new Error("The edited range is not equal in origin and source"
); |
| 121 |
| 122 var newTextLineCount = sourceNewText.lineEndings().length; |
| 123 if (newTextLineCount > 1) |
| 124 throw new Error("Not implemented for multi-line edit"); |
| 125 |
| 126 if (!map.compiledRangeEdited(edit.oldRange, edit.newRange())) |
| 127 throw new Error("Failed to update compiled locations in sourcemap.")
; |
| 128 |
| 129 var sourceEdit = new WebInspector.SourceMapEdit(sourceURL, sourceOldRang
e, sourceOldText, sourceNewText); |
| 130 if (!map.sourceRangeEdited(sourceURL, sourceEdit.oldRange, sourceEdit.ne
wRange())) |
| 131 throw new Error("Failed to update source locations in sourcemap."); |
| 132 |
| 133 this._sources.set(sourceURL, sourceEdit.applyToText(sourceText)); |
| 134 |
| 135 var offsetRange = edit.oldRange.relativeTo(startMapping.lineNumber, star
tMapping.columnNumber); |
| 136 var sourceNewRange = sourceEdit.newRange(); |
| 137 var reversedMappings = map.findEntriesReversed(sourceEdit.sourceURL, sou
rceNewRange.startLine, sourceNewRange.startColumn + 1); |
| 138 return new WebInspector.SourceMapEditor.BackEditInfo(startMapping, offse
tRange, reversedMappings); |
| 139 }, |
| 140 |
| 141 _doBackEdits: function(map, compiledEdit, backEditInfo) |
| 142 { |
| 143 // There is no multimapping from source position to compiled position. |
| 144 if (backEditInfo.reversedMappings.length < 2) |
| 145 return false; |
| 146 var didEdit = false; |
| 147 var compiledText = this._sources.get(compiledEdit.sourceURL); |
| 148 for (var i = 0; i < backEditInfo.reversedMappings.length; ++i) { |
| 149 var mapping = backEditInfo.reversedMappings[i]; |
| 150 if (mapping === backEditInfo.compiledBaseMapping) |
| 151 continue; |
| 152 var oldEditRange = rebaseOffsetRange(backEditInfo.offsetRange, mappi
ng); |
| 153 var reverseEdit = new WebInspector.SourceMapEdit(compiledEdit.source
URL, oldEditRange, oldEditRange.extract(compiledText), compiledEdit.newText); |
| 154 if (reverseEdit.oldText !== compiledEdit.oldText) { |
| 155 console.error("compiled texts differ."); |
| 156 continue; |
| 157 } |
| 158 if (!map.compiledRangeEdited(reverseEdit.oldRange, reverseEdit.newRa
nge())) |
| 159 throw new Error("Failed to update reversed compiled locations in
sourcemap."); |
| 160 compiledText = reverseEdit.applyToText(compiledText); |
| 161 didEdit = true; |
| 162 } |
| 163 if (didEdit) |
| 164 this._sources.set(compiledEdit.sourceURL, compiledText); |
| 165 |
| 166 return didEdit; |
| 167 |
| 168 function rebaseOffsetRange(range, mapping) |
| 169 { |
| 170 return new WebInspector.TextRange( |
| 171 range.startLine + mapping.lineNumber, |
| 172 range.startColumn + mapping.columnNumber, |
| 173 range.endLine + mapping.lineNumber, |
| 174 range.endColumn + mapping.columnNumber |
| 175 ); |
| 176 } |
| 177 }, |
| 178 |
| 179 /** |
| 180 * @param {!WebInspector.SourceMap.Entry} entry |
| 181 * @param {number} lineNumber |
| 182 * @param {number} columnNumber |
| 183 * @return {!{line: number, column: number}} |
| 184 */ |
| 185 _mapCompiledLocation: function(entry, lineNumber, columnNumber) |
| 186 { |
| 187 var lineOffset = lineNumber - entry.lineNumber; |
| 188 var columnOffset = columnNumber - entry.columnNumber; |
| 189 return {line: entry.sourceLineNumber + lineOffset, column: entry.sourceC
olumnNumber + columnOffset}; |
| 190 } |
| 191 } |
| 192 |
| 193 /** |
| 194 * @constructor |
| 195 * @param {string} sourceURL |
| 196 * @param {!WebInspector.TextRange} oldRange |
| 197 * @param {string} oldText |
| 198 * @param {string} newText |
| 199 */ |
| 200 WebInspector.SourceMapEdit = function(sourceURL, oldRange, oldText, newText) |
| 201 { |
| 202 this.sourceURL = sourceURL; |
| 203 this.oldRange = oldRange; |
| 204 this.oldText = oldText; |
| 205 this.newText = newText; |
| 206 } |
| 207 |
| 208 WebInspector.SourceMapEdit.prototype = { |
| 209 /** |
| 210 * @return {!WebInspector.TextRange} |
| 211 */ |
| 212 newRange: function() |
| 213 { |
| 214 var endLine = this.oldRange.startLine; |
| 215 var endColumn = this.oldRange.startColumn + this.newText.length; |
| 216 var lineEndings = this.newText.lineEndings(); |
| 217 if (lineEndings.length > 1) { |
| 218 endLine = this.oldRange.startLine + lineEndings.length - 1; |
| 219 var len = lineEndings.length; |
| 220 endColumn = lineEndings[len - 1] - lineEndings[len - 2] - 1; |
| 221 } |
| 222 return new WebInspector.TextRange( |
| 223 this.oldRange.startLine, |
| 224 this.oldRange.startColumn, |
| 225 endLine, |
| 226 endColumn); |
| 227 }, |
| 228 |
| 229 applyToText: function(text) |
| 230 { |
| 231 return this.oldRange.replaceInText(text, this.newText); |
| 232 }, |
| 233 } |
| 234 |
| 235 WebInspector.SourceMapEditor.BackEditInfo = function(compiledBaseMapping, offset
Range, reversedMappings) |
| 236 { |
| 237 this.compiledBaseMapping = compiledBaseMapping; |
| 238 this.offsetRange = offsetRange; |
| 239 this.reversedMappings = reversedMappings; |
| 240 } |
| 241 |
| 242 /** |
| 243 * @interface |
| 244 */ |
| 245 WebInspector.SourceMapEditorFlavor = function() { } |
| 246 |
| 247 WebInspector.SourceMapEditorFlavor.prototype = { |
| 248 /** |
| 249 * @param {string} compiledURL |
| 250 * @param {string} compiledText |
| 251 * @param {!WebInspector.TextRange} compiledRange |
| 252 * @param {string} sourceURL |
| 253 * @param {string} sourceText |
| 254 * @param {!WebInspector.TextRange} sourceRange |
| 255 * @return {?string} |
| 256 */ |
| 257 sourceEditText: function(compiledURL, compiledText, compiledRange, sourceTex
t, sourceURL, sourceRange) |
| 258 { |
| 259 }, |
| 260 } |
OLD | NEW |