Web Inspector: Pressing tab in the styles sidebar shouldn't insert a tab character
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 24 Jun 2015 23:54:12 +0000 (23:54 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 24 Jun 2015 23:54:12 +0000 (23:54 +0000)
https://bugs.webkit.org/show_bug.cgi?id=146189

Patch by Devin Rousso <drousso@apple.com> on 2015-06-24
Reviewed by Timothy Hatcher.

* UserInterface/Controllers/CodeMirrorCompletionController.js: Added variable to control whether semicolons are added to the end of autocompleted css values.
(WebInspector.CodeMirrorCompletionController):
(WebInspector.CodeMirrorCompletionController.prototype.set noEndingSemicolon):
(WebInspector.CodeMirrorCompletionController.prototype._generateCSSCompletions):
* UserInterface/Views/CSSStyleDeclarationSection.js:
(WebInspector.CSSStyleDeclarationSection):
(WebInspector.CSSStyleDeclarationSection.prototype.cssStyleDeclarationTextEditorSwitchRule):
(WebInspector.CSSStyleDeclarationSection.prototype.focusRuleSelector):
(WebInspector.CSSStyleDeclarationSection.prototype.selectLastProperty):
(WebInspector.CSSStyleDeclarationSection.prototype.get selectorLocked):
(WebInspector.CSSStyleDeclarationSection.prototype.get locked):
(WebInspector.CSSStyleDeclarationSection.prototype._handleKeyDown):
* UserInterface/Views/CSSStyleDeclarationTextEditor.js:
(WebInspector.CSSStyleDeclarationTextEditor): Added functions for "Tab", "Shift-Tab", and "Shift-Enter" keypresses to improve usability.
(WebInspector.CSSStyleDeclarationTextEditor.prototype.selectFirstProperty): Highlights the first property.
(WebInspector.CSSStyleDeclarationTextEditor.prototype.selectLastProperty): Highlights the last property.
(WebInspector.CSSStyleDeclarationTextEditor.prototype._insertNewlineAfterCurrentLine): Inserts a newline after the currently selected line.
(WebInspector.CSSStyleDeclarationTextEditor.prototype._handleShiftTabKey.switchRule):
(WebInspector.CSSStyleDeclarationTextEditor.prototype._handleShiftTabKey): Pressing shift-tab will move the cursor to the previous non-word character that is immediately after a word character.
(WebInspector.CSSStyleDeclarationTextEditor.prototype._handleTabKey.switchRule):
(WebInspector.CSSStyleDeclarationTextEditor.prototype._handleTabKey.highlightNextNameOrValue):
(WebInspector.CSSStyleDeclarationTextEditor.prototype._handleTabKey): Pressing tab will move the cursor to each space character until the end of the line is reached, at
which point the cursor will move to the beginning of the next line.  Once the cursor is at the last line, pressing tab again will insert a newline.
* UserInterface/Views/RulesStyleDetailsPanel.js:
(WebInspector.RulesStyleDetailsPanel.prototype.cssStyleDeclarationSectionEditorNextRule): Switches the focused rule to the next section.
(WebInspector.RulesStyleDetailsPanel.prototype.cssStyleDeclarationSectionEditorPrevRule): Switches the focused rule to the previous section.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@185935 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js
Source/WebInspectorUI/UserInterface/Views/CSSStyleDeclarationSection.js
Source/WebInspectorUI/UserInterface/Views/CSSStyleDeclarationTextEditor.js
Source/WebInspectorUI/UserInterface/Views/RulesStyleDetailsPanel.js

index 74d083e..0c6c148 100644 (file)
@@ -1,3 +1,37 @@
+2015-06-24  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Pressing tab in the styles sidebar shouldn't insert a tab character
+        https://bugs.webkit.org/show_bug.cgi?id=146189
+
+        Reviewed by Timothy Hatcher.
+
+        * UserInterface/Controllers/CodeMirrorCompletionController.js: Added variable to control whether semicolons are added to the end of autocompleted css values.
+        (WebInspector.CodeMirrorCompletionController):
+        (WebInspector.CodeMirrorCompletionController.prototype.set noEndingSemicolon):
+        (WebInspector.CodeMirrorCompletionController.prototype._generateCSSCompletions):
+        * UserInterface/Views/CSSStyleDeclarationSection.js:
+        (WebInspector.CSSStyleDeclarationSection):
+        (WebInspector.CSSStyleDeclarationSection.prototype.cssStyleDeclarationTextEditorSwitchRule):
+        (WebInspector.CSSStyleDeclarationSection.prototype.focusRuleSelector):
+        (WebInspector.CSSStyleDeclarationSection.prototype.selectLastProperty):
+        (WebInspector.CSSStyleDeclarationSection.prototype.get selectorLocked):
+        (WebInspector.CSSStyleDeclarationSection.prototype.get locked):
+        (WebInspector.CSSStyleDeclarationSection.prototype._handleKeyDown):
+        * UserInterface/Views/CSSStyleDeclarationTextEditor.js:
+        (WebInspector.CSSStyleDeclarationTextEditor): Added functions for "Tab", "Shift-Tab", and "Shift-Enter" keypresses to improve usability.
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype.selectFirstProperty): Highlights the first property.
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype.selectLastProperty): Highlights the last property.
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._insertNewlineAfterCurrentLine): Inserts a newline after the currently selected line.
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._handleShiftTabKey.switchRule):
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._handleShiftTabKey): Pressing shift-tab will move the cursor to the previous non-word character that is immediately after a word character.
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._handleTabKey.switchRule):
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._handleTabKey.highlightNextNameOrValue):
+        (WebInspector.CSSStyleDeclarationTextEditor.prototype._handleTabKey): Pressing tab will move the cursor to each space character until the end of the line is reached, at
+        which point the cursor will move to the beginning of the next line.  Once the cursor is at the last line, pressing tab again will insert a newline.
+        * UserInterface/Views/RulesStyleDetailsPanel.js:
+        (WebInspector.RulesStyleDetailsPanel.prototype.cssStyleDeclarationSectionEditorNextRule): Switches the focused rule to the next section.
+        (WebInspector.RulesStyleDetailsPanel.prototype.cssStyleDeclarationSectionEditorPrevRule): Switches the focused rule to the previous section.
+
 2015-06-24  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: console.group looks poor in console
index 75aa268..9eca93b 100644 (file)
@@ -39,6 +39,7 @@ WebInspector.CodeMirrorCompletionController = class CodeMirrorCompletionControll
         this._endOffset = NaN;
         this._lineNumber = NaN;
         this._prefix = "";
+        this._noEndingSemicolon = false;
         this._completions = [];
         this._extendedCompletionProviders = {};
 
@@ -185,6 +186,11 @@ WebInspector.CodeMirrorCompletionController = class CodeMirrorCompletionControll
         this._commitCompletionHint();
     }
 
+    set noEndingSemicolon(noEndingSemicolon)
+    {
+        this._noEndingSemicolon = noEndingSemicolon;
+    }
+
     // Private
 
     get _currentReplacementText()
@@ -524,7 +530,7 @@ WebInspector.CodeMirrorCompletionController = class CodeMirrorCompletionControll
 
             // If there is a suffix and it isn't a semicolon, then we should use a space since
             // the user is editing in the middle.
-            this._implicitSuffix = suffix && suffix !== ";" ? " " : ";";
+            this._implicitSuffix = suffix && suffix !== ";" ? " " : (this._noEndingSemicolon ? "" : ";");
 
             // Don't use an implicit suffix if it would be the same as the existing suffix.
             if (this._implicitSuffix === suffix)
index 3bdc400..7d75e79 100644 (file)
@@ -50,6 +50,7 @@ WebInspector.CSSStyleDeclarationSection = function(delegate, style)
     this._selectorElement.setAttribute("spellcheck", "false");
     this._selectorElement.addEventListener("mouseover", this._highlightNodesWithSelector.bind(this));
     this._selectorElement.addEventListener("mouseout", this._hideHighlightOnNodesWithSelector.bind(this));
+    this._selectorElement.addEventListener("keydown", this._handleKeyDown.bind(this));
     this._headerElement.appendChild(this._selectorElement);
 
     this._originElement = document.createElement("span");
@@ -333,6 +334,54 @@ WebInspector.CSSStyleDeclarationSection.prototype = {
             this._delegate.cssStyleDeclarationSectionEditorFocused(this);
     },
 
+    cssStyleDeclarationTextEditorSwitchRule: function(reverse)
+    {
+        if (!this._delegate)
+            return;
+
+        if (reverse && typeof this._delegate.cssStyleDeclarationSectionEditorPreviousRule === "function")
+            this._delegate.cssStyleDeclarationSectionEditorPreviousRule(this);
+        else if (!reverse && typeof this._delegate.cssStyleDeclarationSectionEditorNextRule === "function")
+            this._delegate.cssStyleDeclarationSectionEditorNextRule(this);
+    },
+
+    focusRuleSelector: function(reverse)
+    {
+        if (this.selectorLocked) {
+            this.focus();
+            return;
+        }
+
+        if (this.locked) {
+            this.cssStyleDeclarationTextEditorSwitchRule(reverse);
+            return;
+        }
+
+        var selection = window.getSelection();
+        selection.removeAllRanges();
+
+        this._element.scrollIntoViewIfNeeded();
+
+        var range = document.createRange();
+        range.selectNodeContents(this._selectorElement);
+        selection.addRange(range);
+    },
+
+    selectLastProperty: function()
+    {
+        this._propertiesTextEditor.selectLastProperty();
+    },
+
+    get selectorLocked()
+    {
+        return !this.locked && !this._style.ownerRule;
+    },
+
+    get locked()
+    {
+        return !this._style.editable;
+    },
+
     // Private
 
     _handleContextMenuEvent: function(event)
@@ -418,6 +467,25 @@ WebInspector.CSSStyleDeclarationSection.prototype = {
         DOMAgent.hideHighlight();
     },
 
+    _handleKeyDown: function(event)
+    {
+        if (event.keyCode !== 9)
+            return;
+
+        if (event.shiftKey && this._delegate && typeof this._delegate.cssStyleDeclarationSectionEditorPreviousRule === "function") {
+            event.preventDefault();
+            this._delegate.cssStyleDeclarationSectionEditorPreviousRule(this, true);
+            return;
+        }
+
+        if (!event.metaKey) {
+            event.preventDefault();
+            this.focus();
+            this._propertiesTextEditor.selectFirstProperty();
+            return;
+        }
+    },
+
     _commitSelector: function(mutations)
     {
         console.assert(this._style.ownerRule);
index 7d35f6c..6900e79 100644 (file)
@@ -57,9 +57,17 @@ WebInspector.CSSStyleDeclarationTextEditor = class CSSStyleDeclarationTextEditor
             autoCloseBrackets: true
         });
 
+        this._codeMirror.addKeyMap({
+            "Shift-Enter": this._insertNewlineAfterCurrentLine.bind(this),
+            "Shift-Tab": this._handleShiftTabKey.bind(this),
+            "Tab": this._handleTabKey.bind(this)
+        });
+
         this._completionController = new WebInspector.CodeMirrorCompletionController(this._codeMirror, this);
         this._tokenTrackingController = new WebInspector.CodeMirrorTokenTrackingController(this._codeMirror, this);
 
+        this._completionController.noEndingSemicolon = true;
+
         this._jumpToSymbolTrackingModeEnabled = false;
         this._tokenTrackingController.classNameForHighlightedRange = WebInspector.CodeMirrorTokenTrackingController.JumpToSymbolHighlightStyleClassName;
         this._tokenTrackingController.mouseOverDelayDuration = 0;
@@ -335,6 +343,28 @@ WebInspector.CSSStyleDeclarationTextEditor = class CSSStyleDeclarationTextEditor
         return true;
     }
 
+    selectFirstProperty()
+    {
+        var line = this._codeMirror.getLine(0);
+        var trimmedLine = line.trimRight();
+
+        if (!line || !trimmedLine.trimLeft().length)
+            this.clearSelection();
+
+        var index = line.indexOf(":");
+        this._codeMirror.setSelection({line: 0, ch: 0}, {line: 0, ch: index < 0 ? trimmedLine.length : index});
+    }
+
+    selectLastProperty()
+    {
+        var line = this._codeMirror.lineCount() - 1;
+        var lineText = this._codeMirror.getLine(line);
+        var trimmedLine = lineText.trimRight();
+
+        var colon = /(?::\s*)/.exec(lineText);
+        this._codeMirror.setSelection({line, ch: colon ? colon.index + colon[0].length : 0}, {line, ch: trimmedLine.length - trimmedLine.endsWith(";")});
+    }
+
     // Protected
 
     didDismissPopover(popover)
@@ -359,6 +389,146 @@ WebInspector.CSSStyleDeclarationTextEditor = class CSSStyleDeclarationTextEditor
 
     // Private
 
+    _insertNewlineAfterCurrentLine(codeMirror)
+    {
+        var cursor = codeMirror.getCursor();
+        var line = codeMirror.getLine(cursor.line);
+        var trimmedLine = line.trimRight();
+
+        cursor.ch = trimmedLine.length;
+
+        if (cursor.ch) {
+            codeMirror.replaceRange(trimmedLine.endsWith(";") ? "\n" : ";\n", cursor);
+            return;
+        }
+
+        return CodeMirror.Pass;
+    }
+
+    _handleShiftTabKey(codeMirror)
+    {
+        function switchRule()
+        {
+            if (this._delegate && typeof this._delegate.cssStyleDeclarationTextEditorSwitchRule === "function") {
+                this._delegate.cssStyleDeclarationTextEditorSwitchRule(true);
+                return;
+            }
+
+            return CodeMirror.Pass;
+        }
+
+        var cursor = codeMirror.getCursor();
+        var line = codeMirror.getLine(cursor.line);
+        var previousLine = codeMirror.getLine(cursor.line - 1);
+        var trimmedPreviousLine = previousLine ? previousLine.trimRight() : "";
+
+        if (!line && !previousLine && !cursor.line)
+            return switchRule.call(this);
+
+        if (cursor.ch === line.indexOf(":") || line.indexOf(":") < 0) {
+            if (previousLine) {
+                var colon = /(?::\s*)/.exec(previousLine);
+                codeMirror.setSelection({line: cursor.line - 1, ch: colon ? colon.index + colon[0].length : 0}, {line: cursor.line - 1, ch: trimmedPreviousLine.length - trimmedPreviousLine.endsWith(";")});
+                return;
+            }
+
+            if (cursor.line) {
+                codeMirror.setCursor(cursor.line - 1, 0);
+                return;
+            }
+
+            return switchRule.call(this);
+        }
+
+        var match = line.match(/(?:[^:;\s]\s*)+/g);
+        var lastMatch = line.indexOf(match.lastValue) + match.lastValue.length;
+        var prevHead = cursor.ch > lastMatch ? line.indexOf(match.lastValue) : line.indexOf(match[0]);
+        var prevAnchor = cursor.ch > lastMatch ? lastMatch : line.indexOf(match[0]) + match[0].length;
+
+        codeMirror.setSelection({line: cursor.line, ch: prevHead}, {line: cursor.line, ch: prevAnchor});
+    }
+
+    _handleTabKey(codeMirror)
+    {
+        function switchRule() {
+            if (this._delegate && typeof this._delegate.cssStyleDeclarationTextEditorSwitchRule === "function") {
+                this._delegate.cssStyleDeclarationTextEditorSwitchRule();
+                return;
+            }
+
+            return CodeMirror.Pass;
+        }
+
+        function highlightNextNameOrValue(text)
+        {
+            var match = text.match(/(?:[^:;\s]\s*)+/g);
+            var firstMatch = text.indexOf(match[0]) + match[0].length;
+            var nextHead = cursor.ch < firstMatch ? text.indexOf(match[0]) : text.indexOf(match[1]);
+            var nextAnchor = cursor.ch < firstMatch ? firstMatch : text.indexOf(match[1]) + match[1].length;
+
+            codeMirror.setSelection({line: cursor.line, ch: nextHead}, {line: cursor.line, ch: nextAnchor});
+        }
+
+        var cursor = codeMirror.getCursor();
+        var line = codeMirror.getLine(cursor.line);
+        var trimmedLine = line.trimRight();
+        var lastLine = cursor.line === codeMirror.lineCount() - 1;
+        var nextLine = codeMirror.getLine(cursor.line + 1);
+        var trimmedNextLine = nextLine ? nextLine.trimRight() : "";
+
+        if (!trimmedLine.trimLeft().length) {
+            if (lastLine)
+                return switchRule.call(this);
+
+            if (!trimmedNextLine.trimLeft().length) {
+                codeMirror.setCursor(cursor.line + 1, 0);
+                return;
+            }
+
+            ++cursor.line;
+            highlightNextNameOrValue(nextLine);
+            return;
+        }
+
+        if (trimmedLine.endsWith(":")) {
+            codeMirror.setCursor(cursor.line, line.length);
+            this._completionController._completeAtCurrentPosition(true);
+            return;
+        }
+
+        var hasEndingSemicolon = trimmedLine.endsWith(";");
+
+        if (cursor.ch >= line.trimRight().length - hasEndingSemicolon) {
+            if (!line.includes(":")) {
+                codeMirror.setCursor(cursor.line, line.length);
+                codeMirror.replaceRange(": ", cursor);
+                return;
+            }
+
+            var replacement = "";
+
+            if (!hasEndingSemicolon)
+                replacement += ";";
+
+            if (lastLine)
+                replacement += "\n";
+
+            if (replacement.length)
+                codeMirror.replaceRange(replacement, {line: cursor.line, ch: trimmedLine.length});
+
+            if (!nextLine) {
+                codeMirror.setCursor(cursor.line + 1, 0);
+                return;
+            }
+
+            var colon = nextLine.indexOf(":");
+            codeMirror.setSelection({line: cursor.line + 1, ch: 0}, {line: cursor.line + 1, ch: colon < 0 ? trimmedNextLine.length : colon});
+            return;
+        }
+
+        highlightNextNameOrValue(line);
+    }
+
     _clearRemoveEditingLineClassesTimeout()
     {
         if (!this._removeEditingLineClassesTimeout)
index 0e67664..871b01a 100644 (file)
@@ -302,6 +302,35 @@ WebInspector.RulesStyleDetailsPanel = class RulesStyleDetailsPanel extends WebIn
         }
     }
 
+    cssStyleDeclarationSectionEditorNextRule(currentSection)
+    {
+        currentSection.clearSelection();
+
+        var index = this._sections.indexOf(currentSection);
+        this._sections[index < this._sections.length - 1 ? index + 1 : 0].focusRuleSelector();
+    }
+
+    cssStyleDeclarationSectionEditorPreviousRule(currentSection, selectLastProperty) {
+        currentSection.clearSelection();
+
+        if (selectLastProperty || currentSection.selectorLocked) {
+            var index = this._sections.indexOf(currentSection);
+            index = index > 0 ? index - 1 : this._sections.length - 1;
+
+            var section = this._sections[index];
+            while (section.locked) {
+                index = index > 0 ? index - 1 : this._sections.length - 1;
+                section = this._sections[index];
+            }
+
+            section.focus();
+            section.selectLastProperty();
+            return;
+        }
+
+        currentSection.focusRuleSelector(true);
+    }
+
     filterDidChange(filterBar)
     {
         for (var labels of this._ruleMediaAndInherticanceList) {