Web Inspector: implement smart braces functionality
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Feb 2013 12:30:27 +0000 (12:30 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Feb 2013 12:30:27 +0000 (12:30 +0000)
https://bugs.webkit.org/show_bug.cgi?id=109200

Patch by Andrey Lushnikov <lushnikov@chromium.org> on 2013-02-15
Reviewed by Pavel Feldman.

Source/WebCore:

- implement SmartBraceController which will handle character insertions
and override them if brace character was inserted. Additionally it
should handle Backspace key and override it if a cursor is located
inside of a bracket pair.
- guard smart brace functionality via experiment checkbox.

New test: inspector/editor/text-editor-smart-braces.html

* inspector/front-end/DefaultTextEditor.js:
(WebInspector.TextEditorMainPanel):
(WebInspector.TextEditorMainPanel.prototype._registerShortcuts):
(WebInspector.TextEditorMainPanel.prototype._handleKeyPress):
(WebInspector.TextEditorMainPanel.SmartBraceController):
(WebInspector.TextEditorMainPanel.SmartBraceController.prototype.registerShortcuts):
(WebInspector.TextEditorMainPanel.SmartBraceController.prototype.registerCharOverrides):
(WebInspector.TextEditorMainPanel.SmartBraceController.prototype._handleBackspace):
(WebInspector.TextEditorMainPanel.SmartBraceController.prototype._handleBracePairInsertion):
(WebInspector.TextEditorMainPanel.SmartBraceController.prototype._handleClosingBraceOverride):
* inspector/front-end/Settings.js:
(WebInspector.ExperimentsSettings):

Tools:

Fix eventSender.keyDown implementation to correctly process opening
round brace symbol.

* DumpRenderTree/chromium/TestRunner/src/EventSender.cpp:
(WebTestRunner):
(WebTestRunner::EventSender::keyDown):

LayoutTests:

* inspector/editor/text-editor-smart-braces-expected.txt: Added.
* inspector/editor/text-editor-smart-braces.html: Added.
* platform/efl/TestExpectations:
* platform/mac/TestExpectations:
* platform/qt/TestExpectations:

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

LayoutTests/ChangeLog
LayoutTests/inspector/editor/text-editor-smart-braces-expected.txt [new file with mode: 0644]
LayoutTests/inspector/editor/text-editor-smart-braces.html [new file with mode: 0644]
LayoutTests/platform/efl/TestExpectations
LayoutTests/platform/mac/TestExpectations
LayoutTests/platform/qt/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/inspector/front-end/DefaultTextEditor.js
Source/WebCore/inspector/front-end/Settings.js
Tools/ChangeLog
Tools/DumpRenderTree/chromium/TestRunner/src/EventSender.cpp

index 292aeb85927cd024bd2b2fec19694c6040d6364e..014df9562540a86a3376226c56dab801c21255dd 100644 (file)
@@ -1,3 +1,16 @@
+2013-02-15  Andrey Lushnikov  <lushnikov@chromium.org>
+
+        Web Inspector: implement smart braces functionality
+        https://bugs.webkit.org/show_bug.cgi?id=109200
+
+        Reviewed by Pavel Feldman.
+
+        * inspector/editor/text-editor-smart-braces-expected.txt: Added.
+        * inspector/editor/text-editor-smart-braces.html: Added.
+        * platform/efl/TestExpectations:
+        * platform/mac/TestExpectations:
+        * platform/qt/TestExpectations:
+
 2013-02-15  Andrei Bucur  <abucur@adobe.com>
 
         [CSS Regions][Mac] fast/regions/full-screen-video-from-region.html hits an assertion in RenderFlowThread::removeRenderBoxRegionInfo
diff --git a/LayoutTests/inspector/editor/text-editor-smart-braces-expected.txt b/LayoutTests/inspector/editor/text-editor-smart-braces-expected.txt
new file mode 100644 (file)
index 0000000..0acc37e
--- /dev/null
@@ -0,0 +1,63 @@
+This test checks text editor smart braces functionality.
+
+
+Running: typeFunctionName
+function myMax|
+
+Running: typeOpeningRoundBrace
+function myMax(|)
+
+Running: typeFunctionArguments
+function myMax(a, b|)
+
+Running: typeClosingBrace
+function myMax(a, b)|
+
+Running: typeOpeningCurlyBrace
+function myMax(a, b){|}
+
+Running: typeEnter
+function myMax(a, b){
+    |
+}
+
+Running: typeReturnStatement
+function myMax(a, b){
+    return |
+}
+
+Running: typeNestedBraces
+function myMax(a, b){
+    return ({(|)})
+}
+
+Running: hitBackspaceTrippleTimes
+function myMax(a, b){
+    return |
+}
+
+Running: typeMathMax
+function myMax(a, b){
+    return Math.max(a, b);|
+}
+
+Running: setCursorBeforeClosingCurlyBrace
+function myMax(a, b){
+    return Math.max(a, b);
+|}
+
+Running: typeInClosingCurlyBrace
+function myMax(a, b){
+    return Math.max(a, b);
+}|
+
+Running: gotoVeryBeginning
+|function myMax(a, b){
+    return Math.max(a, b);
+}
+
+Running: typeOpeningRoundAndCurlyBraces
+({|function myMax(a, b){
+    return Math.max(a, b);
+}
+
diff --git a/LayoutTests/inspector/editor/text-editor-smart-braces.html b/LayoutTests/inspector/editor/text-editor-smart-braces.html
new file mode 100644 (file)
index 0000000..b5a501d
--- /dev/null
@@ -0,0 +1,139 @@
+<html>
+<head>
+<script src="../../http/tests/inspector/inspector-test.js"></script>
+<script src="editor-test.js"></script>
+<script>
+
+function test()
+{
+    WebInspector.experimentsSettings.textEditorSmartBraces.enableForTest();
+
+    var textEditor = InspectorTest.createTestEditor();
+    textEditor.overrideViewportForTest(0, undefined, 3);
+    textEditor.mimeType = "text/javascript";
+    textEditor.setReadOnly(false);
+    textEditor.element.focus();
+
+    textEditor.setText("");
+    textEditor.setSelection(WebInspector.TextRange.createFromLocation(0, 0));
+
+    function dump(next)
+    {
+        InspectorTest.addResult(InspectorTest.textWithSelection(textEditor.text(), textEditor.selection()));
+        next();
+    }
+
+    function waitAndDump(next)
+    {
+        setTimeout(dump.bind(this, next));
+    }
+
+    function keyDown(character)
+    {
+        eventSender.keyDown(character, []);
+    }
+
+    var insertText = textInputController.insertText.bind(textInputController);
+
+    InspectorTest.runTestSuite([
+        function typeFunctionName(next)
+        {
+            insertText("function myMax");
+            waitAndDump(next);
+        },
+
+        function typeOpeningRoundBrace(next)
+        {
+            keyDown("(");
+            dump(next);
+        },
+
+        function typeFunctionArguments(next)
+        {
+            insertText("a, b");
+            waitAndDump(next);
+        },
+
+        function typeClosingBrace(next)
+        {
+            keyDown(")");
+            dump(next);
+        },
+
+        function typeOpeningCurlyBrace(next)
+        {
+            keyDown("{");
+            dump(next);
+        },
+
+        function typeEnter(next)
+        {
+            keyDown("\n");
+            dump(next);
+        },
+
+        function typeReturnStatement(next)
+        {
+            insertText("return ");
+            waitAndDump(next);
+        },
+
+        function typeNestedBraces(next)
+        {
+            keyDown("(");
+            keyDown("{");
+            keyDown("(");
+            dump(next);
+        },
+
+        function hitBackspaceTrippleTimes(next)
+        {
+            keyDown("\b");
+            keyDown("\b");
+            keyDown("\b");
+            dump(next);
+        },
+
+        function typeMathMax(next)
+        {
+            insertText("Math.max(a, b);");
+            waitAndDump(next);
+        },
+
+        function setCursorBeforeClosingCurlyBrace(next)
+        {
+            textEditor.setSelection(WebInspector.TextRange.createFromLocation(2, 0));
+            dump(next);
+        },
+
+        function typeInClosingCurlyBrace(next)
+        {
+            keyDown("}");
+            dump(next);
+        },
+
+        function gotoVeryBeginning(next)
+        {
+            textEditor.setSelection(WebInspector.TextRange.createFromLocation(0, 0));
+            dump(next);
+        },
+
+        function typeOpeningRoundAndCurlyBraces(next)
+        {
+            keyDown("(");
+            keyDown("{");
+            waitAndDump(next);
+        }
+    ]);
+}
+
+</script>
+</head>
+
+<body onload="runTest();">
+<p>
+This test checks text editor smart braces functionality.
+</p>
+
+</body>
+</html>
index 0daa8630a860d31b352135542c110dd0e18d0c55..a28e368a7d56494cf6c4769504d26a721e476072 100644 (file)
@@ -1831,6 +1831,7 @@ webkit.org/b/106743 media/video-controls-captions.html [ Failure ]
 inspector/editor/text-editor-formatter.html
 inspector/editor/text-editor-word-jumps.html
 inspector/editor/text-editor-home-button.html
+inspector/editor/text-editor-smart-braces.html
 
 # Remove from list after enabling CANVAS_PATH
 webkit.org/b/108508 fast/canvas/canvas-path-constructors.html [ Failure ]
index 1f7bacf0ba3058e834a8b9279e01a9608b00a8c3..99b78e18bdd8e150a5ec8da339cc97a5630f9b17 100644 (file)
@@ -258,6 +258,7 @@ animations/play-state.html
 inspector/editor/text-editor-formatter.html [ Skip ]
 inspector/editor/text-editor-word-jumps.html [ Skip ]
 inspector/editor/text-editor-home-button.html [ Skip ]
+inspector/editor/text-editor-smart-braces.html [ Skip ]
 
 # https://bugs.webkit.org/show_bug.cgi?id=71120
 inspector/debugger/selected-call-frame-after-formatting-source.html
index 3bc9327e6d1045d52442c9a8bff870c88e0e3c00..5f18dd49c4a9f5186f3f81d2137dc8da09be0382 100644 (file)
@@ -2608,6 +2608,7 @@ webkit.org/b/39725 fast/events/drag-and-drop-autoscroll.html [ Skip ]
 inspector/editor/text-editor-formatter.html
 inspector/editor/text-editor-word-jumps.html
 inspector/editor/text-editor-home-button.html
+inspector/editor/text-editor-smart-braces.html
 
 # [Qt] REGRESSION(r141634) test failing
 webkit.org/b/108813 compositing/visibility/visibility-image-layers-dynamic.html [ Skip ]
index 65a2140e18e911b3985927858cfddf60d03d528c..2c734397ee58927c4d80cced2416fbd3838bbbac 100644 (file)
@@ -1,3 +1,31 @@
+2013-02-15  Andrey Lushnikov  <lushnikov@chromium.org>
+
+        Web Inspector: implement smart braces functionality
+        https://bugs.webkit.org/show_bug.cgi?id=109200
+
+        Reviewed by Pavel Feldman.
+
+        - implement SmartBraceController which will handle character insertions
+        and override them if brace character was inserted. Additionally it
+        should handle Backspace key and override it if a cursor is located
+        inside of a bracket pair.
+        - guard smart brace functionality via experiment checkbox.
+
+        New test: inspector/editor/text-editor-smart-braces.html
+
+        * inspector/front-end/DefaultTextEditor.js:
+        (WebInspector.TextEditorMainPanel):
+        (WebInspector.TextEditorMainPanel.prototype._registerShortcuts):
+        (WebInspector.TextEditorMainPanel.prototype._handleKeyPress):
+        (WebInspector.TextEditorMainPanel.SmartBraceController):
+        (WebInspector.TextEditorMainPanel.SmartBraceController.prototype.registerShortcuts):
+        (WebInspector.TextEditorMainPanel.SmartBraceController.prototype.registerCharOverrides):
+        (WebInspector.TextEditorMainPanel.SmartBraceController.prototype._handleBackspace):
+        (WebInspector.TextEditorMainPanel.SmartBraceController.prototype._handleBracePairInsertion):
+        (WebInspector.TextEditorMainPanel.SmartBraceController.prototype._handleClosingBraceOverride):
+        * inspector/front-end/Settings.js:
+        (WebInspector.ExperimentsSettings):
+
 2013-02-15  Andrei Bucur  <abucur@adobe.com>
 
         [CSS Regions][Mac] fast/regions/full-screen-video-from-region.html hits an assertion in RenderFlowThread::removeRenderBoxRegionInfo
index 19e8e408a0e255146e17a67b340a3f6b672d2c0d..546f3fc108826f0095badd5d334176bb0aeb891c 100644 (file)
@@ -1363,6 +1363,7 @@ WebInspector.TextEditorMainPanel = function(delegate, textModel, url, syncScroll
     this.element.addEventListener("focus", this._handleElementFocus.bind(this), false);
     this.element.addEventListener("textInput", this._handleTextInput.bind(this), false);
     this.element.addEventListener("cut", this._handleCut.bind(this), false);
+    this.element.addEventListener("keypress", this._handleKeyPress.bind(this), false);
 
     this._showWhitespace = WebInspector.experimentsSettings.showWhitespaceInEditor.isEnabled();
 
@@ -1373,6 +1374,7 @@ WebInspector.TextEditorMainPanel = function(delegate, textModel, url, syncScroll
     this._tokenHighlighter = new WebInspector.TextEditorMainPanel.TokenHighlighter(this, textModel);
     this._braceMatcher = new WebInspector.TextEditorModel.BraceMatcher(textModel);
     this._braceHighlighter = new WebInspector.TextEditorMainPanel.BraceHighlightController(this, textModel, this._braceMatcher);
+    this._smartBraceController = new WebInspector.TextEditorMainPanel.SmartBraceController(this, textModel, this._braceMatcher);
 
     this._freeCachedElements();
     this.buildChunks();
@@ -1412,6 +1414,22 @@ WebInspector.TextEditorMainPanel.prototype = {
         var homeModifier = WebInspector.isMac() ? modifiers.Meta : modifiers.None;
         this._shortcuts[WebInspector.KeyboardShortcut.makeKey(homeKey.code, homeModifier)] = this._handleHomeKey.bind(this, false);
         this._shortcuts[WebInspector.KeyboardShortcut.makeKey(homeKey.code, homeModifier | modifiers.Shift)] = this._handleHomeKey.bind(this, true);
+
+        this._charOverrides = {};
+
+        this._smartBraceController.registerShortcuts(this._shortcuts);
+        this._smartBraceController.registerCharOverrides(this._charOverrides);
+    },
+
+    _handleKeyPress: function(event)
+    {
+        var char = String.fromCharCode(event.which);
+        var handler = this._charOverrides[char];
+        if (handler && handler()) {
+            event.consume(true);
+            return;
+        }
+        this._keyDownCode = event.keyCode;
     },
 
     /**
@@ -3467,4 +3485,116 @@ WebInspector.TextEditorMainPanel.BraceHighlightController.prototype = {
     }
 }
 
+/**
+ * @constructor
+ * @param {WebInspector.TextEditorMainPanel} mainPanel
+ * @param {WebInspector.TextEditorModel} textModel
+ * @param {WebInspector.TextEditorModel.BraceMatcher} braceMatcher
+ */
+WebInspector.TextEditorMainPanel.SmartBraceController = function(mainPanel, textModel, braceMatcher)
+{
+    this._mainPanel = mainPanel;
+    this._textModel = textModel;
+    this._braceMatcher = braceMatcher
+}
+
+WebInspector.TextEditorMainPanel.SmartBraceController.prototype = {
+    /**
+     * @param {Object.<number, function()>} shortcuts
+     */
+    registerShortcuts: function(shortcuts)
+    {
+        if (!WebInspector.experimentsSettings.textEditorSmartBraces.isEnabled())
+            return;
+
+        var keys = WebInspector.KeyboardShortcut.Keys;
+        var modifiers = WebInspector.KeyboardShortcut.Modifiers;
+
+        shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Backspace.code, modifiers.None)] = this._handleBackspace.bind(this);
+    },
+
+    /**
+     * @param {Object.<string, function()>} charOverrides
+     */
+    registerCharOverrides: function(charOverrides)
+    {
+        if (!WebInspector.experimentsSettings.textEditorSmartBraces.isEnabled())
+            return;
+        charOverrides["("] = this._handleBracePairInsertion.bind(this, "()");
+        charOverrides[")"] = this._handleClosingBraceOverride.bind(this, ")");
+        charOverrides["{"] = this._handleBracePairInsertion.bind(this, "{}");
+        charOverrides["}"] = this._handleClosingBraceOverride.bind(this, "}");
+    },
+
+    _handleBackspace: function()
+    {
+        var selection = this._mainPanel.lastSelection();
+        if (!selection || !selection.isEmpty())
+            return false;
+
+        var column = selection.startColumn;
+        if (column == 0)
+            return false;
+
+        var lineNumber = selection.startLine;
+        var line = this._textModel.line(lineNumber);
+        if (column === line.length)
+            return false;
+
+        var pair = line.substr(column - 1, 2);
+        if (pair === "()" || pair === "{}") {
+            this._textModel.editRange(new WebInspector.TextRange(lineNumber, column - 1, lineNumber, column + 1), "");
+            this._mainPanel.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, column - 1));
+            return true;
+        } else
+            return false;
+    },
+
+    /**
+     * @param {string} bracePair
+     * @return {boolean}
+     */
+    _handleBracePairInsertion: function(bracePair)
+    {
+        var selection = this._mainPanel.lastSelection().normalize();
+        if (selection.isEmpty()) {
+            var lineNumber = selection.startLine;
+            var column = selection.startColumn;
+            var line = this._textModel.line(lineNumber);
+            if (column < line.length) {
+                var char = line.charAt(column);
+                if (WebInspector.TextUtils.isWordChar(char) || (!WebInspector.TextUtils.isBraceChar(char) && WebInspector.TextUtils.isStopChar(char)))
+                    return false;
+            }
+        }
+        this._textModel.editRange(selection, bracePair);
+        this._mainPanel.setSelection(WebInspector.TextRange.createFromLocation(selection.startLine, selection.startColumn + 1));
+        return true;
+    },
+
+    /**
+     * @param {string} brace
+     * @return {boolean}
+     */
+    _handleClosingBraceOverride: function(brace)
+    {
+        var selection = this._mainPanel.lastSelection().normalize();
+        if (!selection || !selection.isEmpty())
+            return false;
+
+        var lineNumber = selection.startLine;
+        var column = selection.startColumn;
+        var line = this._textModel.line(lineNumber);
+        if (line.charAt(column) !== brace)
+            return false;
+
+        var braces = this._braceMatcher.enclosingBraces(lineNumber, column);
+        if (braces && braces.rightBrace.lineNumber === lineNumber && braces.rightBrace.column === column) {
+            this._mainPanel.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, column + 1));
+            return true;
+        } else
+            return false;
+    },
+}
+
 WebInspector.debugDefaultTextEditor = false;
index 6703ebe476f02cbfed2a292477edbd81bd43196c..9fdbbd31a9ffa6a22d8880c87b48e4fdf851ee5b 100644 (file)
@@ -214,6 +214,7 @@ WebInspector.ExperimentsSettings = function()
     this.fileSystemProject = this._createExperiment("fileSystemProject", "File system folders in Sources Panel");
     this.horizontalPanelSplit = this._createExperiment("horizontalPanelSplit", "Allow horizontal split in Elements and Sources panels");
     this.showWhitespaceInEditor = this._createExperiment("showWhitespaceInEditor", "Show whitespace characters in editor");
+    this.textEditorSmartBraces = this._createExperiment("textEditorSmartBraces", "Enable smart braces in text editor");
 
     this._cleanUpSetting();
 }
index 2a5a4a1dbb31977eedafabf0c8d80a189e2b8dd8..28398de13e23a57a22b2f0ec67d30ab42fc881ee 100644 (file)
@@ -1,3 +1,17 @@
+2013-02-15  Andrey Lushnikov  <lushnikov@chromium.org>
+
+        Web Inspector: implement smart braces functionality
+        https://bugs.webkit.org/show_bug.cgi?id=109200
+
+        Reviewed by Pavel Feldman.
+
+        Fix eventSender.keyDown implementation to correctly process opening
+        round brace symbol.
+
+        * DumpRenderTree/chromium/TestRunner/src/EventSender.cpp:
+        (WebTestRunner):
+        (WebTestRunner::EventSender::keyDown):
+
 2013-02-15  Jochen Eisinger  <jochen@chromium.org>
 
         Speculative build fix for chromium-win.
index 384ecb9a5af9d624f1e51f1e263d660a225d56f3..008a010c3f1093439bac2382ab2fb394ed5c1b9f 100644 (file)
@@ -614,6 +614,11 @@ void EventSender::keyDown(const CppArgumentList& arguments, CppVariant* result)
                 code -= 'a' - 'A';
             generateChar = true;
         }
+
+        if ("(" == codeStr) {
+            code = '9';
+            needsShiftKeyModifier = true;
+        }
     }
 
     // For one generated keyboard event, we need to generate a keyDown/keyUp