Title: [184000] trunk/Source/WebInspectorUI
Revision
184000
Author
[email protected]
Date
2015-05-08 11:37:49 -0700 (Fri, 08 May 2015)

Log Message

Web Inspector: Styles sidebar editing with incomplete property looks poor in UI
https://bugs.webkit.org/show_bug.cgi?id=141692

Patch by Tobias Reiss <[email protected]> on 2015-05-08
Reviewed by Timothy Hatcher.

Add "css-rule" Formatter that breaks CSS declarations into multiple lines,
keeps comments and invalid styles and adds whitespace.

* Tools/PrettyPrinting/css-rule-tests/*.css: Added.
Add test cases.

* Tools/PrettyPrinting/index.html:
Enable Test setup to be able to run "css-rule" Formatter tests.

* UserInterface/Controllers/Formatter.js:
(Formatter.prototype._handleToken):
* UserInterface/Controllers/FormatterContentBuilder.js:
(FormatterContentBuilder.prototype.removeLastNewline):
(FormatterContentBuilder.prototype.removeLastWhitespace):
(FormatterContentBuilder.prototype._popFormattedContent):
(FormatterContentBuilder.prototype._popNewLine): Deleted.
* UserInterface/Views/CSSStyleDeclarationTextEditor.js:
(WebInspector.CSSStyleDeclarationTextEditor.prototype._formattedContentFromEditor):
(WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent.update.set this):
(WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent.update.get this):
(WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent.update):
(WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent):
(WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent.update.countNewLineCharacters): Deleted.
(WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent.update.else): Deleted.
* UserInterface/Views/CodeMirrorFormatters.js:

Modified Paths

Diff

Modified: trunk/Source/WebInspectorUI/ChangeLog (183999 => 184000)


--- trunk/Source/WebInspectorUI/ChangeLog	2015-05-08 18:20:59 UTC (rev 183999)
+++ trunk/Source/WebInspectorUI/ChangeLog	2015-05-08 18:37:49 UTC (rev 184000)
@@ -1,3 +1,36 @@
+2015-05-08  Tobias Reiss  <[email protected]>
+
+        Web Inspector: Styles sidebar editing with incomplete property looks poor in UI
+        https://bugs.webkit.org/show_bug.cgi?id=141692
+
+        Reviewed by Timothy Hatcher.
+
+        Add "css-rule" Formatter that breaks CSS declarations into multiple lines,
+        keeps comments and invalid styles and adds whitespace.
+
+        * Tools/PrettyPrinting/css-rule-tests/*.css: Added.
+        Add test cases.
+
+        * Tools/PrettyPrinting/index.html:
+        Enable Test setup to be able to run "css-rule" Formatter tests.
+
+        * UserInterface/Controllers/Formatter.js:
+        (Formatter.prototype._handleToken):
+        * UserInterface/Controllers/FormatterContentBuilder.js:
+        (FormatterContentBuilder.prototype.removeLastNewline):
+        (FormatterContentBuilder.prototype.removeLastWhitespace):
+        (FormatterContentBuilder.prototype._popFormattedContent):
+        (FormatterContentBuilder.prototype._popNewLine): Deleted.
+        * UserInterface/Views/CSSStyleDeclarationTextEditor.js:
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._formattedContentFromEditor):
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent.update.set this):
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent.update.get this):
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent.update):
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent):
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent.update.countNewLineCharacters): Deleted.
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._resetContent.update.else): Deleted.
+        * UserInterface/Views/CodeMirrorFormatters.js:
+
 2015-05-07  Joseph Pecoraro  <[email protected]>
 
         Web Inspector: Expanding Object with only __proto__ looks poor should have a label

Modified: trunk/Source/WebInspectorUI/Tools/PrettyPrinting/index.html (183999 => 184000)


--- trunk/Source/WebInspectorUI/Tools/PrettyPrinting/index.html	2015-05-08 18:20:59 UTC (rev 183999)
+++ trunk/Source/WebInspectorUI/Tools/PrettyPrinting/index.html	2015-05-08 18:37:49 UTC (rev 184000)
@@ -8,6 +8,8 @@
     <script src=""
     <script src=""
     <script src=""
+    <script src=""
+    <script src=""
     <script src=""
     <script src=""
     <script src=""
@@ -21,6 +23,7 @@
     <select id="mode">
         <option selected value="text/_javascript_">_javascript_</option>
         <option value="text/css">CSS</option>
+        <option value="css-rule">CSS-Rule</option>
     </select>
     <button id="populate">Populate</button>
     <button id="run-tests">Run Tests</button>
@@ -230,6 +233,8 @@
 
         if (modePicker.value === "text/_javascript_")
             runJavaScriptTests(completedCallback);
+        else if (modePicker.value === "css-rule")
+            runCssRuleTests(completedCallback);
         else
             runCSSTests(completedCallback);
     }
@@ -240,6 +245,34 @@
             "js-tests/switch-case-default.js",
         ]);
     }
+    function runCssRuleTests(callback) {
+        _runTests(callback, [
+            "css-rule-tests/invalid-property-is-not-removed.css",
+            "css-rule-tests/remove-whitespace-before-colon.css",
+            "css-rule-tests/remove-whitespace-before-semicolon.css",
+            "css-rule-tests/remove-whitespace-before-property.css",
+            "css-rule-tests/remove-whitespace-before-prefixed-property.css",
+            "css-rule-tests/remove-whitespace-before-invalid-property.css",
+            "css-rule-tests/remove-whitespace-before-comment.css",
+            "css-rule-tests/split-comment-followed-by-property.css",
+            "css-rule-tests/split-comment-followed-by-prefixed-property.css",
+            "css-rule-tests/split-comment-followed-by-invalid-property.css",
+            "css-rule-tests/split-comment-followed-by-comment.css",
+            "css-rule-tests/split-property-followed-by-property.css",
+            "css-rule-tests/split-property-followed-by-prefixed-property.css",
+            "css-rule-tests/split-property-followed-by-invalid-property.css",
+            "css-rule-tests/split-property-followed-by-comment.css",
+            "css-rule-tests/split-invalid-property-followed-by-property.css",
+            "css-rule-tests/split-invalid-property-followed-by-prefixed-property.css",
+            "css-rule-tests/split-invalid-property-followed-by-invalid-property.css",
+            "css-rule-tests/split-invalid-property-followed-by-comment.css",
+            "css-rule-tests/split-property-without-semicolon-followed-by-comment-and-property.css",
+            "css-rule-tests/add-whitespace-after-colon.css",
+            "css-rule-tests/add-whitespace-after-comma.css",
+            "css-rule-tests/do-not-append-semicolon.css",
+            "css-rule-tests/keep-prefixed-value.css",
+        ]);
+    }
     function runCSSTests(callback) {
         _runTests(callback, [
             "css-tests/basic.css",

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/Formatter.js (183999 => 184000)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/Formatter.js	2015-05-08 18:20:59 UTC (rev 183999)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/Formatter.js	2015-05-08 18:37:49 UTC (rev 184000)
@@ -89,7 +89,7 @@
         if (startOfNewLine)
             this._builder.appendNewline();
 
-        // Whitespace. Collapse to a single space.
+        // Whitespace. Remove all spaces or collapse to a single space.
         if (isWhiteSpace) {
             this._builder.appendSpace();
             return;
@@ -101,6 +101,10 @@
         if (mode.modifyStateForTokenPre)
             mode.modifyStateForTokenPre(this._lastToken, this._lastContent, token, state, content, isComment);
 
+        // Should we remove the last whitespace?
+        if (this._builder.lastTokenWasWhitespace && mode.removeLastWhitespace(this._lastToken, this._lastContent, token, state, content, isComment))
+            this._builder.removeLastWhitespace();
+
         // Should we remove the last newline?
         if (this._builder.lastTokenWasNewline && mode.removeLastNewline(this._lastToken, this._lastContent, token, state, content, isComment, firstTokenOnLine))
             this._builder.removeLastNewline();

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/FormatterContentBuilder.js (183999 => 184000)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/FormatterContentBuilder.js	2015-05-08 18:20:59 UTC (rev 183999)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/FormatterContentBuilder.js	2015-05-08 18:37:49 UTC (rev 184000)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2015 Tobias Reiss <[email protected]>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -137,13 +138,27 @@
         console.assert(this.lastTokenWasNewline);
         console.assert(this._formattedContent.lastValue === "\n");
         if (this.lastTokenWasNewline) {
-            this._popNewLine();
+            this._popFormattedContent();
+            this._formattedLineEndings.pop();
             this._startOfLine = false;
             this.lastTokenWasNewline = false;
             this.lastTokenWasWhitespace = false;
         }
     }
 
+    removeLastWhitespace()
+    {
+        console.assert(this.lastTokenWasWhitespace);
+        console.assert(this._formattedContent.lastValue === " ");
+        if (this.lastTokenWasWhitespace) {
+            this._popFormattedContent();
+            // No need to worry about `_startOfLine` and `lastTokenWasNewline`
+            // because `appendSpace` takes care of not adding whitespace
+            // to the beginning of a line.
+            this.lastTokenWasWhitespace = false;
+        }
+    }
+
     indent()
     {
         ++this._indent;
@@ -170,11 +185,10 @@
 
     // Private
 
-    _popNewLine()
+    _popFormattedContent()
     {
         var removed = this._formattedContent.pop();
         this._formattedContentLength -= removed.length;
-        this._formattedLineEndings.pop();
     }
 
     _append(str)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CSSStyleDeclarationTextEditor.js (183999 => 184000)


--- trunk/Source/WebInspectorUI/UserInterface/Views/CSSStyleDeclarationTextEditor.js	2015-05-08 18:20:59 UTC (rev 183999)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CSSStyleDeclarationTextEditor.js	2015-05-08 18:37:49 UTC (rev 184000)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2015 Tobias Reiss <[email protected]>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -776,6 +777,20 @@
         }
     }
 
+    _formattedContentFromEditor()
+    {
+        var mapping = {original: [0], formatted: [0]};
+        // FIXME: <rdar://problem/10593948> Provide a way to change the tab width in the Web Inspector
+        var indentString = "    ";
+        var builder = new FormatterContentBuilder(mapping, [], [], 0, 0, indentString);
+        var formatter = new Formatter(this._codeMirror, builder);
+        var start = {line: 0, ch: 0};
+        var end = {line: this._codeMirror.lineCount() - 1};
+        formatter.format(start, end);
+
+        return builder.formattedContent.trim();
+    }
+
     _resetContent()
     {
         if (this._commitChangesTimeout) {
@@ -816,115 +831,63 @@
             // Remember the cursor position/selection.
             var selectionAnchor = this._codeMirror.getCursor("anchor");
             var selectionHead = this._codeMirror.getCursor("head");
+            var isEditorReadOnly = this._codeMirror.getOption("readOnly");
+            var styleText = this._style.text.trim();
+            var findWhitespace = /\s+/g;
 
-            function countNewLineCharacters(text)
-            {
-                var matches = text.match(/\n/g);
-                return matches ? matches.length : 0;
+            // Only format non-empty styles. Keep in mind that styleText is always empty
+            // for "readOnly" Editors. But prepare Checkbox placeholders in any case.
+            // Because that will indent the cursor when the User starts typing.
+            if (!styleText && !isEditorReadOnly) {
+                this._markLinesWithCheckboxPlaceholder();
+                return;
             }
 
-            var styleText = this._style.text;
+            // Set non-optimized, valid and invalid styles in preparation for the Formatter.
+            // Set empty string in case of readonly styles.
+            this._codeMirror.setValue(styleText);
 
-            // Pretty print the content if there are more properties than there are lines.
-            // This could be an option exposed to the user; however, it is almost always
-            // desired in this case.
-
-            if (styleText && this._style.visibleProperties.length <= countNewLineCharacters(styleText.trim()) + 1) {
-                // This style has formatted text content, so use it for a high-fidelity experience.
-
-                var prefixWhitespaceMatch = styleText.match(/^[ \t]*\n/);
-                this._prefixWhitespace = prefixWhitespaceMatch ? prefixWhitespaceMatch[0] : "";
-
-                var suffixWhitespaceMatch = styleText.match(/\n[ \t]*$/);
-                this._suffixWhitespace = suffixWhitespaceMatch ? suffixWhitespaceMatch[0] : "";
-
-                this._codeMirror.setValue(styleText);
-
-                if (this._prefixWhitespace)
-                    this._codeMirror.replaceRange("", {line: 0, ch: 0}, {line: 1, ch: 0});
-
-                if (this._suffixWhitespace) {
-                    var lineCount = this._codeMirror.lineCount();
-                    this._codeMirror.replaceRange("", {line: lineCount - 2}, {line: lineCount - 1});
-                }
-
-                this._linePrefixWhitespace = "";
-
-                var linesToStrip = [];
-
-                // Remember the whitespace so it can be restored on commit.
-                var lineCount = this._codeMirror.lineCount();
-                for (var i = 0; i < lineCount; ++i) {
-                    var lineContent = this._codeMirror.getLine(i);
-                    var prefixWhitespaceMatch = lineContent.match(/^\s+/);
-
-                    // If there is no prefix whitespace (except for empty lines) then the prefix
-                    // whitespace of all other lines will be retained as is. Update markers and return.
-                    if (!prefixWhitespaceMatch) {
-                        if (!lineContent)
-                            continue;
-                        this._linePrefixWhitespace = "";
-                        this._updateTextMarkers(true);
-                        return;
-                    }
-
-                    linesToStrip.push(i);
-
-                    // Only remember the shortest whitespace so we don't loose any of the
-                    // original author's whitespace if their indentation lengths differed.
-                    // Using the shortest also makes the adjustment work in _updateTextMarkers.
-
-                    // FIXME: This messes up if there is a mix of spaces and tabs. A tab
-                    // is treated the same as a space when prefix whitespace is omitted,
-                    // so if the shortest prefixed whitespace is, say, two tab characters,
-                    // lines that begin with four spaces will only have a two space indent.
-                    if (!this._linePrefixWhitespace || prefixWhitespaceMatch[0].length < this._linePrefixWhitespace.length)
-                        this._linePrefixWhitespace = prefixWhitespaceMatch[0];
-                }
-
-                // Strip the whitespace from the beginning of each line.
-                for (var i = 0; i < linesToStrip.length; ++i) {
-                    var lineNumber = linesToStrip[i];
-                    var from = {line: lineNumber, ch: 0};
-                    var to = {line: lineNumber, ch: this._linePrefixWhitespace.length};
-                    this._codeMirror.replaceRange("", from, to);
-                }
-
-                // Update all the text markers.
-                this._updateTextMarkers(true);
-            } else {
-                // This style does not have text content or it is minified, so we want to synthesize the text content.
-
-                this._prefixWhitespace = "";
-                this._suffixWhitespace = "";
-                this._linePrefixWhitespace = "";
-
-                this._codeMirror.setValue("");
-
+            if (isEditorReadOnly) {
                 var lineNumber = 0;
-
-                // Iterate only visible properties if we have original style text. That way we known we only synthesize
-                // what was originaly in the style text.
-                this._iterateOverProperties(styleText ? true : false, function(property) {
-                    // Some property text can have line breaks, so consider that in the ranges below.
-                    var propertyText = property.synthesizedText;
-                    var propertyLineCount = countNewLineCharacters(propertyText);
-
+                this._iterateOverProperties(false, function(property) {
                     var from = {line: lineNumber, ch: 0};
-                    var to = {line: lineNumber + propertyLineCount};
-
-                    this._codeMirror.replaceRange((lineNumber ? "\n" : "") + propertyText, from);
+                    var to = {line: lineNumber};
+                    // Readonly properties are pretty printed by `synthesizedText` and not the Formatter.
+                    this._codeMirror.replaceRange((lineNumber ? "\n" : "") + property.synthesizedText, from);
                     this._createTextMarkerForPropertyIfNeeded(from, to, property);
-
-                    lineNumber += propertyLineCount + 1;
+                    lineNumber++;
                 });
 
-                // Look for colors and make swatches.
-                this._createColorSwatches(true);
+                return;
             }
 
-            this._markLinesWithCheckboxPlaceholder();
+            // Now the Formatter pretty prints the styles.
+            this._codeMirror.setValue(this._formattedContentFromEditor());
 
+            // We need to workaround the fact that...
+            // 1) `this._style.properties` only holds valid CSSProperty instances but not
+            // comments and invalid properties like `color;`.
+            // 2) `_createTextMarkerForPropertyIfNeeded` relies on CSSProperty instances.
+            var cssPropertiesMap = new Map();
+            this._iterateOverProperties(false, function(cssProperty) {
+                cssPropertiesMap.set(cssProperty.text.replace(findWhitespace, ""), cssProperty);
+            });
+
+            // Go through the Editor line by line and create TextMarker when a
+            // CSSProperty instance for that property exists. If not, then don't create a TextMarker.
+            this._codeMirror.eachLine(function(lineHandler) {
+                var lineNumber = lineHandler.lineNo();
+                var lineContentSansWhitespace = lineHandler.text.replace(findWhitespace, "");
+                if (cssPropertiesMap.has(lineContentSansWhitespace)) {
+                    var from = {line: lineNumber, ch: 0};
+                    var to = {line: lineNumber};
+                    this._createTextMarkerForPropertyIfNeeded(from, to, cssPropertiesMap.get(lineContentSansWhitespace));
+                }
+            }.bind(this));
+
+            // Look for colors and make swatches.
+            this._createColorSwatches(true);
+
             // Restore the cursor position/selection.
             this._codeMirror.setSelection(selectionAnchor, selectionHead);
 
@@ -934,6 +897,8 @@
 
             // Mark the editor as clean (unedited state).
             this._codeMirror.markClean();
+
+            this._markLinesWithCheckboxPlaceholder();
         }
 
         // This needs to be done first and as a separate operation to avoid an exception in CodeMirror.

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CodeMirrorFormatters.js (183999 => 184000)


--- trunk/Source/WebInspectorUI/UserInterface/Views/CodeMirrorFormatters.js	2015-05-08 18:20:59 UTC (rev 183999)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CodeMirrorFormatters.js	2015-05-08 18:37:49 UTC (rev 184000)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2015 Tobias Reiss <[email protected]>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -106,6 +107,11 @@
         return 0;
     },
 
+    removeLastWhitespace: function(lastToken, lastContent, token, state, content, isComment)
+    {
+        return false;
+    },
+
     removeLastNewline: function(lastToken, lastContent, token, state, content, isComment, firstTokenOnLine)
     {
         if (!token) {
@@ -374,6 +380,11 @@
         return 0;
     },
 
+    removeLastWhitespace: function(lastToken, lastContent, token, state, content, isComment)
+    {
+        return false;
+    },
+
     removeLastNewline: function(lastToken, lastContent, token, state, content, isComment, firstTokenOnLine)
     {
         if (isComment) { // Comment after semicolon.
@@ -419,3 +430,83 @@
             state._cssPrettyPrint.lineLength = 0;
     }
 });
+
+CodeMirror.extendMode("css-rule", {
+    shouldHaveSpaceBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
+    {
+        return lastContent === ":" && !lastToken;
+    },
+
+    shouldHaveSpaceAfterLastToken: function(lastToken, lastContent, token, state, content, isComment)
+    {
+        return lastContent === "," && !lastToken;
+    },
+
+    newlinesAfterToken: function(lastToken, lastContent, token, state, content, isComment)
+    {
+        return 0;
+    },
+
+    removeLastWhitespace: function(lastToken, lastContent, token, state, content, isComment)
+    {
+        // Remove whitespace before a comment which moves the comment to the beginning of the line.
+        if (isComment)
+            return true;
+
+        // A semicolon indicates the end of line. So remove whitespace before next line.
+        if (!lastToken)
+            return lastContent === ";";
+
+        // Remove whitespace before semicolon. Like `prop: value ;`.
+        // Remove whitespace before colon. Like `prop : value;`.
+        if (!token)
+            return content === ";" || content === ":";
+
+        // A comment is supposed to be in its own line. So remove whitespace before next line.
+        if (/\bcomment\b/.test(lastToken))
+            return true;
+
+        return false;
+    },
+
+    removeLastNewline: function(lastToken, lastContent, token, state, content, isComment, firstTokenOnLine)
+    {
+        return false;
+    },
+
+    indentAfterToken: function(lastToken, lastContent, token, state, content, isComment)
+    {
+        return false;
+    },
+
+    newlineBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
+    {
+        // Add new line before comments.
+        if (isComment)
+            return true;
+
+        // Add new line before a prefixed property like `-webkit-animation`.
+        if (state.state === "block")
+            return /\bmeta\b/.test(token);
+
+        // Add new line after comment
+        if (/\bcomment\b/.test(lastToken))
+            return true;
+
+        // Add new line before a regular property like `display`.
+        if (/\bproperty\b/.test(token))
+            return !(/\bmeta\b/.test(lastToken));
+
+        return false;
+    },
+
+    indentBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
+    {
+        return false;
+    },
+
+    dedentsBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
+    {
+        return 0;
+    }
+});
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to