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.

This commit is contained in:
dgeorge 2011-10-28 14:23:55 -07:00
commit e0048b4967
5 changed files with 300 additions and 94 deletions

View file

@ -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;
});

View file

@ -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 <fabian AT ajax DOT org>
*
* 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;
});

View file

@ -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(["(()(", "())))"]);

View file

@ -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));
},

112
lib/ace/token_iterator.js Normal file
View file

@ -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 <fabian AT ajax DOT org>
*
* 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;
});