Web Inspector: highlight matching braces in DTE.
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Feb 2013 11:51:08 +0000 (11:51 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Feb 2013 11:51:08 +0000 (11:51 +0000)
https://bugs.webkit.org/show_bug.cgi?id=108697

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

Source/WebCore:

Implement BraceMatcher class which for given position in textModel
will respond with enclosing brace pair for that position.
Make use of this class in DefaultTextEditor by handling
selectionChange event. Make use of this class in "_closingBlockOffset"
method of TextEditorMainPanel as this method implements similar
functionality.

New test: inspector/editor/brace-matcher.html

* inspector/front-end/DefaultTextEditor.js:
(WebInspector.TextEditorMainPanel):
(WebInspector.TextEditorMainPanel.prototype._applyDomUpdates):
(WebInspector.TextEditorMainPanel.prototype._closingBlockOffset):
(WebInspector.TextEditorMainPanel.prototype._handleSelectionChange):
(WebInspector.TextEditorMainPanel.BraceHighlightController):
(WebInspector.TextEditorMainPanel.BraceHighlightController.prototype.handleSelectionChange):
* inspector/front-end/TextEditorHighlighter.js:
(WebInspector.TextEditorHighlighter.prototype._highlightLines):
* inspector/front-end/TextEditorModel.js:
(WebInspector.TextEditorModel.endsWithBracketRegex):
(WebInspector.TextEditorModel.endsWithBracketRegex.):
* inspector/front-end/textEditor.css:
(.text-editor-brace-match):

LayoutTests:

New layout test to verify brace matching functionality. Fix some
layout test expectations as the patch removes braces from highlight
ranges.

* inspector/editor/brace-matcher-expected.txt: Added.
* inspector/editor/brace-matcher.html: Added.
* inspector/editor/highlighter-basics-expected.txt:
* inspector/editor/text-editor-long-line-expected.txt:

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

LayoutTests/ChangeLog
LayoutTests/inspector/editor/brace-matcher-expected.txt [new file with mode: 0644]
LayoutTests/inspector/editor/brace-matcher.html [new file with mode: 0644]
LayoutTests/inspector/editor/highlighter-basics-expected.txt
LayoutTests/inspector/editor/text-editor-long-line-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/inspector/front-end/DefaultTextEditor.js
Source/WebCore/inspector/front-end/TextEditorHighlighter.js
Source/WebCore/inspector/front-end/TextEditorModel.js
Source/WebCore/inspector/front-end/textEditor.css

index b09546a221448f203051c6c73f8e2cc7f99df78c..350d06929f28243a289342fbc8c9223c71b25e5e 100644 (file)
@@ -1,3 +1,19 @@
+2013-02-07  Andrey Lushnikov  <lushnikov@chromium.org>
+
+        Web Inspector: highlight matching braces in DTE.
+        https://bugs.webkit.org/show_bug.cgi?id=108697
+
+        Reviewed by Pavel Feldman.
+
+        New layout test to verify brace matching functionality. Fix some
+        layout test expectations as the patch removes braces from highlight
+        ranges.
+
+        * inspector/editor/brace-matcher-expected.txt: Added.
+        * inspector/editor/brace-matcher.html: Added.
+        * inspector/editor/highlighter-basics-expected.txt:
+        * inspector/editor/text-editor-long-line-expected.txt:
+
 2013-02-07  Matt Falkenhagen  <falken@chromium.org>
 
         Rollout r142058 various crashes and timeouts on AppleMac and Chromium
diff --git a/LayoutTests/inspector/editor/brace-matcher-expected.txt b/LayoutTests/inspector/editor/brace-matcher-expected.txt
new file mode 100644 (file)
index 0000000..dcb9930
--- /dev/null
@@ -0,0 +1,81 @@
+This test checks highlighter correctness.
+
+function bar() {
+    // comment here
+    var a, b, c, d;
+    var a = ((1 + 2) * (3 + 4));
+    for(var i = 0; i < 100; ++i) {
+        if (a < b && c > d) {
+        }
+    }
+}
+Cursor at line #0 >>>function bar() {<<< column 0 (char is "f")
+enclosing braces: null
+
+Cursor at line #0 >>>function bar() {<<< column 12 (char is "(")
+enclosing braces: {"leftBrace":{"lineNumber":0,"column":12,"token":"brace-start"},"rightBrace":{"lineNumber":0,"column":13,"token":"brace-end"}}
+
+Cursor at line #0 >>>function bar() {<<< column 13 (char is ")")
+enclosing braces: {"leftBrace":{"lineNumber":0,"column":12,"token":"brace-start"},"rightBrace":{"lineNumber":0,"column":13,"token":"brace-end"}}
+
+Cursor at line #0 >>>function bar() {<<< column 14 (char is " ")
+enclosing braces: null
+
+Cursor at line #0 >>>function bar() {<<< column 15 (char is "{")
+enclosing braces: {"leftBrace":{"lineNumber":0,"column":15,"token":"block-start"},"rightBrace":{"lineNumber":8,"column":0,"token":"block-end"}}
+
+Cursor at line #2 >>>    var a, b, c, d;<<< column 5 (char is "a")
+enclosing braces: {"leftBrace":{"lineNumber":0,"column":15,"token":"block-start"},"rightBrace":{"lineNumber":8,"column":0,"token":"block-end"}}
+
+Cursor at line #3 >>>    var a = ((1 + 2) * (3 + 4));<<< column 12 (char is "(")
+enclosing braces: {"leftBrace":{"lineNumber":3,"column":12,"token":"brace-start"},"rightBrace":{"lineNumber":3,"column":30,"token":"brace-end"}}
+
+Cursor at line #3 >>>    var a = ((1 + 2) * (3 + 4));<<< column 13 (char is "(")
+enclosing braces: {"leftBrace":{"lineNumber":3,"column":13,"token":"brace-start"},"rightBrace":{"lineNumber":3,"column":19,"token":"brace-end"}}
+
+Cursor at line #3 >>>    var a = ((1 + 2) * (3 + 4));<<< column 14 (char is "1")
+enclosing braces: {"leftBrace":{"lineNumber":3,"column":13,"token":"brace-start"},"rightBrace":{"lineNumber":3,"column":19,"token":"brace-end"}}
+
+Cursor at line #3 >>>    var a = ((1 + 2) * (3 + 4));<<< column 15 (char is " ")
+enclosing braces: {"leftBrace":{"lineNumber":3,"column":13,"token":"brace-start"},"rightBrace":{"lineNumber":3,"column":19,"token":"brace-end"}}
+
+Cursor at line #3 >>>    var a = ((1 + 2) * (3 + 4));<<< column 22 (char is " ")
+enclosing braces: {"leftBrace":{"lineNumber":3,"column":12,"token":"brace-start"},"rightBrace":{"lineNumber":3,"column":30,"token":"brace-end"}}
+
+Cursor at line #3 >>>    var a = ((1 + 2) * (3 + 4));<<< column 24 (char is "3")
+enclosing braces: {"leftBrace":{"lineNumber":3,"column":23,"token":"brace-start"},"rightBrace":{"lineNumber":3,"column":29,"token":"brace-end"}}
+
+Cursor at line #3 >>>    var a = ((1 + 2) * (3 + 4));<<< column 29 (char is ")")
+enclosing braces: {"leftBrace":{"lineNumber":3,"column":23,"token":"brace-start"},"rightBrace":{"lineNumber":3,"column":29,"token":"brace-end"}}
+
+Cursor at line #3 >>>    var a = ((1 + 2) * (3 + 4));<<< column 30 (char is ")")
+enclosing braces: {"leftBrace":{"lineNumber":3,"column":12,"token":"brace-start"},"rightBrace":{"lineNumber":3,"column":30,"token":"brace-end"}}
+
+Cursor at line #3 >>>    var a = ((1 + 2) * (3 + 4));<<< column 31 (char is ";")
+enclosing braces: {"leftBrace":{"lineNumber":0,"column":15,"token":"block-start"},"rightBrace":{"lineNumber":8,"column":0,"token":"block-end"}}
+
+Cursor at line #4 >>>    for(var i = 0; i < 100; ++i) {<<< column 19 (char is "i")
+enclosing braces: {"leftBrace":{"lineNumber":4,"column":7,"token":"brace-start"},"rightBrace":{"lineNumber":4,"column":31,"token":"brace-end"}}
+
+Cursor at line #4 >>>    for(var i = 0; i < 100; ++i) {<<< column 32 (char is " ")
+enclosing braces: {"leftBrace":{"lineNumber":0,"column":15,"token":"block-start"},"rightBrace":{"lineNumber":8,"column":0,"token":"block-end"}}
+
+Cursor at line #4 >>>    for(var i = 0; i < 100; ++i) {<<< column 33 (char is "{")
+enclosing braces: {"leftBrace":{"lineNumber":4,"column":33,"token":"block-start"},"rightBrace":{"lineNumber":7,"column":4,"token":"block-end"}}
+
+Cursor at line #5 >>>        if (a < b && c > d) {<<< column 20 (char is " ")
+enclosing braces: {"leftBrace":{"lineNumber":5,"column":11,"token":"brace-start"},"rightBrace":{"lineNumber":5,"column":26,"token":"brace-end"}}
+
+Cursor at line #5 >>>        if (a < b && c > d) {<<< column 28 (char is "{")
+enclosing braces: {"leftBrace":{"lineNumber":5,"column":28,"token":"block-start"},"rightBrace":{"lineNumber":6,"column":8,"token":"block-end"}}
+
+Cursor at line #6 >>>        }<<< column 8 (char is "}")
+enclosing braces: {"leftBrace":{"lineNumber":5,"column":28,"token":"block-start"},"rightBrace":{"lineNumber":6,"column":8,"token":"block-end"}}
+
+Cursor at line #7 >>>    }<<< column 4 (char is "}")
+enclosing braces: {"leftBrace":{"lineNumber":4,"column":33,"token":"block-start"},"rightBrace":{"lineNumber":7,"column":4,"token":"block-end"}}
+
+Cursor at line #8 >>>}<<< column 0 (char is "}")
+enclosing braces: {"leftBrace":{"lineNumber":0,"column":15,"token":"block-start"},"rightBrace":{"lineNumber":8,"column":0,"token":"block-end"}}
+
+
diff --git a/LayoutTests/inspector/editor/brace-matcher.html b/LayoutTests/inspector/editor/brace-matcher.html
new file mode 100644 (file)
index 0000000..c896925
--- /dev/null
@@ -0,0 +1,73 @@
+<html>
+<head>
+<script src="../../http/tests/inspector/inspector-test.js"></script>
+<script src="editor-test.js"></script>
+<script>
+
+function test()
+{
+function bar() {
+    // comment here
+    var a, b, c, d;
+    var a = ((1 + 2) * (3 + 4));
+    for(var i = 0; i < 100; ++i) {
+        if (a < b && c > d) {
+        }
+    }
+}
+
+
+    var textEditor = InspectorTest.createTestEditor();
+    textEditor.overrideViewportForTest(0, undefined, 3);
+    textEditor.mimeType = "text/javascript";
+    textEditor.setReadOnly(false);
+    textEditor.setText(bar.toString());
+    InspectorTest.addResult(textEditor.text());
+
+    var braceHighlighter = new WebInspector.TextEditorModel.BraceMatcher(textEditor._textModel);
+    function testAndDump(lineNumber, column)
+    {
+        var line = textEditor._textModel.line(lineNumber);
+        var text = "Cursor at line #" + (lineNumber) + " >>>" + line + "<<< column " + column + " (char is \"" + line.charAt(column) + "\")";
+        text += "\nenclosing braces: " + JSON.stringify(braceHighlighter.enclosingBraces(lineNumber, column));
+        text += "\n";
+        InspectorTest.addResult(text);
+    }
+
+    testAndDump(0, 0);
+    testAndDump(0, 12);
+    testAndDump(0, 13);
+    testAndDump(0, 14);
+    testAndDump(0, 15);
+    testAndDump(2, 5);
+    testAndDump(3, 12);
+    testAndDump(3, 13);
+    testAndDump(3, 14);
+    testAndDump(3, 15);
+    testAndDump(3, 22);
+    testAndDump(3, 24);
+    testAndDump(3, 29);
+    testAndDump(3, 30);
+    testAndDump(3, 31);
+    testAndDump(4, 19);
+    testAndDump(4, 32);
+    testAndDump(4, 33);
+    testAndDump(5, 20);
+    testAndDump(5, 28);
+    testAndDump(6, 8);
+    testAndDump(7, 4);
+    testAndDump(8, 0);
+
+    InspectorTest.completeTest();
+}
+
+</script>
+</head>
+
+<body onload="runTest()">
+<p>
+This test checks highlighter correctness.
+</p>
+
+</body>
+</html>
index b6634fcc7df24ae3f19fdb5799f771ff5cf56831..8e6719d6b146b3078915074a7259026e7debece9 100644 (file)
@@ -91,7 +91,7 @@ After highlighting up to the end
 15 :  * line #6 : whitespace[0-1] whitespace[2-3] javascript-ident[3-7] whitespace[7-8] javascript-number[9-10]
 16 :  * line #7 : whitespace[0-1] whitespace[2-3] javascript-ident[3-7] whitespace[7-8] javascript-number[9-10]
 17 :  * line #8 : whitespace[0-1] whitespace[2-3] javascript-ident[3-7] whitespace[7-8] javascript-number[9-10]
-18 :  * var a = new A(); //*/ : whitespace[0-1] whitespace[2-3] javascript-keyword[3-6] whitespace[6-7] javascript-ident[7-8] whitespace[8-9] whitespace[10-11] javascript-keyword[11-14] whitespace[14-15] javascript-ident[15-16] brace-start[16-17] brace-end[17-18] whitespace[19-20] javascript-comment[20-24]
+18 :  * var a = new A(); //*/ : whitespace[0-1] whitespace[2-3] javascript-keyword[3-6] whitespace[6-7] javascript-ident[7-8] whitespace[8-9] whitespace[10-11] javascript-keyword[11-14] whitespace[14-15] javascript-ident[15-16] whitespace[19-20] javascript-comment[20-24]
 19 : some text : javascript-ident[0-4] whitespace[4-5] javascript-ident[5-9]
 20 :  :
 
@@ -113,7 +113,7 @@ After changing the first line, and updating the highlights for the first 5 lines
 15 :  * line #6 : whitespace[0-1] whitespace[2-3] javascript-ident[3-7] whitespace[7-8] javascript-number[9-10]
 16 :  * line #7 : whitespace[0-1] whitespace[2-3] javascript-ident[3-7] whitespace[7-8] javascript-number[9-10]
 17 :  * line #8 : whitespace[0-1] whitespace[2-3] javascript-ident[3-7] whitespace[7-8] javascript-number[9-10]
-18 :  * var a = new A(); //*/ : whitespace[0-1] whitespace[2-3] javascript-keyword[3-6] whitespace[6-7] javascript-ident[7-8] whitespace[8-9] whitespace[10-11] javascript-keyword[11-14] whitespace[14-15] javascript-ident[15-16] brace-start[16-17] brace-end[17-18] whitespace[19-20] javascript-comment[20-24]
+18 :  * var a = new A(); //*/ : whitespace[0-1] whitespace[2-3] javascript-keyword[3-6] whitespace[6-7] javascript-ident[7-8] whitespace[8-9] whitespace[10-11] javascript-keyword[11-14] whitespace[14-15] javascript-ident[15-16] whitespace[19-20] javascript-comment[20-24]
 19 : some text : javascript-ident[0-4] whitespace[4-5] javascript-ident[5-9]
 20 :  :
 
index 8dcd89111429a26e0f7ec7895283e36f86b32fb1..8f936b8e68d240318406fedee6b8351672edf279 100644 (file)
@@ -2,5 +2,5 @@ This test checks that text editor doesn't paint highlight for too long lines.
 
 
 <div class="inner-container" tabindex="0">
-<div class="webkit-line-content"><span class="webkit-javascript-comment">/* START */</span><span class="webkit-whitespace"> </span><span class="webkit-javascript-keyword">this</span>.<span class="webkit-javascript-ident">field</span><span class="webkit-whitespace"> </span>=<span class="webkit-whitespace"> </span><span class="webkit-javascript-string">"foo"</span>;<span class="webkit-whitespace"> </span><span class="webkit-javascript-comment">/* comment */</span><span class="webkit-whitespace"> </span><span class="webkit-javascript-keyword">function</span><span class="webkit-whitespace"> </span><span class="webkit-javascript-ident">bar</span><span class="webkit-brace-start">(</span><span class="webkit-brace-end">)</span><span class="webkit-whitespace"> </span><span class="webkit-block-start">{</span><span class="webkit-whitespace"> </span><span class="webkit-javascript-keyword">return</span><span class="webkit-whitespace"> </span><span class="webkit-javascript-keyword">null</span>;<span class="webkit-whitespace"> </span><span class="webkit-block-end">}</span><span class="webkit-javascript-keyword">this</span>.<span class="webkit-javascript-ident">field</span><span class="webkit-whitespace"> </span>=<span class="webkit-whitespace"> </span><span class="webkit-javascript-string">"foo"</span>;<span class="webkit-whitespace"> </span><span class="webkit-javascript-comment">/* comment */</span> function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }/* FINISH */</div></div>
+<div class="webkit-line-content"><span class="webkit-javascript-comment">/* START */</span><span class="webkit-whitespace"> </span><span class="webkit-javascript-keyword">this</span>.<span class="webkit-javascript-ident">field</span><span class="webkit-whitespace"> </span>=<span class="webkit-whitespace"> </span><span class="webkit-javascript-string">"foo"</span>;<span class="webkit-whitespace"> </span><span class="webkit-javascript-comment">/* comment */</span><span class="webkit-whitespace"> </span><span class="webkit-javascript-keyword">function</span><span class="webkit-whitespace"> </span><span class="webkit-javascript-ident">bar</span>()<span class="webkit-whitespace"> </span>{<span class="webkit-whitespace"> </span><span class="webkit-javascript-keyword">return</span><span class="webkit-whitespace"> </span><span class="webkit-javascript-keyword">null</span>;<span class="webkit-whitespace"> </span>}<span class="webkit-javascript-keyword">this</span>.<span class="webkit-javascript-ident">field</span><span class="webkit-whitespace"> </span>=<span class="webkit-whitespace"> </span><span class="webkit-javascript-string">"foo"</span>;<span class="webkit-whitespace"> </span><span class="webkit-javascript-comment">/* comment */</span> function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }this.field = "foo"; /* comment */ function bar() { return null; }/* FINISH */</div></div>
 
index ce78bce28b98f9ba9c1a3b77ef45a93a5e1fad25..0468862fcd9cf0a2c58bdb3b11016d901fdf8455 100644 (file)
@@ -1,3 +1,34 @@
+2013-02-07  Andrey Lushnikov  <lushnikov@chromium.org>
+
+        Web Inspector: highlight matching braces in DTE.
+        https://bugs.webkit.org/show_bug.cgi?id=108697
+
+        Reviewed by Pavel Feldman.
+
+        Implement BraceMatcher class which for given position in textModel
+        will respond with enclosing brace pair for that position.
+        Make use of this class in DefaultTextEditor by handling
+        selectionChange event. Make use of this class in "_closingBlockOffset"
+        method of TextEditorMainPanel as this method implements similar
+        functionality.
+
+        New test: inspector/editor/brace-matcher.html
+
+        * inspector/front-end/DefaultTextEditor.js:
+        (WebInspector.TextEditorMainPanel):
+        (WebInspector.TextEditorMainPanel.prototype._applyDomUpdates):
+        (WebInspector.TextEditorMainPanel.prototype._closingBlockOffset):
+        (WebInspector.TextEditorMainPanel.prototype._handleSelectionChange):
+        (WebInspector.TextEditorMainPanel.BraceHighlightController):
+        (WebInspector.TextEditorMainPanel.BraceHighlightController.prototype.handleSelectionChange):
+        * inspector/front-end/TextEditorHighlighter.js:
+        (WebInspector.TextEditorHighlighter.prototype._highlightLines):
+        * inspector/front-end/TextEditorModel.js:
+        (WebInspector.TextEditorModel.endsWithBracketRegex):
+        (WebInspector.TextEditorModel.endsWithBracketRegex.):
+        * inspector/front-end/textEditor.css:
+        (.text-editor-brace-match):
+
 2013-02-05  Eunmi Lee  <eunmi15.lee@samsung.com> and Raphael Kubo da Costa  <raphael.kubo.da.costa@intel.com>
 
         [EFL][WK2] Refactoring initialization and shutdown codes of EFL libraries.
index 6691429a26d156765b45b09d2dee70a5d1be9681..05b8e1b1fdf58ae83893425c55a9af65a5aebfe2 100644 (file)
@@ -1369,6 +1369,8 @@ WebInspector.TextEditorMainPanel = function(delegate, textModel, url, syncScroll
     this._highlightDescriptors = [];
 
     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._freeCachedElements();
     this.buildChunks();
@@ -2401,7 +2403,7 @@ WebInspector.TextEditorMainPanel.prototype = {
 
         // Unindent after block
         if (editInfo.text === "}" && editInfo.range.isEmpty() && selection.isEmpty() && !this._textModel.line(editInfo.range.endLine).trim()) {
-            var offset = this._closingBlockOffset(editInfo.range, selection);
+            var offset = this._closingBlockOffset(editInfo.range);
             if (offset >= 0) {
                 editInfo.range.startColumn = offset;
                 selection.startColumn = offset + 1;
@@ -2491,30 +2493,15 @@ WebInspector.TextEditorMainPanel.prototype = {
 
     /**
      * @param {WebInspector.TextRange} oldRange
-     * @param {WebInspector.TextRange} selection
      * @return {number}
      */
-    _closingBlockOffset: function(oldRange, selection)
+    _closingBlockOffset: function(oldRange)
     {
-        var nestingLevel = 1;
-        for (var i = oldRange.endLine; i >= 0; --i) {
-            var attribute = this._textModel.getAttribute(i, "highlight");
-            if (!attribute)
-                continue;
-            var ranges = attribute.ranges;
-            for (var j = ranges.length - 1; j >= 0; j--) {
-                var token = ranges[j].token;
-                if (token === "block-start") {
-                    if (!(--nestingLevel)) {
-                        var lineContent = this._textModel.line(i);
-                        return lineContent.length - lineContent.trimLeft().length;
-                    }
-                }
-                if (token === "block-end")
-                    ++nestingLevel;
-            }
-        }
-        return -1;
+        var leftBrace = this._braceMatcher.findLeftCandidate(oldRange.startLine, oldRange.startColumn);
+        if (!leftBrace || leftBrace.token !== "block-start")
+            return -1;
+        var lineContent = this._textModel.line(leftBrace.lineNumber);
+        return lineContent.length - lineContent.trimLeft().length;
     },
 
     /**
@@ -2680,6 +2667,7 @@ WebInspector.TextEditorMainPanel.prototype = {
             this._lastSelection = textRange;
 
         this._tokenHighlighter.handleSelectionChange(textRange);
+        this._braceHighlighter.handleSelectionChange(textRange);
         this._delegate.selectionChanged(textRange);
     },
 
@@ -3359,4 +3347,61 @@ WebInspector.DefaultTextEditor.WordMovementController.prototype = {
     }
 }
 
+/**
+ * @constructor
+ * @param {WebInspector.TextEditorMainPanel} textEditor
+ * @param {WebInspector.TextEditorModel} textModel
+ * @param {WebInspector.TextEditorModel.BraceMatcher} braceMatcher
+ */
+WebInspector.TextEditorMainPanel.BraceHighlightController = function(textEditor, textModel, braceMatcher)
+{
+    this._textEditor = textEditor;
+    this._textModel = textModel;
+    this._braceMatcher = braceMatcher;
+    this._highlightDescriptors = [];
+}
+
+WebInspector.TextEditorMainPanel.BraceHighlightController.prototype = {
+    /**
+     * @param {WebInspector.TextRange} selectionRange
+     */
+    handleSelectionChange: function(selectionRange)
+    {
+        if (!selectionRange || !selectionRange.isEmpty()) {
+            this._removeHighlight();
+            return;
+        }
+
+        if (this._highlightedRange && this._highlightedRange.compareTo(selectionRange) === 0)
+            return;
+
+        this._removeHighlight();
+        var lineNumber = selectionRange.startLine;
+        var column = selectionRange.startColumn;
+        var line = this._textModel.line(lineNumber);
+        if (column > 0 && /[)}]/.test(line.charAt(column - 1)))
+            --column;
+
+        var enclosingBraces = this._braceMatcher.enclosingBraces(lineNumber, column);
+        if (!enclosingBraces)
+            return;
+
+        this._highlightedRange = selectionRange;
+        this._highlightDescriptors.push(this._textEditor.highlightRange(WebInspector.TextRange.createFromLocation(enclosingBraces.leftBrace.lineNumber, enclosingBraces.leftBrace.column), "text-editor-brace-match"));
+        this._highlightDescriptors.push(this._textEditor.highlightRange(WebInspector.TextRange.createFromLocation(enclosingBraces.rightBrace.lineNumber, enclosingBraces.rightBrace.column), "text-editor-brace-match"));
+    },
+
+    _removeHighlight: function()
+    {
+        if (!this._highlightDescriptors.length)
+            return;
+
+        for(var i = 0; i < this._highlightDescriptors.length; ++i)
+            this._textEditor.removeHighlight(this._highlightDescriptors[i]);
+
+        this._highlightDescriptors = [];
+        delete this._highlightedRange;
+    }
+}
+
 WebInspector.debugDefaultTextEditor = false;
index b231dab6b8f8bc0f81885bd22353c39dd52e17bb..be251fef2961ba697cc3ae6da71d7eca6b8a5a95 100644 (file)
@@ -185,15 +185,25 @@ WebInspector.TextEditorHighlighter.prototype = {
 
                 // Highlight line.
                 state.ranges = state.ranges || [];
+                state.braces = state.braces || [];
                 do {
                     var newColumn = this._tokenizer.nextToken(lastHighlightedColumn);
                     var tokenType = this._tokenizer.tokenType;
-                    if (tokenType && lastHighlightedColumn < this._highlightLineLimit)
-                        state.ranges.push({
-                            startColumn: lastHighlightedColumn,
-                            endColumn: newColumn - 1,
-                            token: tokenType
-                        });
+                    if (tokenType && lastHighlightedColumn < this._highlightLineLimit) {
+                        if (tokenType === "brace-start" || tokenType === "brace-end" || tokenType === "block-start" || tokenType === "block-end") {
+                            state.braces.push({
+                                startColumn: lastHighlightedColumn,
+                                endColumn: newColumn - 1,
+                                token: tokenType
+                            });
+                        } else {
+                            state.ranges.push({
+                                startColumn: lastHighlightedColumn,
+                                endColumn: newColumn - 1,
+                                token: tokenType
+                            });
+                        }
+                    }
                     lastHighlightedColumn = newColumn;
                     if (++tokensCount > this._highlightChunkLimit)
                         break;
index 6d73c723b93cdad9d85b77415f00eff58681b90d..16d0ae5b6d0df40ef44471b57595ac91443913a8 100644 (file)
@@ -656,3 +656,156 @@ WebInspector.TextEditorModel.prototype = {
 
     __proto__: WebInspector.Object.prototype
 }
+
+/**
+ * @constructor
+ * @param {WebInspector.TextEditorModel} textModel
+ */
+WebInspector.TextEditorModel.BraceMatcher = function(textModel)
+{
+    this._textModel = textModel;
+}
+
+WebInspector.TextEditorModel.BraceMatcher.prototype = {
+    /**
+     * @param {number} lineNumber
+     * @return {Array.<{startColumn: number, endColumn: number, token: string}>}
+     */
+    _braceRanges: function(lineNumber)
+    {
+        if (lineNumber >= this._textModel.linesCount || lineNumber < 0)
+            return null;
+
+        var attribute = this._textModel.getAttribute(lineNumber, "highlight");
+        if (!attribute)
+            return null;
+        else
+            return attribute.braces;
+    },
+
+    /**
+     * @param {string} braceTokenLeft
+     * @param {string} braceTokenRight
+     * @return {boolean}
+     */
+    _matches: function(braceTokenLeft, braceTokenRight)
+    {
+        return ((braceTokenLeft === "brace-start" && braceTokenRight === "brace-end") || (braceTokenLeft === "block-start" && braceTokenRight === "block-end"));
+    },
+
+    /**
+     * @param {number} lineNumber
+     * @param {number} column
+     * @param {number=} maxBraceIteration
+     * @return {?{lineNumber: number, column: number, token: string}}
+     */
+    findLeftCandidate: function(lineNumber, column, maxBraceIteration)
+    {
+        var braces = this._braceRanges(lineNumber);
+        if (!braces)
+            return null;
+
+        var braceIndex = braces.length - 1;
+        while (braceIndex >= 0 && braces[braceIndex].startColumn > column)
+            --braceIndex;
+
+        var brace = braceIndex >= 0 ? braces[braceIndex] : null;
+        if (brace && brace.startColumn === column && (brace.token === "block-end" || brace.token === "brace-end"))
+            --braceIndex;
+
+        var stack = [];
+        maxBraceIteration = maxBraceIteration || Number.MAX_VALUE;
+        while (--maxBraceIteration) {
+            if (braceIndex < 0) {
+                while ((braces = this._braceRanges(--lineNumber)) && !braces.length) {};
+                if (!braces)
+                    return null;
+                braceIndex = braces.length - 1;
+            }
+            brace = braces[braceIndex];
+            if (brace.token === "block-end" || brace.token === "brace-end")
+                stack.push(brace.token);
+            else if (stack.length === 0)
+                return {
+                    lineNumber: lineNumber,
+                    column: brace.startColumn,
+                    token: brace.token
+                };
+            else if (!this._matches(brace.token, stack.pop()))
+                return null;
+
+            --braceIndex;
+        }
+        return null;
+    },
+
+    /**
+     * @param {number} lineNumber
+     * @param {number} column
+     * @param {number=} maxBraceIteration
+     * @return {?{lineNumber: number, column: number, token: string}}
+     */
+    findRightCandidate: function(lineNumber, column, maxBraceIteration)
+    {
+        var braces = this._braceRanges(lineNumber);
+        if (!braces)
+            return null;
+
+        var braceIndex = 0;
+        while (braceIndex < braces.length && braces[braceIndex].startColumn < column)
+            ++braceIndex;
+
+        var brace = braceIndex < braces.length ? braces[braceIndex] : null;
+        if (brace && brace.startColumn === column && (brace.token === "block-start" || brace.token === "brace-start"))
+            ++braceIndex;
+
+        var stack = [];
+        maxBraceIteration = maxBraceIteration || Number.MAX_VALUE;
+        while (--maxBraceIteration) {
+            if (braceIndex >= braces.length) {
+                while ((braces = this._braceRanges(++lineNumber)) && !braces.length) {};
+                if (!braces)
+                    return null;
+                braceIndex = 0;
+            }
+            brace = braces[braceIndex];
+            if (brace.token === "block-start" || brace.token === "brace-start")
+                stack.push(brace.token);
+            else if (stack.length === 0)
+                return {
+                    lineNumber: lineNumber,
+                    column: brace.startColumn,
+                    token: brace.token
+                };
+            else if (!this._matches(stack.pop(), brace.token))
+                return null;
+            ++braceIndex;
+        }
+        return null;
+    },
+
+    /**
+     * @param {number} lineNumber
+     * @param {number} column
+     * @param {number=} maxBraceIteration
+     * @return {?{leftBrace: {lineNumber: number, column: number, token: string}, rightBrace: {lineNumber: number, column: number, token: string}}}
+     */
+    enclosingBraces: function(lineNumber, column, maxBraceIteration)
+    {
+        var leftBraceLocation = this.findLeftCandidate(lineNumber, column, maxBraceIteration);
+        if (!leftBraceLocation)
+            return null;
+
+        var rightBraceLocation = this.findRightCandidate(lineNumber, column, maxBraceIteration);
+        if (!rightBraceLocation)
+            return null;
+
+        if (!this._matches(leftBraceLocation.token, rightBraceLocation.token))
+            return null;
+
+        return {
+            leftBrace: leftBraceLocation,
+            rightBrace: rightBraceLocation
+        };
+    },
+}
index 34745c77982faa9b89687298fd7bf4a6c06ee45e..90124ec9ed551afe1ec6c7733dadca46b89ee7f8 100644 (file)
     border-radius: 3px;
 }
 
+.text-editor-brace-match {
+    border-bottom: 1px solid black;
+}
+
 .text-editor-contents .inner-container {
     position: absolute;
     top: 0;