/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jkiss.dbeaver.ui.editors.sql.syntax; import org.jkiss.dbeaver.Log; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextDoubleClickStrategy; import org.eclipse.jface.text.ITextViewer; /** * Process double clicks in the SQL content. */ public class SQLDoubleClickStrategy implements ITextDoubleClickStrategy { private static final Log log = Log.getLog(SQLDoubleClickStrategy.class); protected ITextViewer fText; protected int curPos; protected int startPos; protected int endPos; protected static char[] fgBrackets = { '(', ')', '[', ']', '\'', '\'', '"', '"' }; /** * Constructs an instance of this class. This is the default constructor. */ public SQLDoubleClickStrategy() { super(); } /** * Handles a double-click action by selecting the current word. * * @see org.eclipse.jface.text.ITextDoubleClickStrategy#doubleClicked(ITextViewer) */ @Override public void doubleClicked(ITextViewer viewer) { // Get the viewer we are dealing with. fText = viewer; // Get the double-click location in the document. curPos = viewer.getSelectedRange().x; if (curPos < 0 || curPos >= fText.getDocument().getLength()) { return; } if (!selectBracketBlock()) { selectWord(); } } /** * Attempts to find and match opening or closing brackets just ahead of the * double-click location. Sets fStartPos and fEndPos to the bracket locations * if found. * * @return true if brackets found and matched, otherwise false */ protected boolean matchBracketsAt() { if (curPos == 0) { return false; } char prevChar, nextChar; int i; int bracketIndex1 = fgBrackets.length; int bracketIndex2 = fgBrackets.length; startPos = -1; endPos = -1; // Get the chars preceding and following the start position. try { IDocument doc = fText.getDocument(); prevChar = doc.getChar(curPos - 1); nextChar = doc.getChar(curPos); // Is the char either an open or close bracket? for (i= 0; i < fgBrackets.length; i = i + 2) { if (prevChar == fgBrackets[i]) { startPos = curPos - 1; bracketIndex1 = i; } } for (i= 1; i < fgBrackets.length; i = i + 2) { if (nextChar == fgBrackets[i]) { endPos = curPos; bracketIndex2 = i; } } // If we found an open bracket, find the matching closing bracket. if (startPos > -1 && bracketIndex1 < bracketIndex2) { endPos = searchForClosingBracket(startPos, prevChar, fgBrackets[bracketIndex1 + 1], doc ); if (endPos > -1) return true; startPos = -1; } // Otherwise if we found a closing bracket, find the matching open bracket. else if (endPos > -1) { startPos = searchForOpenBracket(endPos, fgBrackets[bracketIndex2 - 1], nextChar, doc ); if (startPos > -1) return true; endPos = -1; } } catch (BadLocationException x) { log.debug(x); } return false; } /** * Attempts to determine and set the start (fStartPos) and end (fEndPos) of the word * that was double-clicked. * * @return true if the bounds of the word were successfully determined, otherwise false. */ protected boolean matchWord() { IDocument doc = fText.getDocument(); try { int pos = curPos; char c; // Scan backwards for the start of the word. while (pos >= 0) { c = doc.getChar(pos); // Yes we know this isn't Java code we are parsing but the // Java identifier rule is close enough for now. if (!Character.isJavaIdentifierPart(c)) break; --pos; } startPos = pos; // Scan forward for the end of the word. pos = curPos; int length = doc.getLength(); while (pos < length) { c = doc.getChar(pos); if (!Character.isJavaIdentifierPart(c)) break; ++pos; } endPos = pos; return true; } catch (BadLocationException x) { log.debug(x); } return false; } /** * Returns the position of the closing bracket after startPosition. * * @param startPosition the starting position for the search * @param openBracket the open bracket character * @param closeBracket the close bracker character * @param document the document being searched * @return the location of the closing bracket */ protected int searchForClosingBracket( int startPosition, char openBracket, char closeBracket, IDocument document ) throws BadLocationException { int stack = 1; int closePosition = startPosition + 1; int length = document.getLength(); char nextChar; // Scan forward for the closing bracket. Ignore "nested" bracket pairs. while (closePosition < length && stack > 0) { nextChar = document.getChar( closePosition ); if (nextChar == openBracket && nextChar != closeBracket) stack++; else if (nextChar == closeBracket) stack--; closePosition++; } if (stack == 0) return closePosition - 1; return -1; } /** * Returns the position of the open bracket before startPosition. * * @param startPosition the starting position for the search * @param openBracket the open bracket character * @param closeBracket the close bracket character * @param document the document being searched * @return the location of the open bracket */ protected int searchForOpenBracket( int startPosition, char openBracket, char closeBracket, IDocument document ) throws BadLocationException { int stack = 1; int openPos = startPosition - 1; char nextChar; // Scan backward for the opening bracket. Ignore "nested" bracket pairs. while (openPos >= 0 && stack > 0) { nextChar= document.getChar(openPos); if (nextChar == closeBracket && nextChar != openBracket) stack++; else if (nextChar == openBracket) stack--; openPos--; } if (stack == 0) return openPos + 1; return -1; } /** * Select the area between the selected bracket and the closing bracket. Return * true if successful. * * @return <code>true</code> when selection is OK, <code>false</code> when not */ protected boolean selectBracketBlock() { if (matchBracketsAt()) { if (startPos == endPos) fText.setSelectedRange(startPos, 0); else fText.setSelectedRange(startPos + 1, endPos - startPos - 1); return true; } return false; } /** * Selects the word at the current selection location. */ protected void selectWord() { if (matchWord()) { if (startPos == endPos) fText.setSelectedRange(startPos, 0); else fText.setSelectedRange(startPos + 1, endPos - startPos - 1); } } }