# HG changeset patch # User Matthias Bläsing # Date 1337458283 -7200 # Node ID 7e87753c03c7f666cd0136289a1e2c0bdb52e4b5 # Parent dc63d18ccaac7e21b7e8692f382003bf772ddac7 * Add BacesMatcher to SQL Editor diff --git a/db.sql.editor/nbproject/project.xml b/db.sql.editor/nbproject/project.xml --- a/db.sql.editor/nbproject/project.xml +++ b/db.sql.editor/nbproject/project.xml @@ -102,6 +102,15 @@ + org.netbeans.modules.editor.bracesmatching + + + + 0 + 1.22 + + + org.netbeans.modules.editor.completion diff --git a/db.sql.editor/src/org/netbeans/modules/db/sql/editor/SQLBracesMatcher.java b/db.sql.editor/src/org/netbeans/modules/db/sql/editor/SQLBracesMatcher.java new file mode 100644 --- /dev/null +++ b/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 ts = LexerUtilities.getTokenSequence(doc, offset); + + if (ts != null) { + ts.move(offset); + + if (ts.moveNext()) { + + Token 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 ts = LexerUtilities.getTokenSequence(doc, offset); + + if (ts != null) { + ts.move(offset); + + if (ts.moveNext()) { + + Token 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; + } +} diff --git a/db.sql.editor/src/org/netbeans/modules/db/sql/lexer/LexerUtilities.java b/db.sql.editor/src/org/netbeans/modules/db/sql/lexer/LexerUtilities.java new file mode 100644 --- /dev/null +++ b/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 t : list) { + if (t.language() == SQLTokenId.language()) { + ts = (TokenSequence) t; + break; + } + } + + if (ts == null) { + list = th.embeddedTokenSequences(offset, false); + for (TokenSequence 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 ts, int up, int down) { + int balance = 0; + + while (ts.moveNext()) { + Token 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 ts, int up, int down) { + int balance = 0; + + while (ts.movePrevious()) { + Token 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; + } +}