/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.query.ui.sqleditor.sql;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IDocument;
import org.teiid.query.ui.UiConstants;
import org.teiid.query.ui.UiPlugin;
/**
* SqlAutoIndentStrategy
*
* @since 8.0
*/
public class SqlAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy
implements SqlFormattingConstants,
UiConstants {
private String sLastKeywordFound = null;
public SqlAutoIndentStrategy() {
}
/* (non-Javadoc)
* Method declared on IAutoIndentStrategy
` */
@Override
public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
// System.out.println( "[SqlAutoIndentStrategy.customizeDocumentCommand] c.text is: [" + c.text + "]" + " d.get() is: " + d.get() ); //$NON-NLS-1$
/*
* jh note (6/16/2004): The difficulty with this is that we have been supporting all text changes,
* including DELETE and BACKSPACE. The jdt implementations of this (indent on newline or insert
* newline on curly brace) do not support delete and backspace. So the cure is to stop attempting
* to support it. If a user backspaces over "WHERE a > b", leaving "WHE", we do nothing.
* If they then type "RE" we will handle that, because we handle all newly typed characters.
*/
// Solution: do not respond to backspace or delete:
// if DELETE or BACKSPACE case, take no action
// (DELETE and BACKSPACE have a NON-ZERO length, and a text of EMPTY_STRING)
if ( c.length > 0 && c.text.equals( EMPTY_STRING ) ) {
return;
}
if (c.text != null && endsWithClauseKeyword(d, c)) {
smartInsertAfterKeyword(d, c);
}
}
/**
* Returns whether or not the text ends with one of the given search strings.
*/
private boolean endsWithClauseKeyword(IDocument d, DocumentCommand c) {
String sDocTextUpcased = d.get();
// System.out.println( "[SqlAutoIndentStrategy.endsWithClauseKeyword] sDocTextUpcased BEFORE trim: [" + sDocTextUpcased + "]" ); //$NON-NLS-1$
int iLastCharIndex = sDocTextUpcased.length() - 1;
if ( sDocTextUpcased.length() > 0
&& ( sDocTextUpcased.charAt( iLastCharIndex ) == chNEWLINE )
) {
sDocTextUpcased = sDocTextUpcased.trim();
}
// System.out.println( "[SqlAutoIndentStrategy.endsWithClauseKeyword] sDocTextUpcased AFTER trim: [" + sDocTextUpcased + "]" ); //$NON-NLS-1$
// check to see if user typed a single whitespace (ie, no copying and pasting):
boolean rv;
if (c.text != null
&& isAllWhitespace(c.text)) {
// yes, whitespace was typed (or pasted) in.
// see if previous text ended with a keyword
rv = endsWithClauseKeyword(sDocTextUpcased);
} else {
// person is still typing a name; don't allow modifications
rv = false;
} // endif
return rv;
}
/**
* @param text
* @return
*/
private boolean isAllWhitespace(String text) {
for (int i = 0; i < text.length(); i++) {
if (!Character.isWhitespace(text.charAt(i))) {
// as soon as we find a non-whitespace, we know it is not all whitespace
return false;
} // endif
} // endfor
return true;
}
/**
* @param sDocTextUpcased
* @return
*/
private boolean endsWithClauseKeyword(String sDocTextUpcased) {
sDocTextUpcased = sDocTextUpcased.toUpperCase();
for (int i= 0; i < KEYWORDS.length; i++) {
if( sDocTextUpcased.endsWith( KEYWORDS[i] ) ) {
sLastKeywordFound = KEYWORDS[i];
// only return true if there is white space before this keyword
StringBuffer sb = new StringBuffer( sDocTextUpcased );
// calc index to char before the keyword:
int iIndex = sb.length() - sLastKeywordFound.length() - 1;
// System.out.println( "[SqlAutoIndentStrategy.endsWithClauseKeyword] iIndex: " + iIndex ); //$NON-NLS-1$
if ( iIndex > -1 ) {
// System.out.println( "[SqlAutoIndentStrategy.endsWithClauseKeyword] iIndex > -1 is TRUE, will test for space " ); //$NON-NLS-1$
char cPrecedingChar = sb.charAt( iIndex );
if( cPrecedingChar == SPACE || cPrecedingChar == chNEWLINE || cPrecedingChar == chINDENT ) {
// System.out.println( "[SqlAutoIndentStrategy.endsWithClauseKeyword] returning true A " ); //$NON-NLS-1$
return true;
}
} else {
// System.out.println( "[SqlAutoIndentStrategy.endsWithClauseKeyword] returning true B " ); //$NON-NLS-1$
return true;
}
}
}
// System.out.println( "[SqlAutoIndentStrategy.endsWithClauseKeyword] About to return false C" ); //$NON-NLS-1$
return false;
}
/**
* Set the indent of a bracket based on the command provided in the supplied document.
* @param document - the document being parsed
* @param command - the command being performed
*/
protected void smartInsertAfterKeyword(IDocument document, DocumentCommand command) {
if (command.offset == -1 || document.getLength() == 0) {
sLastKeywordFound = null;
return;
}
try {
// String sDoc =
// document.get();
int p = (command.offset == document.getLength() ? command.offset - 1 : command.offset);
int line = document.getLineOfOffset(p);
int start = document.getLineOffset(line);
int whiteend = findEndOfWhiteSpace(document, start, command.offset);
String sKeyword = getEndingKeyword( document, command );
String sNewCommandText = EMPTY_STRING;
// 1. put the keyword on a new line, depending on preference
// (but only if there is not already a newline immidiately before it)
int iBefore = p - ( sKeyword.length() - 1 );
char chCharOneBeforeKeyword = ' ';
char chCharTwoBeforeKeyword = ' ';
if ( iBefore > 0 ) {
chCharOneBeforeKeyword = document.getChar( iBefore );
chCharTwoBeforeKeyword = document.getChar( iBefore -1 );
}
if ( whiteend > 0
&& chCharOneBeforeKeyword != chNEWLINE
&& chCharTwoBeforeKeyword != chNEWLINE
&& startClausesOnNewLine() ) {
sNewCommandText = NEWLINE;
}
// 2. add in the keyword
sNewCommandText += sKeyword
+ NEWLINE;
// 3. indent the content, depending on preference
// jh note: the indent makes no sense unless the newline is also requested,
// so I am linking them.
if ( startClausesOnNewLine() && indentClauseContent() ) {
sNewCommandText += INDENT;
}
command.length = sNewCommandText.length();
/* Strategy:
*
* command.offset should be: command.offset - (length of sKeyword, minus 1 ) =
* 9 6 1 4
* 1234567890
* SELEC
* \nSELECT\nbbbb
*
* iOverage should be: sNewCommandText.length() - (length of sKeyword, minus 1)
*/
/* PAF, 2004-12-03: The above "minus 1" clause no longer applies,
* since we are now waiting a character before reformatting.
*/
command.offset = command.offset - ( sKeyword.length() );
int iOverage = sNewCommandText.length() - ( sKeyword.length() );
command.text = sNewCommandText;
command.doit = true;
// expand the document to fit the new content: increate doc length by iOverage spaces
String sCurrentDocText = document.get();
int iOrigLength = sCurrentDocText.length();
StringBuffer replaceText= new StringBuffer( iOrigLength );
replaceText.replace(0, iOrigLength, sCurrentDocText );
// append just what you need, and use blanks
for( int i = 0; i < iOverage; i++ ) {
replaceText.append( ' ' ); // add a character blank
}
document.set( replaceText.toString() );
// jh: to test: force the update here so we can catch the exception...
// Copy the 'execute' method from DocumentCommand to this class:
// execute( document, command );
} catch (BadLocationException excp) {
// System.out.println("AutoIndent.error.bad_location_2"); //$NON-NLS-1$
}
sLastKeywordFound = null;
}
private String getEndingKeyword( IDocument d, DocumentCommand command ) {
// use the keyword 'endsWithClauseKeyword' found, if it is available. Avoid duplication!
if ( sLastKeywordFound != null ) {
return sLastKeywordFound;
}
String sDocTextUpcased = d.get();
if ( sDocTextUpcased.length() > 0 && sDocTextUpcased.charAt( sDocTextUpcased.length() - 1 ) == chNEWLINE ) {
sDocTextUpcased = sDocTextUpcased.trim();
}
sDocTextUpcased += command.text;
sDocTextUpcased = sDocTextUpcased.toUpperCase();
for (int i= 0; i < KEYWORDS.length; i++) {
if( sDocTextUpcased.endsWith( KEYWORDS[i] ) ) {
return KEYWORDS[i];
}
}
return EMPTY_STRING;
}
private boolean startClausesOnNewLine() {
return UiPlugin.getDefault().getPreferenceStore()
.getBoolean( UiConstants.Prefs.START_CLAUSES_ON_NEW_LINE );
}
private boolean indentClauseContent() {
return UiPlugin.getDefault().getPreferenceStore()
.getBoolean( UiConstants.Prefs.INDENT_CLAUSE_CONTENT );
}
}