Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1503)

Unified Diff: Source/devtools/front_end/bindings/SourceMapEditor.js

Issue 1307063005: DevTools: edit SASS through SourceMaps. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: improvements Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: Source/devtools/front_end/bindings/SourceMapEditor.js
diff --git a/Source/devtools/front_end/bindings/SourceMapEditor.js b/Source/devtools/front_end/bindings/SourceMapEditor.js
new file mode 100644
index 0000000000000000000000000000000000000000..fd1e0fd8a30b24af6c7634493e4c36b29f51b0a3
--- /dev/null
+++ b/Source/devtools/front_end/bindings/SourceMapEditor.js
@@ -0,0 +1,260 @@
+/**
+ * @constructor
+ * @param {!Map.<string, string>} sources
+ * @param {!WebInspector.SourceMapEditorFlavor} flavor
+ */
+WebInspector.SourceMapEditor = function(sources, flavor)
+{
+ this._sources = sources;
+ this._editFlavor = flavor;
+}
+/**
+ * @param {string} sourceURL
+ * @param {string} contentV1
+ * @param {string} contentV2
+ * @return {!Array.<!WebInspector.SourceMapEdit>}
+ */
+WebInspector.SourceMapEditor.computeEdits = function(sourceURL, contentV1, contentV2)
+{
+ var differ = new diff_match_patch();
+ var diff = differ.diff_main(contentV1, contentV2);
+ differ.diff_cleanupEfficiency(diff);
+ var oldSourceOffset = 0;
+ var edits = [];
+ var i = 0;
+ while (i < diff.length) {
+ var token = diff[i];
+ if (token[0] === 0) {
+ oldSourceOffset += token[1].length;
+ ++i;
+ continue;
+ }
+
+ var nextToken = diff[i + 1];
+ if (nextToken && nextToken[0] === -token[0]) {
+ // If this is an EDIT command.
+ var add, remove;
+ if (token[0] === 1) {
+ add = token;
+ remove = nextToken;
+ } else {
+ add = nextToken;
+ remove = token;
+ }
+ var sourceRange = new WebInspector.SourceRange(oldSourceOffset, remove[1].length);
+ var oldRange = WebInspector.TextRange.createFromSourceRange(contentV1, sourceRange);
+ edits.push(new WebInspector.SourceMapEdit(sourceURL, oldRange, remove[1], add[1]));
+ oldSourceOffset += remove[1].length;
+ i += 2;
+ } else if (token[0] === 1) {
+ // If this is an ADD command.
+ var sourceRange = new WebInspector.SourceRange(oldSourceOffset, 0);
+ var oldRange = WebInspector.TextRange.createFromSourceRange(contentV1, sourceRange);
+ edits.push(new WebInspector.SourceMapEdit(sourceURL, oldRange, "", token[1]));
+ i += 1;
+ } else {
+ // If this is a REMOVE command.
+ var sourceRange = new WebInspector.SourceRange(oldSourceOffset, token[1].length);
+ var oldRange = WebInspector.TextRange.createFromSourceRange(contentV1, sourceRange);
+ edits.push(new WebInspector.SourceMapEdit(sourceURL, oldRange, token[1], ""));
+ oldSourceOffset += token[1].length;
+ i += 1;
+ }
+ }
+ return edits;
+}
+
+WebInspector.SourceMapEditor.prototype = {
+ /**
+ * @param {!WebInspector.SourceMap} map
+ * @param {!Array<!WebInspector.SourceMapEdit>} edits
+ * @return {!Set<string, string>}
+ */
+ handleCompiledEdits: function(map, edits)
+ {
+ var backEditInfos = [];
+ for (var i = 0; i < edits.length; ++i)
+ backEditInfos.push(this._doCompiledEdit(map, edits[i]));
+
+ var didBackEdits = false;
+ for (var i = 0; i < edits.length; ++i)
+ didBackEdits = this._doBackEdits(map, edits[i], backEditInfos[i]) || didBackEdits;
+
+ var result = new Set();
+ if (didBackEdits)
+ result.add(edits[0].sourceURL);
+ for (var i = 0; i < backEditInfos.length; ++i) {
+ var sourceURL = backEditInfos[i].compiledBaseMapping.sourceURL;
+ result.add(sourceURL);
+ }
+ return result;
+ },
+
+ /**
+ * @param {!WebInspector.SourceMap} map
+ * @param {!WebInspector.SourceMapEdit} edit
+ * @return {!WebInspector.SourceMapEditor.BackEditInfo}
+ */
+ _doCompiledEdit: function(map, edit)
+ {
+ var startMapping = map.findEntry(edit.oldRange.startLine, edit.oldRange.startColumn);
+ var endMapping = map.findEntry(edit.oldRange.endLine, edit.oldRange.endColumn);
+ if (startMapping.sourceURL !== endMapping.sourceURL)
+ throw new Error("An edit spans multiple source files.");
+
+ var sourceURL = startMapping.sourceURL;
+ var compiledText = this._sources.get(edit.sourceURL);
+ var sourceText = this._sources.get(sourceURL);
+
+ // Return if we don't have source for mapped entries.
+ if (!compiledText || !sourceText)
+ throw new Error("Source of " + sourceURL + " is missing");
+
+ var startLocation = this._mapCompiledLocation(startMapping, edit.oldRange.startLine, edit.oldRange.startColumn);
+ var endLocation = this._mapCompiledLocation(endMapping, edit.oldRange.endLine, edit.oldRange.endColumn);
+ var sourceOldRange = new WebInspector.TextRange(startLocation.line, startLocation.column, endLocation.line, endLocation.column);
+ var sourceOldText = sourceOldRange.extract(sourceText);
+ // If the text in compiled and original differs, then we cannot edit it.
+ var sourceNewText = this._editFlavor.sourceEditText(edit.oldText, sourceOldText, edit.newText)
+ if (sourceNewText === null)
+ throw new Error("The edited range is not equal in origin and source");
+
+ var newTextLineCount = sourceNewText.lineEndings().length;
+ if (newTextLineCount > 1)
+ throw new Error("Not implemented for multi-line edit");
+
+ if (!map.compiledRangeEdited(edit.oldRange, edit.newRange()))
+ throw new Error("Failed to update compiled locations in sourcemap.");
+
+ var sourceEdit = new WebInspector.SourceMapEdit(sourceURL, sourceOldRange, sourceOldText, sourceNewText);
+ if (!map.sourceRangeEdited(sourceURL, sourceEdit.oldRange, sourceEdit.newRange()))
+ throw new Error("Failed to update source locations in sourcemap.");
+
+ this._sources.set(sourceURL, sourceEdit.applyToText(sourceText));
+
+ var offsetRange = edit.oldRange.relativeTo(startMapping.lineNumber, startMapping.columnNumber);
+ var sourceNewRange = sourceEdit.newRange();
+ var reversedMappings = map.findEntriesReversed(sourceEdit.sourceURL, sourceNewRange.startLine, sourceNewRange.startColumn + 1);
+ return new WebInspector.SourceMapEditor.BackEditInfo(startMapping, offsetRange, reversedMappings);
+ },
+
+ _doBackEdits: function(map, compiledEdit, backEditInfo)
+ {
+ // There is no multimapping from source position to compiled position.
+ if (backEditInfo.reversedMappings.length < 2)
+ return false;
+ var didEdit = false;
+ var compiledText = this._sources.get(compiledEdit.sourceURL);
+ for (var i = 0; i < backEditInfo.reversedMappings.length; ++i) {
+ var mapping = backEditInfo.reversedMappings[i];
+ if (mapping === backEditInfo.compiledBaseMapping)
+ continue;
+ var oldEditRange = rebaseOffsetRange(backEditInfo.offsetRange, mapping);
+ var reverseEdit = new WebInspector.SourceMapEdit(compiledEdit.sourceURL, oldEditRange, oldEditRange.extract(compiledText), compiledEdit.newText);
+ if (reverseEdit.oldText !== compiledEdit.oldText) {
+ console.error("compiled texts differ.");
+ continue;
+ }
+ if (!map.compiledRangeEdited(reverseEdit.oldRange, reverseEdit.newRange()))
+ throw new Error("Failed to update reversed compiled locations in sourcemap.");
+ compiledText = reverseEdit.applyToText(compiledText);
+ didEdit = true;
+ }
+ if (didEdit)
+ this._sources.set(compiledEdit.sourceURL, compiledText);
+
+ return didEdit;
+
+ function rebaseOffsetRange(range, mapping)
+ {
+ return new WebInspector.TextRange(
+ range.startLine + mapping.lineNumber,
+ range.startColumn + mapping.columnNumber,
+ range.endLine + mapping.lineNumber,
+ range.endColumn + mapping.columnNumber
+ );
+ }
+ },
+
+ /**
+ * @param {!WebInspector.SourceMap.Entry} entry
+ * @param {number} lineNumber
+ * @param {number} columnNumber
+ * @return {!{line: number, column: number}}
+ */
+ _mapCompiledLocation: function(entry, lineNumber, columnNumber)
+ {
+ var lineOffset = lineNumber - entry.lineNumber;
+ var columnOffset = columnNumber - entry.columnNumber;
+ return {line: entry.sourceLineNumber + lineOffset, column: entry.sourceColumnNumber + columnOffset};
+ }
+}
+
+/**
+ * @constructor
+ * @param {string} sourceURL
+ * @param {!WebInspector.TextRange} oldRange
+ * @param {string} oldText
+ * @param {string} newText
+ */
+WebInspector.SourceMapEdit = function(sourceURL, oldRange, oldText, newText)
+{
+ this.sourceURL = sourceURL;
+ this.oldRange = oldRange;
+ this.oldText = oldText;
+ this.newText = newText;
+}
+
+WebInspector.SourceMapEdit.prototype = {
+ /**
+ * @return {!WebInspector.TextRange}
+ */
+ newRange: function()
+ {
+ var endLine = this.oldRange.startLine;
+ var endColumn = this.oldRange.startColumn + this.newText.length;
+ var lineEndings = this.newText.lineEndings();
+ if (lineEndings.length > 1) {
+ endLine = this.oldRange.startLine + lineEndings.length - 1;
+ var len = lineEndings.length;
+ endColumn = lineEndings[len - 1] - lineEndings[len - 2] - 1;
+ }
+ return new WebInspector.TextRange(
+ this.oldRange.startLine,
+ this.oldRange.startColumn,
+ endLine,
+ endColumn);
+ },
+
+ applyToText: function(text)
+ {
+ return this.oldRange.replaceInText(text, this.newText);
+ },
+}
+
+WebInspector.SourceMapEditor.BackEditInfo = function(compiledBaseMapping, offsetRange, reversedMappings)
+{
+ this.compiledBaseMapping = compiledBaseMapping;
+ this.offsetRange = offsetRange;
+ this.reversedMappings = reversedMappings;
+}
+
+/**
+ * @interface
+ */
+WebInspector.SourceMapEditorFlavor = function() { }
+
+WebInspector.SourceMapEditorFlavor.prototype = {
+ /**
+ * @param {string} compiledURL
+ * @param {string} compiledText
+ * @param {!WebInspector.TextRange} compiledRange
+ * @param {string} sourceURL
+ * @param {string} sourceText
+ * @param {!WebInspector.TextRange} sourceRange
+ * @return {?string}
+ */
+ sourceEditText: function(compiledURL, compiledText, compiledRange, sourceText, sourceURL, sourceRange)
+ {
+ },
+}
« no previous file with comments | « Source/devtools/front_end/bindings/SASSStructureMapping.js ('k') | Source/devtools/front_end/bindings/diff_match_patch.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698