--- a/db.sql.editor/nbproject/project.xml
+++ a/db.sql.editor/nbproject/project.xml
@@ -102,6 +102,15 @@
+ org.netbeans.modules.editor.bracesmatching
+
+
+
+ 0
+ 1.22
+
+
+
org.netbeans.modules.editor.completion
--- a/db.sql.editor/src/org/netbeans/modules/db/sql/editor/SQLBracesMatcher.java
+++ a/db.sql.editor/src/org/netbeans/modules/db/sql/editor/SQLBracesMatcher.java
@@ -0,0 +1,166 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2012 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.db.sql.editor;
+
+import javax.swing.text.AbstractDocument;
+import javax.swing.text.BadLocationException;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.api.editor.mimelookup.MimeRegistrations;
+import org.netbeans.api.lexer.Token;
+import org.netbeans.api.lexer.TokenId;
+import org.netbeans.api.lexer.TokenSequence;
+import org.netbeans.editor.BaseDocument;
+import org.netbeans.modules.csl.api.OffsetRange;
+import org.netbeans.modules.db.sql.lexer.LexerUtilities;
+import org.netbeans.modules.db.sql.lexer.SQLLexer;
+import org.netbeans.modules.db.sql.lexer.SQLTokenId;
+import org.netbeans.spi.editor.bracesmatching.BracesMatcher;
+import org.netbeans.spi.editor.bracesmatching.BracesMatcherFactory;
+import org.netbeans.spi.editor.bracesmatching.MatcherContext;
+
+public class SQLBracesMatcher implements BracesMatcher {
+
+ @MimeRegistrations({
+ @MimeRegistration(mimeType = "text/x-sql", service = BracesMatcherFactory.class)
+ })
+ public static class Factory implements BracesMatcherFactory {
+
+ @Override
+ public SQLBracesMatcher createMatcher(MatcherContext context) {
+ return new SQLBracesMatcher(context);
+ }
+ }
+ private MatcherContext context;
+
+ public SQLBracesMatcher(MatcherContext context) {
+ this.context = context;
+ }
+
+ @Override
+ public int[] findOrigin() throws InterruptedException, BadLocationException {
+ int[] ret = null;
+ ((AbstractDocument) context.getDocument()).readLock();
+ try {
+ BaseDocument doc = (BaseDocument) context.getDocument();
+ int offset = context.getSearchOffset();
+ TokenSequence extends SQLTokenId> ts = LexerUtilities.getTokenSequence(doc, offset);
+
+ if (ts != null) {
+ ts.move(offset);
+
+ if (ts.moveNext()) {
+
+ Token extends SQLTokenId> token = ts.token();
+
+ if (token != null) {
+ TokenId id = token.id();
+
+ if (id == SQLTokenId.LPAREN || id == SQLTokenId.RPAREN) {
+ ret = new int[]{ts.offset(), ts.offset() + token.length()};
+ } else if (id == SQLTokenId.IDENTIFIER || id == SQLTokenId.STRING) {
+ int startOffset = ts.offset();
+ int endOffset = ts.offset() + token.length() - 1;
+ char startChar = token.text().charAt(0);
+ char endChar = token.text().charAt(token.length() - 1);
+ if (offset == startOffset && SQLLexer.isEndIdentifierQuoteChar(startChar, endChar)) {
+ ret = new int[]{startOffset, startOffset + 1};
+ } else if (offset == endOffset && SQLLexer.isEndIdentifierQuoteChar(startChar, endChar)) {
+ ret = new int[]{endOffset, endOffset + 1};
+ }
+ }
+ }
+ }
+ }
+
+ } finally {
+ ((AbstractDocument) context.getDocument()).readUnlock();
+ }
+ return ret;
+ }
+
+ @Override
+ public int[] findMatches() throws InterruptedException, BadLocationException {
+ int[] ret = null;
+ ((AbstractDocument) context.getDocument()).readLock();
+ try {
+ BaseDocument doc = (BaseDocument) context.getDocument();
+ int offset = context.getSearchOffset();
+ TokenSequence extends SQLTokenId> ts = LexerUtilities.getTokenSequence(doc, offset);
+
+ if (ts != null) {
+ ts.move(offset);
+
+ if (ts.moveNext()) {
+
+ Token extends SQLTokenId> token = ts.token();
+
+ if (token != null) {
+ TokenId id = token.id();
+
+ if (id == SQLTokenId.LPAREN) {
+ OffsetRange r = LexerUtilities.findFwd(ts, SQLTokenId.LPAREN.ordinal(),
+ SQLTokenId.RPAREN.ordinal());
+ ret = new int[]{r.getStart(), r.getEnd()};
+ } else if (id == SQLTokenId.RPAREN) {
+ OffsetRange r = LexerUtilities.findBwd(ts, SQLTokenId.LPAREN.ordinal(),
+ SQLTokenId.RPAREN.ordinal());
+ ret = new int[]{r.getStart(), r.getEnd()};
+ } else if (id == SQLTokenId.IDENTIFIER || id == SQLTokenId.STRING) {
+ int startOffset = ts.offset();
+ int endOffset = ts.offset() + token.length() - 1;
+ char startChar = token.text().charAt(0);
+ char endChar = token.text().charAt(token.length() - 1);
+ if (offset == startOffset && SQLLexer.isEndIdentifierQuoteChar(startChar, endChar)) {
+ ret = new int[]{endOffset, endOffset + 1};
+ } else if (offset == endOffset && SQLLexer.isEndIdentifierQuoteChar(startChar, endChar)) {
+ ret = new int[]{startOffset, startOffset + 1};
+ }
+ }
+ }
+ }
+ }
+ } finally {
+ ((AbstractDocument) context.getDocument()).readUnlock();
+ }
+ return ret;
+ }
+}
--- a/db.sql.editor/src/org/netbeans/modules/db/sql/lexer/LexerUtilities.java
+++ a/db.sql.editor/src/org/netbeans/modules/db/sql/lexer/LexerUtilities.java
@@ -0,0 +1,159 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2012 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.db.sql.lexer;
+
+import java.util.List;
+import javax.swing.text.Document;
+import org.netbeans.api.lexer.Token;
+import org.netbeans.api.lexer.TokenHierarchy;
+import org.netbeans.api.lexer.TokenId;
+import org.netbeans.api.lexer.TokenSequence;
+import org.netbeans.modules.csl.api.OffsetRange;
+
+/**
+ * Based on documentation on netbeans.org: http://wiki.netbeans.org/Netbeans_Antlr_BracesMatching
+ */
+public class LexerUtilities {
+
+ @SuppressWarnings("unchecked")
+ public static TokenSequence getTokenSequence(Document doc, int offset) {
+ TokenHierarchy th = TokenHierarchy.get(doc);
+ TokenSequence ts = th == null ? null : th.tokenSequence(SQLTokenId.language());
+
+ if (ts == null) {
+ // Possibly an embedding scenario such as an RHTML file
+ // First try with backward bias true
+ List> list = th.embeddedTokenSequences(offset, true);
+
+ for (TokenSequence extends TokenId> t : list) {
+ if (t.language() == SQLTokenId.language()) {
+ ts = (TokenSequence) t;
+ break;
+ }
+ }
+
+ if (ts == null) {
+ list = th.embeddedTokenSequences(offset, false);
+ for (TokenSequence extends TokenId> t : list) {
+ if (t.language() == SQLTokenId.language()) {
+ ts = (TokenSequence) t;
+ break;
+ }
+ }
+ }
+ }
+
+ return ts;
+ }
+
+ /**
+ * Search forwards in the token sequence until a matching closing token is
+ * found so keeps track of nested pairs of up-down eg (()) is ignored if
+ * we're searching for a )
+ *
+ * @param ts the TokenSequence set to the position after an up
+ * @param up the opening token eg { or [
+ * @param down the closing token eg } or ]
+ * @return the Range of closing token in our case 1 char
+ */
+ public static OffsetRange findFwd(TokenSequence extends SQLTokenId> ts, int up, int down) {
+ int balance = 0;
+
+ while (ts.moveNext()) {
+ Token extends SQLTokenId> token = ts.token();
+
+ if (token.id().ordinal() == up) {
+ balance++;
+ } else if (token.id().ordinal() == down) {
+ if (balance == 0) {
+ return new OffsetRange(ts.offset(), ts.offset() + token.length());
+ }
+ balance--;
+ }
+ }
+
+ return OffsetRange.NONE;
+ }
+
+ /**
+ * Search forwards in the token sequence until a matching closing token is
+ * found so keeps track of nested pairs of up-down eg (()) is ignored if
+ * we're searching for a )
+ *
+ * @param ts the TokenSequence set to the position after an up
+ * @param up the opening token eg { or [
+ * @param down the closing token eg } or ]
+ * @return the Range of closing token in our case 1 char
+ */
+ public static OffsetRange findBwd(TokenSequence extends SQLTokenId> ts, int up, int down) {
+ int balance = 0;
+
+ while (ts.movePrevious()) {
+ Token extends SQLTokenId> token = ts.token();
+
+ if (token.id().ordinal() == up) {
+ if (balance == 0) {
+ return new OffsetRange(ts.offset(), ts.offset() + token.length());
+ }
+
+ balance++;
+ } else if (token.id().ordinal() == down) {
+ balance--;
+ }
+ }
+
+ return OffsetRange.NONE;
+ }
+
+ public static boolean textEquals(CharSequence text1, char... text2) {
+ int len = text1.length();
+ if (len == text2.length) {
+ for (int i = len - 1; i >= 0; i--) {
+ if (text1.charAt(i) != text2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+}