From e0048b4967c9d32f2036b84761f785631f5ba3d4 Mon Sep 17 00:00:00 2001 From: dgeorge Date: Fri, 28 Oct 2011 14:23:55 -0700 Subject: [PATCH 1/6] EditSession.findMatchingBracket now uses tokens instead of a simple text search. Moved the bracket-matching code to a separate file edit_session/bracket_match.js. Added a TokenIterator helper class, which walks forward or backward through all tokens in the document. --- lib/ace/edit_session.js | 93 +------------- lib/ace/edit_session/bracket_match.js | 174 ++++++++++++++++++++++++++ lib/ace/edit_session_test.js | 13 +- lib/ace/mode/javascript_test.js | 2 +- lib/ace/token_iterator.js | 112 +++++++++++++++++ 5 files changed, 300 insertions(+), 94 deletions(-) create mode 100644 lib/ace/edit_session/bracket_match.js create mode 100644 lib/ace/token_iterator.js diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 9f22e284..c2fbbf5b 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -612,98 +612,6 @@ var EditSession = function(text, mode) { return this.doc.getTextRange(range); }; - this.findMatchingBracket = function(position) { - if (position.column == 0) return null; - - var charBeforeCursor = this.getLine(position.row).charAt(position.column-1); - if (charBeforeCursor == "") return null; - - var match = charBeforeCursor.match(/([\(\[\{])|([\)\]\}])/); - if (!match) { - return null; - } - - if (match[1]) { - return this.$findClosingBracket(match[1], position); - } else { - return this.$findOpeningBracket(match[2], position); - } - }; - - this.$brackets = { - ")": "(", - "(": ")", - "]": "[", - "[": "]", - "{": "}", - "}": "{" - }; - - this.$findOpeningBracket = function(bracket, position) { - var openBracket = this.$brackets[bracket]; - - var column = position.column - 2; - var row = position.row; - var depth = 1; - - var line = this.getLine(row); - - while (true) { - while(column >= 0) { - var ch = line.charAt(column); - if (ch == openBracket) { - depth -= 1; - if (depth == 0) { - return {row: row, column: column}; - } - } - else if (ch == bracket) { - depth +=1; - } - column -= 1; - } - row -=1; - if (row < 0) break; - - var line = this.getLine(row); - var column = line.length-1; - } - return null; - }; - - this.$findClosingBracket = function(bracket, position) { - var closingBracket = this.$brackets[bracket]; - - var column = position.column; - var row = position.row; - var depth = 1; - - var line = this.getLine(row); - var lineCount = this.getLength(); - - while (true) { - while(column < line.length) { - var ch = line.charAt(column); - if (ch == closingBracket) { - depth -= 1; - if (depth == 0) { - return {row: row, column: column}; - } - } - else if (ch == bracket) { - depth +=1; - } - column += 1; - } - row +=1; - if (row >= lineCount) break; - - var line = this.getLine(row); - var column = 0; - } - return null; - }; - this.insert = function(position, text) { return this.doc.insert(position, text); }; @@ -1732,6 +1640,7 @@ var EditSession = function(text, mode) { }).call(EditSession.prototype); require("ace/edit_session/folding").Folding.call(EditSession.prototype); +require("ace/edit_session/bracket_match").BracketMatch.call(EditSession.prototype); exports.EditSession = EditSession; }); \ No newline at end of file diff --git a/lib/ace/edit_session/bracket_match.js b/lib/ace/edit_session/bracket_match.js new file mode 100644 index 00000000..05fe0307 --- /dev/null +++ b/lib/ace/edit_session/bracket_match.js @@ -0,0 +1,174 @@ +/* vim:ts=4:sts=4:sw=4: + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Fabian Jakobs + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { + +var TokenIterator = require("ace/token_iterator").TokenIterator; + +function BracketMatch() { + + this.findMatchingBracket = function(position) { + if (position.column == 0) return null; + + var charBeforeCursor = this.getLine(position.row).charAt(position.column-1); + if (charBeforeCursor == "") return null; + + var match = charBeforeCursor.match(/([\(\[\{])|([\)\]\}])/); + if (!match) { + return null; + } + + if (match[1]) { + return this.$findClosingBracket(match[1], position); + } else { + return this.$findOpeningBracket(match[2], position); + } + }; + + this.$brackets = { + ")": "(", + "(": ")", + "]": "[", + "[": "]", + "{": "}", + "}": "{" + }; + + this.$findOpeningBracket = function(bracket, position) { + var openBracket = this.$brackets[bracket]; + var depth = 1; + + var iterator = new TokenIterator(this, position.row, position.column); + var token = iterator.getCurrentToken(); + if (!token) return null; + + // Create a pattern that matches any token with the same type as token.type. + // Exception: if token.type includes "rparen", then also match "lparen". + var typeRe = new RegExp("(\\.?[" + + token.type.replace(".", "|").replace("rparen", "lparen|rparen") + "])+"); + + // Start searching in token, just before the character at position.column + var vIndex = position.column - iterator.getCurrentTokenColumn() - 2; + var value = token.value; + + while (true) { + + while (vIndex >= 0) { + var char = value.charAt(vIndex); + if (char == openBracket) { + depth -= 1; + if (depth == 0) { + return {row: iterator.getCurrentTokenRow(), + column: vIndex + iterator.getCurrentTokenColumn()}; + } + } + else if (char == bracket) { + depth += 1; + } + vIndex -= 1; + } + + // Scan backward through the document, looking for the next token + // whose type matches typeRe + do { + token = iterator.stepBackward(); + } while (token && !typeRe.test(token.type)) + + if (token == null) + break; + + value = token.value; + vIndex = token.value.length - 1; + } + + return null; + }; + + this.$findClosingBracket = function(bracket, position) { + var closingBracket = this.$brackets[bracket]; + var depth = 1; + + var iterator = new TokenIterator(this, position.row, position.column); + var token = iterator.getCurrentToken(); + if (!token) return null; + + // Create a pattern that matches any token with the same type as token.type. + // Exception: if token.type includes "lparen", then also match "rparen". + var typeRe = new RegExp("(\\.?[" + + token.type.replace(".", "|").replace("lparen", "lparen|rparen") + "])+"); + + // Start searching in token, after after the character at position.column + var vIndex = position.column - iterator.getCurrentTokenColumn(); + + while (true) { + + var value = token.value; + var valueLength = value.length; + while (vIndex < valueLength) { + var char = value.charAt(vIndex); + if (char == closingBracket) { + depth -= 1; + if (depth == 0) { + return {row: iterator.getCurrentTokenRow(), + column: vIndex + iterator.getCurrentTokenColumn()}; + } + } + else if (char == bracket) { + depth += 1; + } + vIndex += 1; + } + + // Scan forward through the document, looking for the next token + // whose type matches typeRe + do { + token = iterator.stepForward(); + } while (token && !typeRe.test(token.type)) + + if (token == null) + break; + + vIndex = 0; + } + + return null; + }; +} +exports.BracketMatch = BracketMatch; + +}); diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index cb3ce7bb..aa7d796c 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -50,6 +50,7 @@ var UndoManager = require("ace/undomanager").UndoManager; var MockRenderer = require("ace/test/mockrenderer").MockRenderer; var Range = require("ace/range").Range; var assert = require("ace/test/assertions"); +var JavaScriptMode = require("ace/mode/javascript").Mode; function createFoldTestSession() { var lines = [ @@ -69,7 +70,7 @@ function createFoldTestSession() { module.exports = { - "test: find matching opening bracket" : function() { + "test: find matching opening bracket in Text mode" : function() { var session = new EditSession(["(()(", "())))"]); assert.position(session.findMatchingBracket({row: 0, column: 3}), 0, 1); @@ -79,6 +80,16 @@ module.exports = { assert.equal(session.findMatchingBracket({row: 1, column: 5}), null); }, + "test: find matching opening bracket in JavaScript mode" : function() { + var session = new EditSession(["(()(", "", "())))"], new JavaScriptMode()); + + assert.position(session.findMatchingBracket({row: 0, column: 3}), 0, 1); + assert.position(session.findMatchingBracket({row: 2, column: 2}), 2, 0); + assert.position(session.findMatchingBracket({row: 2, column: 3}), 0, 3); + assert.position(session.findMatchingBracket({row: 2, column: 4}), 0, 0); + assert.equal(session.findMatchingBracket({row: 2, column: 5}), null); + }, + "test: find matching closing bracket" : function() { var session = new EditSession(["(()(", "())))"]); diff --git a/lib/ace/mode/javascript_test.js b/lib/ace/mode/javascript_test.js index e2a89be5..f2880f29 100644 --- a/lib/ace/mode/javascript_test.js +++ b/lib/ace/mode/javascript_test.js @@ -141,7 +141,7 @@ module.exports = { }, "test: auto outdent should indent the line with the same indent as the line with the matching opening brace" : function() { - var session = new EditSession([" function foo() {", " bla", " }"]); + var session = new EditSession([" function foo() {", " bla", " }"], new JavaScriptMode()); this.mode.autoOutdent("start", session, 2); assert.equal(" }", session.getLine(2)); }, diff --git a/lib/ace/token_iterator.js b/lib/ace/token_iterator.js new file mode 100644 index 00000000..7924a435 --- /dev/null +++ b/lib/ace/token_iterator.js @@ -0,0 +1,112 @@ +/* vim:ts=4:sts=4:sw=4: + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Fabian Jakobs + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { + +var TokenIterator = function(session, initialRow, initialColumn) { + this.$session = session; + this.$row = initialRow; + this.$rowTokens = session.getTokens(initialRow, initialRow)[0].tokens; + + var token = session.getTokenAt(initialRow, initialColumn); + this.$tokenIndex = token ? token.index : -1; +}; + +(function() { + + this.stepBackward = function() { + this.$tokenIndex -= 1; + + while (this.$tokenIndex < 0) { + this.$row -= 1; + if (this.$row < 0) + return null; + + this.$rowTokens = this.$session.getTokens(this.$row, this.$row)[0].tokens; + this.$tokenIndex = this.$rowTokens.length - 1; + } + + return this.$rowTokens[this.$tokenIndex]; + } + + this.stepForward = function() { + var rowCount = this.$session.getLength(); + this.$tokenIndex += 1; + + while (this.$tokenIndex >= this.$rowTokens.length) { + this.$row += 1; + if (this.$row >= rowCount) + return null; + + this.$rowTokens = this.$session.getTokens(this.$row, this.$row)[0].tokens; + this.$tokenIndex = 0; + } + + return this.$rowTokens[this.$tokenIndex]; + } + + this.getCurrentToken = function () { + return this.$rowTokens[this.$tokenIndex]; + } + + this.getCurrentTokenRow = function () { + return this.$row; + } + + this.getCurrentTokenColumn = function() { + var rowTokens = this.$rowTokens; + var tokenIndex = this.$tokenIndex; + + // If a column was cached by EditSession.getTokenAt, then use it + var column = rowTokens[tokenIndex].start; + if (column !== undefined) + return column; + + column = 0; + while (tokenIndex > 0) { + tokenIndex -= 1; + column += rowTokens[tokenIndex].value.length; + } + + return column; + } + +}).call(TokenIterator.prototype); + +exports.TokenIterator = TokenIterator; +}); From bcabfcb2e2bcde513c6731c704fb8475725fcbc1 Mon Sep 17 00:00:00 2001 From: dgeorge Date: Fri, 28 Oct 2011 16:58:45 -0700 Subject: [PATCH 2/6] Added unit tests for TokenIterator --- lib/ace/test/all_browser.js | 1 + lib/ace/token_iterator_test.js | 198 +++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 lib/ace/token_iterator_test.js diff --git a/lib/ace/test/all_browser.js b/lib/ace/test/all_browser.js index 42e32731..32ccfdd0 100644 --- a/lib/ace/test/all_browser.js +++ b/lib/ace/test/all_browser.js @@ -35,6 +35,7 @@ var tests = [ require("ace/search_test"), require("ace/selection_test"), require("ace/test/event_emitter_test"), + require("ace/token_iterator_test"), require("ace/virtual_renderer_test") ] diff --git a/lib/ace/token_iterator_test.js b/lib/ace/token_iterator_test.js new file mode 100644 index 00000000..aa29917b --- /dev/null +++ b/lib/ace/token_iterator_test.js @@ -0,0 +1,198 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Fabian Jakobs + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +if (typeof process !== "undefined") { + require("../../support/paths"); +} + +define(function(require, exports, module) { + +var EditSession = require("ace/edit_session").EditSession; +var JavaScriptMode = require("ace/mode/javascript").Mode; +var TokenIterator = require("ace/token_iterator").TokenIterator; +var assert = require("ace/test/assertions"); + +module.exports = { + "test: token iterator initialization in JavaScript document" : function() { + var lines = [ + "function foo(items) {", + " for (var i=0; i= 0; i--) + assert.equal(iterator.stepBackward(), tokens[i]); + assert.equal(iterator.stepBackward(), null); + assert.equal(iterator.getCurrentToken(), null); + }, + + "test: token iterator reports correct row and column" : function() { + var lines = [ + "function foo(items) {", + " for (var i=0; i Date: Mon, 31 Oct 2011 16:05:52 -0700 Subject: [PATCH 3/6] Changed leading white space to use spaces instead of tabs (no actual code changes, just reformatting the code) --- lib/ace/edit_session/bracket_match.js | 220 +++++++++++++------------- lib/ace/token_iterator.js | 126 +++++++-------- 2 files changed, 173 insertions(+), 173 deletions(-) diff --git a/lib/ace/edit_session/bracket_match.js b/lib/ace/edit_session/bracket_match.js index a7f403e3..878cff13 100644 --- a/lib/ace/edit_session/bracket_match.js +++ b/lib/ace/edit_session/bracket_match.js @@ -20,7 +20,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Fabian Jakobs + * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -42,132 +42,132 @@ var TokenIterator = require("ace/token_iterator").TokenIterator; function BracketMatch() { - this.findMatchingBracket = function(position) { - if (position.column == 0) return null; + this.findMatchingBracket = function(position) { + if (position.column == 0) return null; - var charBeforeCursor = this.getLine(position.row).charAt(position.column-1); - if (charBeforeCursor == "") return null; + var charBeforeCursor = this.getLine(position.row).charAt(position.column-1); + if (charBeforeCursor == "") return null; - var match = charBeforeCursor.match(/([\(\[\{])|([\)\]\}])/); - if (!match) { - return null; - } + var match = charBeforeCursor.match(/([\(\[\{])|([\)\]\}])/); + if (!match) { + return null; + } - if (match[1]) { - return this.$findClosingBracket(match[1], position); - } else { - return this.$findOpeningBracket(match[2], position); - } - }; + if (match[1]) { + return this.$findClosingBracket(match[1], position); + } else { + return this.$findOpeningBracket(match[2], position); + } + }; - this.$brackets = { - ")": "(", - "(": ")", - "]": "[", - "[": "]", - "{": "}", - "}": "{" - }; + this.$brackets = { + ")": "(", + "(": ")", + "]": "[", + "[": "]", + "{": "}", + "}": "{" + }; - this.$findOpeningBracket = function(bracket, position) { - var openBracket = this.$brackets[bracket]; - var depth = 1; + this.$findOpeningBracket = function(bracket, position) { + var openBracket = this.$brackets[bracket]; + var depth = 1; - var iterator = new TokenIterator(this, position.row, position.column); - var token = iterator.getCurrentToken(); - if (!token) return null; - - // Create a pattern that matches any token with the same type as token.type. - // Exception: if token.type includes "rparen", then also match "lparen". - var typeRe = new RegExp("(\\.?[" + - token.type.replace(".", "|").replace("rparen", "lparen|rparen") + "])+"); - - // Start searching in token, just before the character at position.column - var vIndex = position.column - iterator.getCurrentTokenColumn() - 2; - var value = token.value; - - while (true) { - - while (vIndex >= 0) { - var char = value.charAt(vIndex); - if (char == openBracket) { - depth -= 1; - if (depth == 0) { - return {row: iterator.getCurrentTokenRow(), - column: vIndex + iterator.getCurrentTokenColumn()}; - } - } - else if (char == bracket) { - depth += 1; - } - vIndex -= 1; - } + var iterator = new TokenIterator(this, position.row, position.column); + var token = iterator.getCurrentToken(); + if (!token) return null; + + // Create a pattern that matches any token with the same type as token.type. + // Exception: if token.type includes "rparen", then also match "lparen". + var typeRe = new RegExp("(\\.?[" + + token.type.replace(".", "|").replace("rparen", "lparen|rparen") + "])+"); + + // Start searching in token, just before the character at position.column + var vIndex = position.column - iterator.getCurrentTokenColumn() - 2; + var value = token.value; + + while (true) { + + while (vIndex >= 0) { + var char = value.charAt(vIndex); + if (char == openBracket) { + depth -= 1; + if (depth == 0) { + return {row: iterator.getCurrentTokenRow(), + column: vIndex + iterator.getCurrentTokenColumn()}; + } + } + else if (char == bracket) { + depth += 1; + } + vIndex -= 1; + } - // Scan backward through the document, looking for the next token - // whose type matches typeRe - do { - token = iterator.stepBackward(); - } while (token && !typeRe.test(token.type)) + // Scan backward through the document, looking for the next token + // whose type matches typeRe + do { + token = iterator.stepBackward(); + } while (token && !typeRe.test(token.type)) - if (token == null) - break; - - value = token.value; - vIndex = token.value.length - 1; - } - - return null; - }; + if (token == null) + break; + + value = token.value; + vIndex = token.value.length - 1; + } + + return null; + }; - this.$findClosingBracket = function(bracket, position) { - var closingBracket = this.$brackets[bracket]; - var depth = 1; + this.$findClosingBracket = function(bracket, position) { + var closingBracket = this.$brackets[bracket]; + var depth = 1; - var iterator = new TokenIterator(this, position.row, position.column); - var token = iterator.getCurrentToken(); - if (!token) return null; + var iterator = new TokenIterator(this, position.row, position.column); + var token = iterator.getCurrentToken(); + if (!token) return null; - // Create a pattern that matches any token with the same type as token.type. - // Exception: if token.type includes "lparen", then also match "rparen". - var typeRe = new RegExp("(\\.?[" + - token.type.replace(".", "|").replace("lparen", "lparen|rparen") + "])+"); + // Create a pattern that matches any token with the same type as token.type. + // Exception: if token.type includes "lparen", then also match "rparen". + var typeRe = new RegExp("(\\.?[" + + token.type.replace(".", "|").replace("lparen", "lparen|rparen") + "])+"); - // Start searching in token, after after the character at position.column - var vIndex = position.column - iterator.getCurrentTokenColumn(); + // Start searching in token, after after the character at position.column + var vIndex = position.column - iterator.getCurrentTokenColumn(); - while (true) { + while (true) { - var value = token.value; - var valueLength = value.length; - while (vIndex < valueLength) { - var char = value.charAt(vIndex); - if (char == closingBracket) { - depth -= 1; - if (depth == 0) { - return {row: iterator.getCurrentTokenRow(), - column: vIndex + iterator.getCurrentTokenColumn()}; - } - } - else if (char == bracket) { - depth += 1; - } - vIndex += 1; - } + var value = token.value; + var valueLength = value.length; + while (vIndex < valueLength) { + var char = value.charAt(vIndex); + if (char == closingBracket) { + depth -= 1; + if (depth == 0) { + return {row: iterator.getCurrentTokenRow(), + column: vIndex + iterator.getCurrentTokenColumn()}; + } + } + else if (char == bracket) { + depth += 1; + } + vIndex += 1; + } - // Scan forward through the document, looking for the next token - // whose type matches typeRe - do { - token = iterator.stepForward(); - } while (token && !typeRe.test(token.type)) + // Scan forward through the document, looking for the next token + // whose type matches typeRe + do { + token = iterator.stepForward(); + } while (token && !typeRe.test(token.type)) - if (token == null) - break; + if (token == null) + break; - vIndex = 0; - } - - return null; - }; + vIndex = 0; + } + + return null; + }; } exports.BracketMatch = BracketMatch; diff --git a/lib/ace/token_iterator.js b/lib/ace/token_iterator.js index 407266b6..2eee9386 100644 --- a/lib/ace/token_iterator.js +++ b/lib/ace/token_iterator.js @@ -20,7 +20,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Fabian Jakobs + * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -39,73 +39,73 @@ define(function(require, exports, module) { var TokenIterator = function(session, initialRow, initialColumn) { - this.$session = session; - this.$row = initialRow; - this.$rowTokens = session.getTokens(initialRow, initialRow)[0].tokens; + this.$session = session; + this.$row = initialRow; + this.$rowTokens = session.getTokens(initialRow, initialRow)[0].tokens; - var token = session.getTokenAt(initialRow, initialColumn); - this.$tokenIndex = token ? token.index : -1; + var token = session.getTokenAt(initialRow, initialColumn); + this.$tokenIndex = token ? token.index : -1; }; (function() { - - this.stepBackward = function() { - this.$tokenIndex -= 1; - - while (this.$tokenIndex < 0) { - this.$row -= 1; - if (this.$row < 0) - return null; - - this.$rowTokens = this.$session.getTokens(this.$row, this.$row)[0].tokens; - this.$tokenIndex = this.$rowTokens.length - 1; - } - - return this.$rowTokens[this.$tokenIndex]; - } - - this.stepForward = function() { - var rowCount = this.$session.getLength(); - this.$tokenIndex += 1; - - while (this.$tokenIndex >= this.$rowTokens.length) { - this.$row += 1; - if (this.$row >= rowCount) - return null; + + this.stepBackward = function() { + this.$tokenIndex -= 1; + + while (this.$tokenIndex < 0) { + this.$row -= 1; + if (this.$row < 0) + return null; + + this.$rowTokens = this.$session.getTokens(this.$row, this.$row)[0].tokens; + this.$tokenIndex = this.$rowTokens.length - 1; + } + + return this.$rowTokens[this.$tokenIndex]; + } + + this.stepForward = function() { + var rowCount = this.$session.getLength(); + this.$tokenIndex += 1; + + while (this.$tokenIndex >= this.$rowTokens.length) { + this.$row += 1; + if (this.$row >= rowCount) + return null; - this.$rowTokens = this.$session.getTokens(this.$row, this.$row)[0].tokens; - this.$tokenIndex = 0; - } - - return this.$rowTokens[this.$tokenIndex]; - } - - this.getCurrentToken = function () { - return this.$rowTokens[this.$tokenIndex]; - } - - this.getCurrentTokenRow = function () { - return this.$row; - } - - this.getCurrentTokenColumn = function() { - var rowTokens = this.$rowTokens; - var tokenIndex = this.$tokenIndex; - - // If a column was cached by EditSession.getTokenAt, then use it - var column = rowTokens[tokenIndex].start; - if (column !== undefined) - return column; - - column = 0; - while (tokenIndex > 0) { - tokenIndex -= 1; - column += rowTokens[tokenIndex].value.length; - } - - return column; - } - + this.$rowTokens = this.$session.getTokens(this.$row, this.$row)[0].tokens; + this.$tokenIndex = 0; + } + + return this.$rowTokens[this.$tokenIndex]; + } + + this.getCurrentToken = function () { + return this.$rowTokens[this.$tokenIndex]; + } + + this.getCurrentTokenRow = function () { + return this.$row; + } + + this.getCurrentTokenColumn = function() { + var rowTokens = this.$rowTokens; + var tokenIndex = this.$tokenIndex; + + // If a column was cached by EditSession.getTokenAt, then use it + var column = rowTokens[tokenIndex].start; + if (column !== undefined) + return column; + + column = 0; + while (tokenIndex > 0) { + tokenIndex -= 1; + column += rowTokens[tokenIndex].value.length; + } + + return column; + } + }).call(TokenIterator.prototype); exports.TokenIterator = TokenIterator; From 017d6dc96e9db8153fc796f4d85406fde0168e49 Mon Sep 17 00:00:00 2001 From: dgeorge Date: Mon, 31 Oct 2011 16:08:21 -0700 Subject: [PATCH 4/6] Added more unit tests. Changed the unit test code to be formatted with leading spaces instead of tabs. --- lib/ace/edit_session_test.js | 55 +++++-- lib/ace/token_iterator_test.js | 286 ++++++++++++++++++--------------- 2 files changed, 197 insertions(+), 144 deletions(-) diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index aa7d796c..32b571c4 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -80,17 +80,7 @@ module.exports = { assert.equal(session.findMatchingBracket({row: 1, column: 5}), null); }, - "test: find matching opening bracket in JavaScript mode" : function() { - var session = new EditSession(["(()(", "", "())))"], new JavaScriptMode()); - - assert.position(session.findMatchingBracket({row: 0, column: 3}), 0, 1); - assert.position(session.findMatchingBracket({row: 2, column: 2}), 2, 0); - assert.position(session.findMatchingBracket({row: 2, column: 3}), 0, 3); - assert.position(session.findMatchingBracket({row: 2, column: 4}), 0, 0); - assert.equal(session.findMatchingBracket({row: 2, column: 5}), null); - }, - - "test: find matching closing bracket" : function() { + "test: find matching closing bracket in Text mode" : function() { var session = new EditSession(["(()(", "())))"]); assert.position(session.findMatchingBracket({row: 1, column: 1}), 1, 1); @@ -101,6 +91,49 @@ module.exports = { assert.equal(session.findMatchingBracket({row: 0, column: 0}), null); }, + "test: find matching opening bracket in JavaScript mode" : function() { + var lines = [ + "function foo() {", + " var str = \"{ foo()\";", + " if (debug) {", + " // write str (a string) to the console", + " console.log(str);", + " }", + " str += \" bar() }\";", + "}" + ]; + var session = new EditSession(lines.join("\n"), new JavaScriptMode()); + + assert.position(session.findMatchingBracket({row: 0, column: 14}), 0, 12); + assert.position(session.findMatchingBracket({row: 7, column: 1}), 0, 15); + assert.position(session.findMatchingBracket({row: 6, column: 20}), 1, 15); + assert.position(session.findMatchingBracket({row: 1, column: 22}), 1, 20); + assert.position(session.findMatchingBracket({row: 3, column: 31}), 3, 21); + assert.position(session.findMatchingBracket({row: 4, column: 24}), 4, 19); + assert.equal(session.findMatchingBracket({row: 0, column: 1}), null); + }, + + "test: find matching closing bracket in JavaScript mode" : function() { + var lines = [ + "function foo() {", + " var str = \"{ foo()\";", + " if (debug) {", + " // write str (a string) to the console", + " console.log(str);", + " }", + " str += \" bar() }\";", + "}" + ]; + var session = new EditSession(lines.join("\n"), new JavaScriptMode()); + + assert.position(session.findMatchingBracket({row: 0, column: 13}), 0, 13); + assert.position(session.findMatchingBracket({row: 0, column: 16}), 7, 0); + assert.position(session.findMatchingBracket({row: 1, column: 16}), 6, 19); + assert.position(session.findMatchingBracket({row: 1, column: 21}), 1, 21); + assert.position(session.findMatchingBracket({row: 3, column: 22}), 3, 30); + assert.position(session.findMatchingBracket({row: 4, column: 20}), 4, 23); + }, + "test: match different bracket types" : function() { var session = new EditSession(["({[", ")]}"]); diff --git a/lib/ace/token_iterator_test.js b/lib/ace/token_iterator_test.js index aa29917b..9950ef3c 100644 --- a/lib/ace/token_iterator_test.js +++ b/lib/ace/token_iterator_test.js @@ -19,7 +19,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Fabian Jakobs + * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -36,7 +36,7 @@ * ***** END LICENSE BLOCK ***** */ if (typeof process !== "undefined") { - require("../../support/paths"); + require("../../support/paths"); } define(function(require, exports, module) { @@ -47,152 +47,172 @@ var TokenIterator = require("ace/token_iterator").TokenIterator; var assert = require("ace/test/assertions"); module.exports = { - "test: token iterator initialization in JavaScript document" : function() { - var lines = [ - "function foo(items) {", - " for (var i=0; i= 0; i--) - assert.equal(iterator.stepBackward(), tokens[i]); - assert.equal(iterator.stepBackward(), null); - assert.equal(iterator.getCurrentToken(), null); - }, + var rows = session.getTokens(0, lines.length-1); + var tokens = []; + for (var i = 0; i < rows.length; i++) + tokens = tokens.concat(rows[i].tokens); + + var iterator = new TokenIterator(session, 4, 0); + for (var i = tokens.length-2; i >= 0; i--) + assert.equal(iterator.stepBackward(), tokens[i]); + assert.equal(iterator.stepBackward(), null); + assert.equal(iterator.getCurrentToken(), null); + }, - "test: token iterator reports correct row and column" : function() { - var lines = [ - "function foo(items) {", - " for (var i=0; i Date: Tue, 1 Nov 2011 17:09:43 -0700 Subject: [PATCH 5/6] More unit tests. Clearer comments. Better variable names. --- lib/ace/edit_session/bracket_match.js | 44 +++++++++++++++------------ lib/ace/edit_session_test.js | 24 +++++++++++++-- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/lib/ace/edit_session/bracket_match.js b/lib/ace/edit_session/bracket_match.js index 878cff13..d936296f 100644 --- a/lib/ace/edit_session/bracket_match.js +++ b/lib/ace/edit_session/bracket_match.js @@ -77,43 +77,46 @@ function BracketMatch() { var token = iterator.getCurrentToken(); if (!token) return null; - // Create a pattern that matches any token with the same type as token.type. - // Exception: if token.type includes "rparen", then also match "lparen". + // token.type contains a period-delimited list of token identifiers + // (e.g.: "constant.numeric" or "paren.lparen"). Create a pattern that + // matches any token containing the same identifiers or a subset. In + // addition, if token.type includes "rparen", then also match "lparen". + // So if type.token is "paren.rparen", then typeRe will match "lparen.paren". var typeRe = new RegExp("(\\.?[" + token.type.replace(".", "|").replace("rparen", "lparen|rparen") + "])+"); // Start searching in token, just before the character at position.column - var vIndex = position.column - iterator.getCurrentTokenColumn() - 2; + var valueIndex = position.column - iterator.getCurrentTokenColumn() - 2; var value = token.value; while (true) { - while (vIndex >= 0) { - var char = value.charAt(vIndex); + while (valueIndex >= 0) { + var char = value.charAt(valueIndex); if (char == openBracket) { depth -= 1; if (depth == 0) { return {row: iterator.getCurrentTokenRow(), - column: vIndex + iterator.getCurrentTokenColumn()}; + column: valueIndex + iterator.getCurrentTokenColumn()}; } } else if (char == bracket) { depth += 1; } - vIndex -= 1; + valueIndex -= 1; } // Scan backward through the document, looking for the next token // whose type matches typeRe do { token = iterator.stepBackward(); - } while (token && !typeRe.test(token.type)) + } while (token && !typeRe.test(token.type)); if (token == null) break; value = token.value; - vIndex = token.value.length - 1; + valueIndex = value.length - 1; } return null; @@ -127,43 +130,46 @@ function BracketMatch() { var token = iterator.getCurrentToken(); if (!token) return null; - // Create a pattern that matches any token with the same type as token.type. - // Exception: if token.type includes "lparen", then also match "rparen". + // token.type contains a period-delimited list of token identifiers + // (e.g.: "constant.numeric" or "paren.lparen"). Create a pattern that + // matches any token containing the same identifiers or a subset. In + // addition, if token.type includes "lparen", then also match "rparen". + // So if type.token is "lparen.paren", then typeRe will match "paren.rparen". var typeRe = new RegExp("(\\.?[" + token.type.replace(".", "|").replace("lparen", "lparen|rparen") + "])+"); - // Start searching in token, after after the character at position.column - var vIndex = position.column - iterator.getCurrentTokenColumn(); + // Start searching in token, after the character at position.column + var valueIndex = position.column - iterator.getCurrentTokenColumn(); while (true) { var value = token.value; var valueLength = value.length; - while (vIndex < valueLength) { - var char = value.charAt(vIndex); + while (valueIndex < valueLength) { + var char = value.charAt(valueIndex); if (char == closingBracket) { depth -= 1; if (depth == 0) { return {row: iterator.getCurrentTokenRow(), - column: vIndex + iterator.getCurrentTokenColumn()}; + column: valueIndex + iterator.getCurrentTokenColumn()}; } } else if (char == bracket) { depth += 1; } - vIndex += 1; + valueIndex += 1; } // Scan forward through the document, looking for the next token // whose type matches typeRe do { token = iterator.stepForward(); - } while (token && !typeRe.test(token.type)) + } while (token && !typeRe.test(token.type)); if (token == null) break; - vIndex = 0; + valueIndex = 0; } return null; diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index effd8c62..49957932 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -91,7 +91,7 @@ module.exports = { assert.equal(session.findMatchingBracket({row: 0, column: 0}), null); }, - "test: find matching opening bracket in JavaScript mode" : function() { + "test: find matching opening bracket in JavaScript mode" : function() { var lines = [ "function foo() {", " var str = \"{ foo()\";", @@ -112,8 +112,8 @@ module.exports = { assert.position(session.findMatchingBracket({row: 4, column: 24}), 4, 19); assert.equal(session.findMatchingBracket({row: 0, column: 1}), null); }, - - "test: find matching closing bracket in JavaScript mode" : function() { + + "test: find matching closing bracket in JavaScript mode" : function() { var lines = [ "function foo() {", " var str = \"{ foo()\";", @@ -134,6 +134,24 @@ module.exports = { assert.position(session.findMatchingBracket({row: 4, column: 20}), 4, 23); }, + "test: handle unbalanced brackets in JavaScript mode" : function() { + var lines = [ + "function foo() {", + " var str = \"{ foo()\";", + " if (debug) {", + " // write str a string) to the console", + " console.log(str);", + " ", + " str += \" bar() \";", + "}" + ]; + var session = new EditSession(lines.join("\n"), new JavaScriptMode()); + + assert.equal(session.findMatchingBracket({row: 0, column: 16}), null); + assert.equal(session.findMatchingBracket({row: 3, column: 30}), null); + assert.equal(session.findMatchingBracket({row: 1, column: 16}), null); + }, + "test: match different bracket types" : function() { var session = new EditSession(["({[", ")]}"]); From a4e1ab55f03b01c97640998d3746656f7c74b5d8 Mon Sep 17 00:00:00 2001 From: dgeorge Date: Fri, 11 Nov 2011 09:24:08 -0800 Subject: [PATCH 6/6] Changed the typeRe regular expression to be more strict. --- lib/ace/edit_session/bracket_match.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ace/edit_session/bracket_match.js b/lib/ace/edit_session/bracket_match.js index d936296f..cc634b5a 100644 --- a/lib/ace/edit_session/bracket_match.js +++ b/lib/ace/edit_session/bracket_match.js @@ -82,8 +82,8 @@ function BracketMatch() { // matches any token containing the same identifiers or a subset. In // addition, if token.type includes "rparen", then also match "lparen". // So if type.token is "paren.rparen", then typeRe will match "lparen.paren". - var typeRe = new RegExp("(\\.?[" + - token.type.replace(".", "|").replace("rparen", "lparen|rparen") + "])+"); + var typeRe = new RegExp("(\\.?" + + token.type.replace(".", "|").replace("rparen", "lparen|rparen") + ")+"); // Start searching in token, just before the character at position.column var valueIndex = position.column - iterator.getCurrentTokenColumn() - 2; @@ -135,8 +135,8 @@ function BracketMatch() { // matches any token containing the same identifiers or a subset. In // addition, if token.type includes "lparen", then also match "rparen". // So if type.token is "lparen.paren", then typeRe will match "paren.rparen". - var typeRe = new RegExp("(\\.?[" + - token.type.replace(".", "|").replace("lparen", "lparen|rparen") + "])+"); + var typeRe = new RegExp("(\\.?" + + token.type.replace(".", "|").replace("lparen", "lparen|rparen") + ")+"); // Start searching in token, after the character at position.column var valueIndex = position.column - iterator.getCurrentTokenColumn();