/* ***** BEGIN LICENSE BLOCK ***** * Distributed under the BSD license: * * Copyright (c) 2010, Ajax.org B.V. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Ajax.org B.V. nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ***** END LICENSE BLOCK ***** */ define(function(require, exports, module) { "use strict"; var TokenIterator = require("../token_iterator").TokenIterator; var Range = require("../range").Range; function BracketMatch() { this.findMatchingBracket = function(position, chr) { if (position.column == 0) return null; var charBeforeCursor = chr || 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.getBracketRange = function(pos) { var line = this.getLine(pos.row); var before = true, range; var chr = line.charAt(pos.column-1); var match = chr && chr.match(/([\(\[\{])|([\)\]\}])/); if (!match) { chr = line.charAt(pos.column); pos = {row: pos.row, column: pos.column + 1}; match = chr && chr.match(/([\(\[\{])|([\)\]\}])/); before = false; } if (!match) return null; if (match[1]) { var bracketPos = this.$findClosingBracket(match[1], pos); if (!bracketPos) return null; range = Range.fromPoints(pos, bracketPos); if (!before) { range.end.column++; range.start.column--; } range.cursor = range.end; } else { var bracketPos = this.$findOpeningBracket(match[2], pos); if (!bracketPos) return null; range = Range.fromPoints(bracketPos, pos); if (!before) { range.start.column++; range.end.column--; } range.cursor = range.start; } return range; }; this.$brackets = { ")": "(", "(": ")", "]": "[", "[": "]", "{": "}", "}": "{" }; this.$findOpeningBracket = function(bracket, position, typeRe) { var openBracket = this.$brackets[bracket]; var depth = 1; var iterator = new TokenIterator(this, position.row, position.column); var token = iterator.getCurrentToken(); if (!token) token = iterator.stepForward(); if (!token) return; if (!typeRe){ typeRe = new RegExp( "(\\.?" + token.type.replace(".", "\\.").replace("rparen", ".paren") + ")+" ); } // Start searching in token, just before the character at position.column var valueIndex = position.column - iterator.getCurrentTokenColumn() - 2; var value = token.value; while (true) { while (valueIndex >= 0) { var chr = value.charAt(valueIndex); if (chr == openBracket) { depth -= 1; if (depth == 0) { return {row: iterator.getCurrentTokenRow(), column: valueIndex + iterator.getCurrentTokenColumn()}; } } else if (chr == bracket) { depth += 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)); if (token == null) break; value = token.value; valueIndex = value.length - 1; } return null; }; this.$findClosingBracket = function(bracket, position, typeRe) { var closingBracket = this.$brackets[bracket]; var depth = 1; var iterator = new TokenIterator(this, position.row, position.column); var token = iterator.getCurrentToken(); if (!token) token = iterator.stepForward(); if (!token) return; if (!typeRe){ typeRe = new RegExp( "(\\.?" + token.type.replace(".", "\\.").replace("lparen", ".paren") + ")+" ); } // 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 (valueIndex < valueLength) { var chr = value.charAt(valueIndex); if (chr == closingBracket) { depth -= 1; if (depth == 0) { return {row: iterator.getCurrentTokenRow(), column: valueIndex + iterator.getCurrentTokenColumn()}; } } else if (chr == bracket) { depth += 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)); if (token == null) break; valueIndex = 0; } return null; }; } exports.BracketMatch = BracketMatch; });