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

Side by Side Diff: Source/devtools/front_end/bindings/SASSSupport.js

Issue 1331083002: DevTools: [STRUCT] edit SASS through SourceMaps (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: rebaseline atop master 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 WebInspector.SASSSupport = {
6 insertPropertyAfter: function(property, after)
7 {
8 var rule = after.parent;
9 rule.insertPropertyAfter(property, after);
10
11 var ast = after.root();
12 var oldRange = WebInspector.TextRange.createFromLocation(after.range.end Line, after.range.endColumn);
13 var indent = (new WebInspector.TextRange(after.range.startLine, 0, after .range.startLine, after.range.startColumn)).extract(ast.text);
14 if (!/^\s+$/.test(indent)) indent = "";
15
16 // Split property addition into chunks to preserve mappings.
17 var newText = String.sprintf("\n%s%s: %s;", indent, property.name.text, property.value.text);
18 return new WebInspector.SourceEdit(ast.url, oldRange, "", newText);
19 },
20
21 insertPropertyBefore: function(property, before)
22 {
23 var rule = before.parent;
24 rule.insertPropertyAfter(property, null);
25
26 var ast = before.root();
27 var oldRange = WebInspector.TextRange.createFromLocation(before.range.st artLine, before.range.startColumn);
28 var indent = (new WebInspector.TextRange(before.range.startLine, 0, befo re.range.startLine, before.range.startColumn)).extract(ast.text);
29 if (!/^\s+$/.test(indent)) indent = "";
30
31 // Split property addition into chunks to preserve mappings.
32 var newText = String.sprintf("%s: %s;\n%s", property.name.text, property .value.text, indent);
33 return new WebInspector.SourceEdit(ast.url, oldRange, "", newText);
34 },
35
36 removeProperty: function(property)
37 {
38 var rule = property.parent;
39 var ast = property.root();
40 rule.removeProperty(property);
41
42 var lineRemoveRange = new WebInspector.TextRange(property.range.startLin e, 0, property.range.endLine + 1, 0);
43 var oldRange = (lineRemoveRange.extract(ast.text).trim() === property.ra nge.extract(ast.text).trim()) ? lineRemoveRange : property.range;
44 return new WebInspector.SourceEdit(ast.url, oldRange, oldRange.extract(a st.text), "");
45 },
46
47 setText: function(node, newText)
48 {
49 console.assert(node.type === "TextNode", "Cannot set text to node of typ e " + node.type);
50 node.text = newText;
51 var ast = node.root();
52 return new WebInspector.SourceEdit(ast.url, node.range, node.range.extra ct(ast.text), newText);
53 },
54
55 toggleDisabled: function(property, disabled)
56 {
57 property.disabled = disabled;
58 var ast = property.root();
59 if (disabled) {
60 var oldRange1 = WebInspector.TextRange.createFromLocation(property.r ange.startLine, property.range.startColumn);
61 var edit1 = new WebInspector.SourceEdit(ast.url, oldRange1, "", "/* ");
62 var oldRange2 = WebInspector.TextRange.createFromLocation(property.r ange.endLine, property.range.endColumn);
63 var edit2 = new WebInspector.SourceEdit(ast.url, oldRange2, "", " */ ");
64 return [edit1, edit2];
65 }
66 var oldRange1 = new WebInspector.TextRange(property.range.startLine, pro perty.range.startColumn, property.range.startLine, property.name.range.startColu mn);
67 var text = ast.text;
68 var edit1 = new WebInspector.SourceEdit(ast.url, oldRange1, oldRange1.ex tract(text), "");
69 var oldRange2 = new WebInspector.TextRange(property.range.endLine, prope rty.range.endColumn - 2, property.range.endLine, property.range.endColumn);
70 var edit2 = new WebInspector.SourceEdit(ast.url, oldRange2, "*/", "");
71 return [edit1, edit2];
72 },
73 }
74
75 WebInspector.SASSSupport.parseCSS = function(url, text)
76 {
77 var parser = new WebInspector.CSSParser();
78 return parsePromise = parser.parsePromise(text)
79 .then(onParsed.bind(this));
80
81 function onParsed(parsedCSS)
82 {
83 parser.dispose();
84 var rules = [];
85 for (var i = 0; i < parsedCSS.length; ++i) {
86 var rule = parsedCSS[i];
87 if (!rule.properties)
88 continue;
89 var properties = [];
90 for (var j = 0; j < rule.properties.length; ++j) {
91 var cssProperty = rule.properties[j];
92 var name = new WebInspector.SASSSupport.TextNode(cssProperty.nam e, WebInspector.TextRange.fromObject(cssProperty.nameRange));
93 var value = new WebInspector.SASSSupport.TextNode(cssProperty.va lue, WebInspector.TextRange.fromObject(cssProperty.valueRange));
94 var property = new WebInspector.SASSSupport.Property(name, value , WebInspector.TextRange.fromObject(cssProperty.range));
95 property.disabled = cssProperty.disabled;
96 properties.push(property);
97 }
98 rules.push(new WebInspector.SASSSupport.Rule(rule.selectorText, prop erties));
99 }
100 return new WebInspector.SASSSupport.AST(url, text, rules);
101 }
102 },
103
104 WebInspector.SASSSupport.parseSASS = function(url, text, tokenizerFactory)
105 {
106 var result = WebInspector.SASSSupport._innerParseSASS(text, tokenizerFactory );
107
108 var rules = [
109 new WebInspector.SASSSupport.Rule("variables", result.variables),
110 new WebInspector.SASSSupport.Rule("properties", result.properties),
111 new WebInspector.SASSSupport.Rule("mixins", result.mixins)
112 ];
113
114 return new WebInspector.SASSSupport.AST(url, text, rules);
115 }
116
117 WebInspector.SASSSupport.SCSSParserStates = {
118 Initial: "Initial",
119 PropertyName: "PropertyName",
120 PropertyValue: "PropertyValue",
121 VariableName: "VariableName",
122 VariableValue: "VariableValue",
123 MixinInclude: "MixinInclude",
124 MixinValue: "MixinValue"
125 }
126
127 WebInspector.SASSSupport._innerParseSASS = function(text, tokenizerFactory)
128 {
129 var lines = text.split("\n");
130 var properties = [];
131 var variables = [];
132 var mixins = [];
133
134 var States = WebInspector.SASSSupport.SCSSParserStates;
135 var state = States.Initial;
136 var propertyName, propertyValue;
137 var variableName, variableValue;
138 var mixinName, mixinValue;
139 var UndefTokenType = {};
140
141 /**
142 * @param {string} tokenValue
143 * @param {?string} tokenTypes
144 * @param {number} column
145 * @param {number} newColumn
146 */
147 function processToken(tokenValue, tokenTypes, column, newColumn)
148 {
149 var tokenType = tokenTypes ? tokenTypes.split(" ").keySet() : UndefToken Type;
150 switch (state) {
151 case States.Initial:
152 if (tokenType["css-variable-2"]) {
153 variableName = new WebInspector.SASSSupport.TextNode(tokenValue, new WebInspector.TextRange(lineNumber, column, lineNumber, newColumn));
154 state = States.VariableName;
155 } else if (tokenType["css-property"] || tokenType["css-meta"]) {
156 propertyName = new WebInspector.SASSSupport.TextNode(tokenValue, new WebInspector.TextRange(lineNumber, column, lineNumber, newColumn));
157 state = States.PropertyName;
158 } else if (tokenType["css-def"] && tokenValue === "@include") {
159 mixinName = new WebInspector.SASSSupport.TextNode(tokenValue, ne w WebInspector.TextRange(lineNumber, column, lineNumber, newColumn));
160 state = States.MixinInclude;
161 } else if (tokenType["css-comment"]) {
162 // The |processToken| is called per-line, so no token spans more then one line.
163 // Support only a one-line comments.
164 if (tokenValue.substring(0, 2) !== "/*" || tokenValue.substring( tokenValue.length - 2) !== "*/")
165 break;
166 var uncommentedText = tokenValue.substring(2, tokenValue.length - 2);
167 var fakeRule = "a{\n" + uncommentedText + "}";
168 disabledRules = [];
169 var result = WebInspector.SASSSupport._innerParseSASS(fakeRule, tokenizerFactory);
170 if (result.properties.length === 1 && result.variables.length == = 0 && result.mixins.length === 0) {
171 var disabledProperty = result.properties[0];
172 // We should offset property to current coordinates.
173 var offset = column + 2;
174 disabledProperty.disabled = true;
175 disabledProperty.range = WebInspector.TextRange.createFromLo cation(lineNumber, column);
176 disabledProperty.range.endColumn = newColumn;
177 disabledProperty.name.range.startColumn += offset;
178 disabledProperty.name.range.endColumn += offset;
179 disabledProperty.value.range.startColumn += offset;
180 disabledProperty.value.range.endColumn += offset;
181 properties.push(disabledProperty);
182 }
183 }
184 break;
185 case States.VariableName:
186 if (tokenValue === ")" && tokenType === UndefTokenType) {
187 state = States.Initial;
188 } else if (tokenValue === ":" && tokenType === UndefTokenType) {
189 state = States.VariableValue;
190 variableValue = new WebInspector.SASSSupport.TextNode("", WebIns pector.TextRange.createFromLocation(lineNumber, newColumn));
191 } else if (tokenType !== UndefTokenType) {
192 state = States.Initial;
193 }
194 break;
195 case States.VariableValue:
196 if (tokenValue === ";" && tokenType === UndefTokenType) {
197 variableValue.range.endLine = lineNumber;
198 variableValue.range.endColumn = column;
199 var variable = new WebInspector.SASSSupport.Property(variableNam e, variableValue, variableName.range.clone());
200 variable.range.endLine = lineNumber;
201 variable.range.endColumn = newColumn;
202 variables.push(variable);
203 state = States.Initial;
204 } else {
205 variableValue.text += tokenValue;
206 }
207 break;
208 case States.PropertyName:
209 if (tokenValue === ":" && tokenType === UndefTokenType) {
210 state = States.PropertyValue;
211 propertyName.range.endLine = lineNumber;
212 propertyName.range.endColumn = column;
213 propertyValue = new WebInspector.SASSSupport.TextNode("", WebIns pector.TextRange.createFromLocation(lineNumber, newColumn));
214 } else if (tokenType["css-property"]) {
215 propertyName.text += tokenValue;
216 }
217 break;
218 case States.PropertyValue:
219 if (tokenValue === ";" && tokenType === UndefTokenType) {
220 propertyValue.range.endLine = lineNumber;
221 propertyValue.range.endColumn = column;
222 var property = new WebInspector.SASSSupport.Property(propertyNam e, propertyValue, propertyName.range.clone());
223 property.range.endLine = lineNumber;
224 property.range.endColumn = newColumn;
225 properties.push(property);
226 state = States.Initial;
227 } else {
228 propertyValue.text += tokenValue;
229 }
230 break;
231 case States.MixinInclude:
232 if (tokenValue === "(" && tokenType === UndefTokenType) {
233 state = States.MixinValue;
234 mixinValue = new WebInspector.SASSSupport.TextNode("", WebInspec tor.TextRange.createFromLocation(lineNumber, newColumn));
235 } else if (tokenValue === ";" && tokenType === UndefTokenType) {
236 state = States.Initial;
237 if (mixinValue) {
238 var mixin = new WebInspector.SASSSupport.Property(mixinName, mixinValue);
239 mixins.push(mixin);
240 }
241 mixinValue = null;
242 } else {
243 mixinName.text += tokenValue;
244 }
245 break;
246 case States.MixinValue:
247 if (tokenValue === ")" && tokenType === UndefTokenType) {
248 state = States.MixinInclude;
249 mixinValue.range.endLine = lineNumber;
250 mixinValue.range.endColumn = column;
251 } else {
252 mixinValue.text += tokenValue;
253 }
254 break;
255 default:
256 console.assert(false, "Unknown SASS parser state.");
257 }
258 }
259 var tokenizer = tokenizerFactory.createTokenizer("text/x-scss");
260 var lineNumber;
261 for (lineNumber = 0; lineNumber < lines.length; ++lineNumber) {
262 var line = lines[lineNumber];
263 tokenizer(line, processToken);
264 }
265 return {
266 variables: variables,
267 properties: properties,
268 mixins: mixins
269 };
270 }
271
272 WebInspector.SASSSupport.Node = function(type)
273 {
274 this.type = type;
275 }
276
277 WebInspector.SASSSupport.Node.prototype = {
278 root: function()
279 {
280 if (this._root)
281 return this._root;
282 this._root = this;
283 while (this._root.parent)
284 this._root = this._root.parent;
285 return this._root;
286 }
287 }
288
289 WebInspector.SASSSupport.TextNode = function(text, range)
290 {
291 WebInspector.SASSSupport.Node.call(this, "TextNode");
292 this.text = text;
293 this.range = range;
294 this.parent = null;
295 }
296
297 WebInspector.SASSSupport.TextNode.prototype = {
298 clone: function()
299 {
300 return new WebInspector.SASSSupport.TextNode(this.text, this.range.clone ());
301 },
302
303 __proto__: WebInspector.SASSSupport.Node.prototype
304 }
305
306 WebInspector.SASSSupport.Property = function(name, value, range)
307 {
308 WebInspector.SASSSupport.Node.call(this, "Property");
309 this.name = name;
310 this.value = value;
311 this.range = range;
312 this.name.parent = this;
313 this.value.parent = this;
314 this.parent = null;
315 this.disabled = false;
316 }
317
318 WebInspector.SASSSupport.Property.prototype = {
319 clone: function()
320 {
321 return new WebInspector.SASSSupport.Property(this.name.clone(), this.val ue.clone(), this.range.clone());
322 },
323
324 __proto__: WebInspector.SASSSupport.Node.prototype
325 }
326
327 WebInspector.SASSSupport.Rule = function(selector, properties)
328 {
329 WebInspector.SASSSupport.Node.call(this, "Rule");
330 this.selector = selector;
331 this.properties = properties;
332 this.parent = null;
333 for (var i = 0; i < this.properties.length; ++i)
334 this.properties[i].parent = this;
335 }
336
337 WebInspector.SASSSupport.Rule.prototype = {
338 insertPropertyAfter: function(property, after)
339 {
340 var index = this.properties.indexOf(after);
341 this.properties.splice(index + 1, 0, property);
342 property.parent = this;
343 },
344
345 removeProperty: function(property)
346 {
347 var index = this.properties.indexOf(property);
348 this.properties.splice(index, 1);
349 property.parent = null;
350 },
351
352 __proto__: WebInspector.SASSSupport.Node.prototype
353 }
354
355 WebInspector.SASSSupport.AST = function(url, text, rules)
356 {
357 WebInspector.SASSSupport.Node.call(this, "AST");
358 this.url = url;
359 this.rules = rules;
360 for (var i = 0; i < rules.length; ++i) {
361 rules[i].before = i > 0 ? rules[i - 1] : null;
362 rules[i].after = i < rules.length - 1 ? rules[i + 1] : null;
363 rules[i].parent = this;
364 }
365 this.text = text;
366 }
367
368 WebInspector.SASSSupport.AST.prototype.__proto__ = WebInspector.SASSSupport.Node .prototype;
369
370 WebInspector.SASSSourceMapping.diffModels = function(oldAST, newAST)
371 {
372 if (oldAST.rules.length !== newAST.rules.length)
373 throw new Error("not implemented for rule diff.");
374 var structuralDiff = [];
375 var aToB = new Map();
376 var bToA = new Map();
377 for (var i = 0; i < oldAST.rules.length; ++i) {
378 var oldRule = oldAST.rules[i];
379 var newRule = newAST.rules[i];
380 var removedSet = new Set();
381 var addedSet = new Set();
382 if (oldRule.properties.length !== newRule.properties.length)
383 WebInspector.SASSSourceMapping.cssPropertiesDiff(oldRule.properties, newRule.properties, removedSet, addedSet);
384
385 // Compute PropertyRemoved diff entries.
386 for (var property of removedSet.values()) {
387 structuralDiff.push({
388 type: "PropertyRemoved",
389 property: property
390 });
391 }
392
393 // Map similar properties.
394 var p1 = 0;
395 var p2 = 0;
396 while (p1 < oldRule.properties.length && p2 < newRule.properties.length) {
397 if (removedSet.has(oldRule.properties[p1])) {
398 ++p1;
399 continue;
400 }
401 if (addedSet.has(newRule.properties[p2])) {
402 ++p2;
403 continue;
404 }
405 var oldProperty = oldRule.properties[p1++];
406 var newProperty = newRule.properties[p2++];
407 aToB.set(oldProperty, newProperty);
408 bToA.set(newProperty, oldProperty);
409 if (oldProperty.name.text !== newProperty.name.text) {
410 structuralDiff.push({
411 type: "NameChanged",
412 property: newProperty,
413 });
414 }
415 if (oldProperty.value.text !== newProperty.value.text) {
416 structuralDiff.push({
417 type: "ValueChanged",
418 property: newProperty,
419 });
420 }
421 if (oldProperty.disabled !== newProperty.disabled) {
422 structuralDiff.push({
423 type: "toggleDisabled",
424 property: newProperty,
425 });
426 }
427 }
428
429 // Compute PropertyAdded diff entries.
430 var firstValidProperty = null;
431 for (var j = 0; j < newRule.properties.length; ++j) {
432 var property = newRule.properties[j];
433 if (!addedSet.has(property)) {
434 firstValidProperty = property;
435 break;
436 }
437 }
438 var lastValidProperty = null;
439 for (var j = 0; j < newRule.properties.length; ++j) {
440 var property = newRule.properties[j];
441 if (!addedSet.has(property)) {
442 lastValidProperty = property;
443 continue;
444 }
445 var diff = {
446 type: "PropertyAdded",
447 property: property,
448 };
449 if (lastValidProperty)
450 diff.after = lastValidProperty;
451 else if (firstValidProperty)
452 diff.before = firstValidProperty;
453 structuralDiff.push(diff);
454 }
455 }
456 return {
457 aToB: aToB,
458 bToA: bToA,
459 structuralDiff
460 };
461 }
462
463 WebInspector.SASSSourceMapping.cssPropertiesDiff = function(properties1, propert ies2, removedSet, addedSet)
464 {
465 var charCode = 33;
466 var encodedProperties = new Map();
467 var lines1 = [];
468 for (var i = 0; i < properties1.length; ++i)
469 lines1.push(properties1[i].name.text + ":" + properties1[i].value.text);
470
471 var lines2 = [];
472 for (var i = 0; i < properties2.length; ++i)
473 lines2.push(properties2[i].name.text + ":" + properties2[i].value.text);
474
475 var diff = WebInspector.Diff.lineDiff(lines1, lines2);
476 var p1 = 0, p2 = 0;
477 for (var i = 0; i < diff.length; ++i) {
478 var token = diff[i];
479 if (token[0] === 0) {
480 p1 += token[1].length;
481 p2 += token[1].length;
482 } else if (token[0] === -1) {
483 for (var j = 0; j < token[1].length; ++j) {
484 var property = properties1[p1++];
485 removedSet.add(property);
486 }
487 } else {
488 for (var j = 0; j < token[1].length; ++j) {
489 var property = properties2[p2++];
490 addedSet.add(property);
491 }
492 }
493 }
494 }
OLDNEW
« no previous file with comments | « Source/devtools/front_end/bindings/SASSSourceMapping.js ('k') | Source/devtools/front_end/bindings/module.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698