content: /*******************************************************************************
   * Copyright (c) 2000, 2010 IBM Corporation and others.
   * All rights reserved. This program and the accompanying materials
   * are made available under the terms of the Eclipse Public License v1.0
   * which accompanies this distribution, and is available at
   * http://www.eclipse.org/legal/epl-v10.html
   *
   * Contributors:
   *     IBM Corporation - initial API and implementation
   *     Nikolay Metchev - Fixed https://bugs.eclipse.org/bugs/show_bug.cgi?id=29909
   *     Tom Eicher (Avaloq Evolution AG) - block selection mode
   *******************************************************************************/
  package org.eclipse.wst.jsdt.internal.ui.text.java;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.jface.preference.IPreferenceStore;
  import org.eclipse.jface.text.BadLocationException;
  import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
  import org.eclipse.jface.text.Document;
  import org.eclipse.jface.text.DocumentCommand;
  import org.eclipse.jface.text.DocumentRewriteSession;
  import org.eclipse.jface.text.DocumentRewriteSessionType;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.jface.text.IRegion;
  import org.eclipse.jface.text.ITypedRegion;
  import org.eclipse.jface.text.Region;
  import org.eclipse.jface.text.TextUtilities;
  import org.eclipse.jface.text.rules.FastPartitioner;
  import org.eclipse.jface.text.source.ISourceViewer;
  import org.eclipse.ui.IEditorPart;
  import org.eclipse.ui.IWorkbenchPage;
  import org.eclipse.ui.texteditor.ITextEditorExtension3;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.JavaScriptCore;
  import org.eclipse.wst.jsdt.core.ToolFactory;
  import org.eclipse.wst.jsdt.core.compiler.IProblem;
  import org.eclipse.wst.jsdt.core.compiler.IScanner;
  import org.eclipse.wst.jsdt.core.compiler.ITerminalSymbols;
  import org.eclipse.wst.jsdt.core.compiler.InvalidInputException;
  import org.eclipse.wst.jsdt.core.dom.AST;
  import org.eclipse.wst.jsdt.core.dom.ASTNode;
  import org.eclipse.wst.jsdt.core.dom.ASTParser;
  import org.eclipse.wst.jsdt.core.dom.DoStatement;
  import org.eclipse.wst.jsdt.core.dom.Expression;
  import org.eclipse.wst.jsdt.core.dom.ForStatement;
  import org.eclipse.wst.jsdt.core.dom.IfStatement;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.Statement;
  import org.eclipse.wst.jsdt.core.dom.WhileStatement;
  import org.eclipse.wst.jsdt.core.formatter.DefaultCodeFormatterConstants;
  import org.eclipse.wst.jsdt.internal.corext.dom.NodeFinder;
  import org.eclipse.wst.jsdt.internal.corext.util.CodeFormatterUtil;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.text.FastJavaPartitionScanner;
  import org.eclipse.wst.jsdt.internal.ui.text.JavaHeuristicScanner;
  import org.eclipse.wst.jsdt.internal.ui.text.JavaIndenter;
  import org.eclipse.wst.jsdt.internal.ui.text.Symbols;
  import org.eclipse.wst.jsdt.ui.PreferenceConstants;
  import org.eclipse.wst.jsdt.ui.text.IJavaScriptPartitions;
  
  
  /**
   * Auto indent strategy sensitive to brackets.
   */
  public class JavaAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy {
  
  	/** The line comment introducer. Value is "{@value}" */
  	private static final String LINE_COMMENT= "//"; //$NON-NLS-1$
  
  		private static class CompilationUnitInfo {
  
  			char[] buffer;
  			int delta;
  
  			CompilationUnitInfo(char[] buffer, int delta) {
  				this.buffer= buffer;
  				this.delta= delta;
  			}
  		}
  
  
  	private boolean fCloseBrace;
  	private boolean fIsSmartMode;
  	private boolean fIsSmartTab;
  
  	private String fPartitioning;
  	private final IJavaScriptProject fProject;
  	private static IScanner fgScanner= ToolFactory.createScanner(false, false, false, false);
  	/**
  	 * The viewer.
  	 * @since 3.5
  	 */
  	private final ISourceViewer fViewer;
  
  	/**
  	 * Creates a new Java auto indent strategy for the given document partitioning.
  	 *
  	 * @param partitioning the document partitioning
  	 * @param project the project to get formatting preferences from, or null to use default preferences
  	 * @param viewer the source viewer that this strategy is attached to
  	 */
  	public JavaAutoIndentStrategy(String partitioning, IJavaScriptProject project, ISourceViewer viewer) {
  		fPartitioning= partitioning;
  		fProject= project;
  		fViewer= viewer;
   	}
  
  	private int getBracketCount(IDocument d, int startOffset, int endOffset, boolean ignoreCloseBrackets) throws BadLocationException {
  
  		int bracketCount= 0;
  		while (startOffset < endOffset) {
  			char curr= d.getChar(startOffset);
  			startOffset++;
  			switch (curr) {
  				case '/' :
  					if (startOffset < endOffset) {
  						char next= d.getChar(startOffset);
  						if (next == '*') {
  							// a comment starts, advance to the comment end
  							startOffset= getCommentEnd(d, startOffset + 1, endOffset);
  						} else if (next == '/') {
  							// '//'-comment: nothing to do anymore on this line
  							startOffset= endOffset;
  						}
  					}
  					break;
  				case '*' :
  					if (startOffset < endOffset) {
  						char next= d.getChar(startOffset);
  						if (next == '/') {
  							// we have been in a comment: forget what we read before
  							bracketCount= 0;
  							startOffset++;
  						}
  					}
  					break;
  				case '{' :
  					bracketCount++;
  					ignoreCloseBrackets= false;
  					break;
  				case '}' :
  					if (!ignoreCloseBrackets) {
  						bracketCount--;
  					}
  					break;
  				case '"' :
  				case '\'' :
  					startOffset= getStringEnd(d, startOffset, endOffset, curr);
  					break;
  				default :
  					}
  		}
  		return bracketCount;
  	}
  
  	// ----------- bracket counting ------------------------------------------------------
  
  	private int getCommentEnd(IDocument d, int offset, int endOffset) throws BadLocationException {
  		while (offset < endOffset) {
  			char curr= d.getChar(offset);
  			offset++;
  			if (curr == '*') {
  				if (offset < endOffset && d.getChar(offset) == '/') {
  					return offset + 1;
  				}
  			}
  		}
  		return endOffset;
  	}
  
  	private String getIndentOfLine(IDocument d, int line) throws BadLocationException {
  		if (line > -1) {
  			int start= d.getLineOffset(line);
  			int end= start + d.getLineLength(line) - 1;
  			int whiteEnd= findEndOfWhiteSpace(d, start, end);
  			return d.get(start, whiteEnd - start);
  		} else {
  			return ""; //$NON-NLS-1$
  		}
  	}
  
  	private int getStringEnd(IDocument d, int offset, int endOffset, char ch) throws BadLocationException {
  		while (offset < endOffset) {
  			char curr= d.getChar(offset);
  			offset++;
  			if (curr == '\\') {
  				// ignore escaped characters
  				offset++;
  			} else if (curr == ch) {
  				return offset;
  			}
  		}
  		return endOffset;
  	}
  
  	private void smartIndentAfterClosingBracket(IDocument d, DocumentCommand c) {
  		if (c.offset == -1 || d.getLength() == 0)
  			return;
  
  		try {
  			int p= (c.offset == d.getLength() ? c.offset - 1 : c.offset);
  			int line= d.getLineOfOffset(p);
  			int start= d.getLineOffset(line);
  			int whiteend= findEndOfWhiteSpace(d, start, c.offset);
  
  			JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
  			JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
  
  			// shift only when line does not contain any text up to the closing bracket
  			if (whiteend == c.offset) {
  				// evaluate the line with the opening bracket that matches out closing bracket
  				int reference= indenter.findReferencePosition(c.offset, false, true, false, false);
  				int indLine= d.getLineOfOffset(reference);
  				if (indLine != -1 && indLine != line) {
  					// take the indent of the found line
  					StringBuffer replaceText= new StringBuffer(getIndentOfLine(d, indLine));
  					// add the rest of the current line including the just added close bracket
  					replaceText.append(d.get(whiteend, c.offset - whiteend));
  					replaceText.append(c.text);
  					// modify document command
  					c.length += c.offset - start;
  					c.offset= start;
  					c.text= replaceText.toString();
  				}
  			}
  		} catch (BadLocationException e) {
  			JavaScriptPlugin.log(e);
  		}
  	}
  
  	private void smartIndentAfterOpeningBracket(IDocument d, DocumentCommand c) {
  		if (c.offset < 1 || d.getLength() == 0)
  			return;
  
  		JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
  
  		int p= (c.offset == d.getLength() ? c.offset - 1 : c.offset);
  
  		try {
  			// current line
  			int line= d.getLineOfOffset(p);
  			int lineOffset= d.getLineOffset(line);
  
  			// make sure we don't have any leading comments etc.
  			if (d.get(lineOffset, p - lineOffset).trim().length() != 0)
  				return;
  
  			// line of last Java code
  			int pos= scanner.findNonWhitespaceBackward(p, JavaHeuristicScanner.UNBOUND);
  			if (pos == -1)
  				return;
  			int lastLine= d.getLineOfOffset(pos);
  
  			// only shift if the last java line is further up and is a braceless block candidate
  			if (lastLine < line) {
  
  				JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
  				StringBuffer indent= indenter.computeIndentation(p, true);
  				String toDelete= d.get(lineOffset, c.offset - lineOffset);
  				if (indent != null && !indent.toString().equals(toDelete)) {
  					c.text= indent.append(c.text).toString();
  					c.length += c.offset - lineOffset;
  					c.offset= lineOffset;
  				}
  			}
  
  		} catch (BadLocationException e) {
  			JavaScriptPlugin.log(e);
  		}
  
  	}
  
  	private void smartIndentAfterNewLine(IDocument d, DocumentCommand c) {
  		JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
  		JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
  		StringBuffer indent= indenter.computeIndentation(c.offset);
  		if (indent == null)
  			indent= new StringBuffer();
  
  		int docLength= d.getLength();
  		if (c.offset == -1 || docLength == 0)
  			return;
  
  		try {
  			int p= (c.offset == docLength ? c.offset - 1 : c.offset);
  			int line= d.getLineOfOffset(p);
  
  			StringBuffer buf= new StringBuffer(c.text + indent);
  
  
  			IRegion reg= d.getLineInformation(line);
  			int lineEnd= reg.getOffset() + reg.getLength();
  
  			int contentStart= findEndOfWhiteSpace(d, c.offset, lineEnd);
  			c.length=  Math.max(contentStart - c.offset, 0);
  
  			int start= reg.getOffset();
  			ITypedRegion region= TextUtilities.getPartition(d, fPartitioning, start, true);
  			if (IJavaScriptPartitions.JAVA_DOC.equals(region.getType()))
  				start= d.getLineInformationOfOffset(region.getOffset()).getOffset();
  
  			// insert closing brace on new line after an unclosed opening brace
  			if (getBracketCount(d, start, c.offset, true) > 0 && closeBrace() && !isClosed(d, c.offset, c.length)) {
  				c.caretOffset= c.offset + buf.length();
  				c.shiftsCaret= false;
  
  				// copy old content of line behind insertion point to new line
  				// unless we think we are inserting an anonymous type definition
  
  				if (c.offset == 0 || computeAnonymousPosition(d, c.offset - 1, fPartitioning, lineEnd) == -1) {
  					if (lineEnd - contentStart > 0) {
  						c.length=  lineEnd - c.offset;
  						buf.append(d.get(contentStart, lineEnd - contentStart).toCharArray());
  					}
  				}
  
  				buf.append(TextUtilities.getDefaultLineDelimiter(d));
  				StringBuffer reference= null;
  				int nonWS= findEndOfWhiteSpace(d, start, lineEnd);
  				if (nonWS < c.offset && d.getChar(nonWS) == '{')
  					reference= new StringBuffer(d.get(start, nonWS - start));
  				else
  					reference= indenter.getReferenceIndentation(c.offset);
  				if (reference != null)
  					buf.append(reference);
  				buf.append('}');
  			}
  			// insert extra line upon new line between two braces
  			else if (c.offset > start && contentStart < lineEnd && d.getChar(contentStart) == '}') {
  				int firstCharPos= scanner.findNonWhitespaceBackward(c.offset - 1, start);
  				if (firstCharPos != JavaHeuristicScanner.NOT_FOUND && d.getChar(firstCharPos) == '{') {
  					c.caretOffset= c.offset + buf.length();
  					c.shiftsCaret= false;
  
  					StringBuffer reference= null;
  					int nonWS= findEndOfWhiteSpace(d, start, lineEnd);
  					if (nonWS < c.offset && d.getChar(nonWS) == '{')
  						reference= new StringBuffer(d.get(start, nonWS - start));
  					else
  						reference= indenter.getReferenceIndentation(c.offset);
  
  					buf.append(TextUtilities.getDefaultLineDelimiter(d));
  
  					if (reference != null)
  						buf.append(reference);
  				}
  			}
  			c.text= buf.toString();
  
  		} catch (BadLocationException e) {
  			JavaScriptPlugin.log(e);
  		}
  	}
  
  	/**
  	 * Computes an insert position for an opening brace if <code>offset</code> maps to a position in
  	 * <code>document</code> with a expression in parenthesis that will take a block after the closing parenthesis.
  	 *
  	 * @param document the document being modified
  	 * @param offset the offset of the caret position, relative to the line start.
  	 * @param partitioning the document partitioning
  	 * @param max the max position
  	 * @return an insert position relative to the line start if <code>line</code> contains a parenthesized expression that can be followed by a block, -1 otherwise
  	 */
  	private static int computeAnonymousPosition(IDocument document, int offset, String partitioning,  int max) {
  		// find the opening parenthesis for every closing parenthesis on the current line after offset
  		// return the position behind the closing parenthesis if it looks like a method declaration
  		// or an expression for an if, while, for, catch statement
  
  		JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
  		int pos= offset;
  		int length= max;
  		int scanTo= scanner.scanForward(pos, length, '}');
  		if (scanTo == -1)
  			scanTo= length;
  
  		int closingParen= findClosingParenToLeft(scanner, pos) - 1;
  		boolean hasNewToken= looksLikeAnonymousClassDef(document, partitioning, scanner, pos);
  		int openingParen= -1;
  		while (true) {
  			int startScan= closingParen + 1;
  			closingParen= scanner.scanForward(startScan, scanTo, ')');
  			if (closingParen == -1) {
  				if (hasNewToken && openingParen != -1)
  					return openingParen + 1;
  				break;
  			}
  
  			openingParen= scanner.findOpeningPeer(closingParen - 1, '(', ')');
  
  			// no way an expression at the beginning of the document can mean anything
  			if (openingParen < 1)
  				break;
  
  			// only select insert positions for parenthesis currently embracing the caret
  			if (openingParen > pos)
  				continue;
  
  			if (looksLikeAnonymousClassDef(document, partitioning, scanner, openingParen - 1))
  				return closingParen + 1;
  
  		}
  
  		return -1;
  	}
  
  	/**
  	 * Finds a closing parenthesis to the left of <code>position</code> in document, where that parenthesis is only
  	 * separated by whitespace from <code>position</code>. If no such parenthesis can be found, <code>position</code> is returned.
  	 * 
  	 * @param scanner the java heuristic scanner set up on the document
  	 * @param position the first character position in <code>document</code> to be considered
  	 * @return the position of a closing parenthesis left to <code>position</code> separated only by whitespace, or <code>position</code> if no parenthesis can be found
  	 */
  	private static int findClosingParenToLeft(JavaHeuristicScanner scanner, int position) {
  		if (position < 1)
  			return position;
  
  		if (scanner.previousToken(position - 1, JavaHeuristicScanner.UNBOUND) == Symbols.TokenRPAREN)
  			return scanner.getPosition() + 1;
  		return position;
  	}
  
  	/**
  	 * Checks whether the content of <code>document</code> in the range (<code>offset</code>, <code>length</code>)
  	 * contains the <code>new</code> keyword.
  	 *
  	 * @param document the document being modified
  	 * @param offset the first character position in <code>document</code> to be considered
  	 * @param length the length of the character range to be considered
  	 * @param partitioning the document partitioning
  	 * @return <code>true</code> if the specified character range contains a <code>new</code> keyword, <code>false</code> otherwise.
  	 */
  	private static boolean isNewMatch(IDocument document, int offset, int length, String partitioning) {
  		Assert.isTrue(length >= 0);
  		Assert.isTrue(offset >= 0);
  		Assert.isTrue(offset + length < document.getLength() + 1);
  
  		try {
  			String text= document.get(offset, length);
  			int pos= text.indexOf("new"); //$NON-NLS-1$
  
  			while (pos != -1 && !isDefaultPartition(document, pos + offset, partitioning))
  				pos= text.indexOf("new", pos + 2); //$NON-NLS-1$
  
  			if (pos < 0)
  				return false;
  
  			if (pos != 0 && Character.isJavaIdentifierPart(text.charAt(pos - 1)))
  				return false;
  
  			if (pos + 3 < length && Character.isJavaIdentifierPart(text.charAt(pos + 3)))
  				return false;
  
  			return true;
  
  		} catch (BadLocationException e) {
  		}
  		return false;
  	}
  
  	/**
  	 * Checks whether the content of <code>document</code> at <code>position</code> looks like an
  	 * anonymous class definition. <code>position</code> must be to the left of the opening
  	 * parenthesis of the definition's parameter list.
  	 *
  	 * @param document the document being modified
  	 * @param partitioning the document partitioning
  	 * @param scanner the scanner
  	 * @param position the first character position in <code>document</code> to be considered
  	 * @return <code>true</code> if the content of <code>document</code> looks like an anonymous class definition, <code>false</code> otherwise
  	 */
  	private static boolean looksLikeAnonymousClassDef(IDocument document, String partitioning, JavaHeuristicScanner scanner, int position) {
  		int previousCommaParenEqual= scanner.scanBackward(position - 1, JavaHeuristicScanner.UNBOUND, new char[] {',', '(', '='});
  		if (previousCommaParenEqual == -1 || position < previousCommaParenEqual + 5) // 2 for borders, 3 for "new"
  			return false;
  
  		if (isNewMatch(document, previousCommaParenEqual + 1, position - previousCommaParenEqual - 2, partitioning))
  			return true;
  
  		return false;
  	}
  
  	/**
  	 * Checks whether <code>position</code> resides in a default (Java) partition of <code>document</code>.
  	 *
  	 * @param document the document being modified
  	 * @param position the position to be checked
  	 * @param partitioning the document partitioning
  	 * @return <code>true</code> if <code>position</code> is in the default partition of <code>document</code>, <code>false</code> otherwise
  	 */
  	private static boolean isDefaultPartition(IDocument document, int position, String partitioning) {
  		Assert.isTrue(position >= 0);
  		Assert.isTrue(position <= document.getLength());
  
  		try {
  			ITypedRegion region= TextUtilities.getPartition(document, partitioning, position, false);
  			return region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE);
  
  		} catch (BadLocationException e) {
  		}
  
  		return false;
  	}
  
  	private boolean isClosed(IDocument document, int offset, int length) {
  
  		CompilationUnitInfo info= getCompilationUnitForMethod(document, offset);
  		if (info == null)
  			return false;
  
  		JavaScriptUnit compilationUnit= null;
  		try {
  			ASTParser parser= ASTParser.newParser(AST.JLS3);
  			parser.setSource(info.buffer);
  			compilationUnit= (JavaScriptUnit) parser.createAST(null);
  		} catch (ArrayIndexOutOfBoundsException x) {
  			// work around for parser problem
  			return false;
  		}
  
  		IProblem[] problems= compilationUnit.getProblems();
  		for (int i= 0; i != problems.length; ++i) {
  			if (problems[i].getID() == IProblem.UnmatchedBracket)
  				return true;
  		}
  
  		final int relativeOffset= offset - info.delta;
  
  		ASTNode node= NodeFinder.perform(compilationUnit, relativeOffset, length);
  
  		if (length == 0) {
  			while (node != null && (relativeOffset == node.getStartPosition() || relativeOffset == node.getStartPosition() + node.getLength()))
  				node= node.getParent();
  		}
  
  		if (node == null)
  			return false;
  
  		switch (node.getNodeType()) {
  			case ASTNode.BLOCK:
  				return getBlockBalance(document, offset, fPartitioning) <= 0;
  
  			case ASTNode.IF_STATEMENT:
  			{
  				IfStatement ifStatement= (IfStatement) node;
  				Expression expression= ifStatement.getExpression();
  				IRegion expressionRegion= createRegion(expression, info.delta);
  				Statement thenStatement= ifStatement.getThenStatement();
  				IRegion thenRegion= createRegion(thenStatement, info.delta);
  
  				// between expression and then statement
  				if (expressionRegion.getOffset() + expressionRegion.getLength() <= offset && offset + length <= thenRegion.getOffset())
  					return thenStatement != null;
  
  				Statement elseStatement= ifStatement.getElseStatement();
  				IRegion elseRegion= createRegion(elseStatement, info.delta);
  
  				if (elseStatement != null) {
  					int sourceOffset= thenRegion.getOffset() + thenRegion.getLength();
  					int sourceLength= elseRegion.getOffset() - sourceOffset;
  					IRegion elseToken= getToken(document, new Region(sourceOffset, sourceLength), ITerminalSymbols.TokenNameelse);
  					return elseToken != null && elseToken.getOffset() + elseToken.getLength() <= offset && offset + length < elseRegion.getOffset();
  				}
  			}
  			break;
  
  			case ASTNode.WHILE_STATEMENT:
  			case ASTNode.FOR_STATEMENT:
  			{
  				Expression expression= node.getNodeType() == ASTNode.WHILE_STATEMENT ? ((WhileStatement) node).getExpression() : ((ForStatement) node).getExpression();
  				IRegion expressionRegion= createRegion(expression, info.delta);
  				Statement body= node.getNodeType() == ASTNode.WHILE_STATEMENT ? ((WhileStatement) node).getBody() : ((ForStatement) node).getBody();
  				IRegion bodyRegion= createRegion(body, info.delta);
  
  				// between expression and body statement
  				if (expressionRegion.getOffset() + expressionRegion.getLength() <= offset && offset + length <= bodyRegion.getOffset())
  					return body != null;
  			}
  			break;
  
  			case ASTNode.DO_STATEMENT:
  			{
  				DoStatement doStatement= (DoStatement) node;
  				IRegion doRegion= createRegion(doStatement, info.delta);
  				Statement body= doStatement.getBody();
  				IRegion bodyRegion= createRegion(body, info.delta);
  
  				if (doRegion.getOffset() + doRegion.getLength() <= offset && offset + length <= bodyRegion.getOffset())
  					return body != null;
  			}
  			break;
  		}
  
  		return true;
  	}
  
  	/**
  	 * Installs a java partitioner with <code>document</code>.
  	 *
  	 * @param document the document
  	 */
  	private static void installJavaStuff(Document document) {
  		String[] types= new String[] {
  									  IJavaScriptPartitions.JAVA_DOC,
  									  IJavaScriptPartitions.JAVA_MULTI_LINE_COMMENT,
  									  IJavaScriptPartitions.JAVA_SINGLE_LINE_COMMENT,
  									  IJavaScriptPartitions.JAVA_STRING,
  									  IJavaScriptPartitions.JAVA_CHARACTER,
  									  IDocument.DEFAULT_CONTENT_TYPE
  		};
  		FastPartitioner partitioner= new FastPartitioner(new FastJavaPartitionScanner(), types);
  		partitioner.connect(document);
  		document.setDocumentPartitioner(IJavaScriptPartitions.JAVA_PARTITIONING, partitioner);
  	}
  
  	/**
  	 * Installs a java partitioner with <code>document</code>.
  	 *
  	 * @param document the document
  	 */
  	private static void removeJavaStuff(Document document) {
  		document.setDocumentPartitioner(IJavaScriptPartitions.JAVA_PARTITIONING, null);
  	}
  
  	private void smartPaste(IDocument document, DocumentCommand command) {
  		int newOffset= command.offset;
  		int newLength= command.length;
  		String newText= command.text;
  
  		try {
  			JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
  			JavaIndenter indenter= new JavaIndenter(document, scanner, fProject);
  			int offset= newOffset;
  
  			// reference position to get the indent from
  			int refOffset= indenter.findReferencePosition(offset);
  			if (refOffset == JavaHeuristicScanner.NOT_FOUND)
  				return;
  			int peerOffset= getPeerPosition(document, command);
  			peerOffset= indenter.findReferencePosition(peerOffset);
  			refOffset= Math.min(refOffset, peerOffset);
  
  			// eat any WS before the insertion to the beginning of the line
  			int firstLine= 1; // don't format the first line per default, as it has other content before it
  			IRegion line= document.getLineInformationOfOffset(offset);
  			String notSelected= document.get(line.getOffset(), offset - line.getOffset());
  			if (notSelected.trim().length() == 0) {
  				newLength += notSelected.length();
  				newOffset= line.getOffset();
  				firstLine= 0;
  			}
  
  			// prefix: the part we need for formatting but won't paste
  			IRegion refLine= document.getLineInformationOfOffset(refOffset);
  			String prefix= document.get(refLine.getOffset(), newOffset - refLine.getOffset());
  
  			// handle the indentation computation inside a temporary document
  			Document temp= new Document(prefix + newText);
  			DocumentRewriteSession session= temp.startRewriteSession(DocumentRewriteSessionType.STRICTLY_SEQUENTIAL);
  			scanner= new JavaHeuristicScanner(temp);
  			indenter= new JavaIndenter(temp, scanner, fProject);
  			installJavaStuff(temp);
  
  			// indent the first and second line
  			// compute the relative indentation difference from the second line
  			// (as the first might be partially selected) and use the value to
  			// indent all other lines.
  			boolean isIndentDetected= false;
  			StringBuffer addition= new StringBuffer();
  			int insertLength= 0;
  			int first= document.computeNumberOfLines(prefix) + firstLine; // don't format first line
  			int lines= temp.getNumberOfLines();
  			int tabLength= getVisualTabLengthPreference();
  			boolean changed= false;
  			for (int l= first; l < lines; l++) { // we don't change the number of lines while adding indents
  
  				IRegion r= temp.getLineInformation(l);
  				int lineOffset= r.getOffset();
  				int lineLength= r.getLength();
  
  				if (lineLength == 0) // don't modify empty lines
  					continue;
  
  				if (!isIndentDetected) {
  
  					// indent the first pasted line
  					String current= getCurrentIndent(temp, l);
  					StringBuffer correct= indenter.computeIndentation(lineOffset);
  					if (correct == null)
  						return; // bail out
  
  					insertLength= subtractIndent(correct, current, addition, tabLength);
  					if (l != first && temp.get(lineOffset, lineLength).trim().length() != 0) {
  						isIndentDetected= true;
  						if (insertLength == 0) {
  							 // no adjustment needed, bail out
  							if (firstLine == 0) {
  								// but we still need to adjust the first line
  								command.offset= newOffset;
  								command.length= newLength;
  								if (changed)
  									break; // still need to get the leading indent of the first line
  							}
  							return;
  						}
  						removeJavaStuff(temp);
  					} else {
  						changed= insertLength != 0;
  					}
  				}
  
  				// relatively indent all pasted lines
  				if (insertLength > 0)
  					addIndent(temp, l, addition, tabLength);
  				else if (insertLength < 0)
  					cutIndent(temp, l, -insertLength, tabLength);
  
  			}
  
  			temp.stopRewriteSession(session);
  			newText= temp.get(prefix.length(), temp.getLength() - prefix.length());
  
  			command.offset= newOffset;
  			command.length= newLength;
  			command.text= newText;
  
  		} catch (BadLocationException e) {
  			JavaScriptPlugin.log(e);
  		}
  
  	}
  
  	/**
  	 * Returns the indentation of the line <code>line</code> in <code>document</code>.
  	 * The returned string may contain pairs of leading slashes that are considered
  	 * part of the indentation. The space before the asterisk in a javadoc-like
  	 * comment is not considered part of the indentation.
  	 *
  	 * @param document the document
  	 * @param line the line
  	 * @return the indentation of <code>line</code> in <code>document</code>
  	 * @throws BadLocationException if the document is changed concurrently
  	 */
  	private static String getCurrentIndent(Document document, int line) throws BadLocationException {
  		IRegion region= document.getLineInformation(line);
  		int from= region.getOffset();
  		int endOffset= region.getOffset() + region.getLength();
  
  		// go behind line comments
  		int to= from;
  		while (to < endOffset - 2 && document.get(to, 2).equals(LINE_COMMENT))
  			to += 2;
  
  		while (to < endOffset) {
  			char ch= document.getChar(to);
  			if (!Character.isWhitespace(ch))
  				break;
  			to++;
  		}
  
  		// don't count the space before javadoc like, asterisk-style comment lines
  		if (to > from && to < endOffset - 1 && document.get(to - 1, 2).equals(" *")) { //$NON-NLS-1$
  			String type= TextUtilities.getContentType(document, IJavaScriptPartitions.JAVA_PARTITIONING, to, true);
  			if (type.equals(IJavaScriptPartitions.JAVA_DOC) || type.equals(IJavaScriptPartitions.JAVA_MULTI_LINE_COMMENT))
  				to--;
  		}
  
  		return document.get(from, to - from);
  	}
  
  	/**
  	 * Computes the difference of two indentations and returns the difference in
  	 * length of current and correct. If the return value is positive, <code>addition</code>
  	 * is initialized with a substring of that length of <code>correct</code>.
  	 *
  	 * @param correct the correct indentation
  	 * @param current the current indentation (might contain non-whitespace)
  	 * @param difference a string buffer - if the return value is positive, it will be cleared and set to the substring of <code>current</code> of that length
  	 * @param tabLength the length of a tab
  	 * @return the difference in length of <code>correct</code> and <code>current</code>
  	 */
  	private int subtractIndent(CharSequence correct, CharSequence current, StringBuffer difference, int tabLength) {
  		int c1= computeVisualLength(correct, tabLength);
  		int c2= computeVisualLength(current, tabLength);
  		int diff= c1 - c2;
  		if (diff <= 0)
  			return diff;
  
  		difference.setLength(0);
  		int len= 0, i= 0;
  		while (len < diff) {
  			char c= correct.charAt(i++);
  			difference.append(c);
  			len += computeVisualLength(c, tabLength);
  		}
  
  
  		return diff;
  	}
  
  	/**
  	 * Indents line <code>line</code> in <code>document</code> with <code>indent</code>.
  	 * Leaves leading comment signs alone.
  	 *
  	 * @param document the document
  	 * @param line the line
  	 * @param indent the indentation to insert
  	 * @param tabLength the length of a tab
  	 * @throws BadLocationException on concurrent document modification
  	 */
  	private void addIndent(Document document, int line, CharSequence indent, int tabLength) throws BadLocationException {
  		IRegion region= document.getLineInformation(line);
  		int insert= region.getOffset();
  		int endOffset= region.getOffset() + region.getLength();
  
  		// Compute insert after all leading line comment markers
  		int newInsert= insert;
  		while (newInsert < endOffset - 2 && document.get(newInsert, 2).equals(LINE_COMMENT))
  			newInsert += 2;
  
  		// Heuristic to check whether it is commented code or just a comment
  		if (newInsert > insert) {
  			int whitespaceCount= 0;
  			int i= newInsert;
  			while (i < endOffset - 1) {
  				 char ch= document.get(i, 1).charAt(0);
  				 if (!Character.isWhitespace(ch))
  					 break;
  				 whitespaceCount= whitespaceCount + computeVisualLength(ch, tabLength);
  				 i++;
  			}
  
  			if (whitespaceCount != 0 && whitespaceCount >= CodeFormatterUtil.getIndentWidth(fProject))
  				insert= newInsert;
  		}
  
  		// Insert indent
  		document.replace(insert, 0, indent.toString());
  	}
  
  	/**
  	 * Cuts the visual equivalent of <code>toDelete</code> characters out of the
  	 * indentation of line <code>line</code> in <code>document</code>. Leaves
  	 * leading comment signs alone.
  	 *
  	 * @param document the document
  	 * @param line the line
  	 * @param toDelete the number of space equivalents to delete
  	 * @param tabLength the length of a tab
  	 * @throws BadLocationException on concurrent document modification
  	 */
  	private void cutIndent(Document document, int line, int toDelete, int tabLength) throws BadLocationException {
  		IRegion region= document.getLineInformation(line);
  		int from= region.getOffset();
  		int endOffset= region.getOffset() + region.getLength();
  
  		// go behind line comments
  		while (from < endOffset - 2 && document.get(from, 2).equals(LINE_COMMENT))
  			from += 2;
  
  		int to= from;
  		while (toDelete > 0 && to < endOffset) {
  			char ch= document.getChar(to);
  			if (!Character.isWhitespace(ch))
  				break;
  			toDelete -= computeVisualLength(ch, tabLength);
  			if (toDelete >= 0)
  				to++;
  			else
  				break;
  		}
  
  		document.replace(from, to - from, ""); //$NON-NLS-1$
  	}
  
  	/**
  	 * Returns the visual length of a given <code>CharSequence</code> taking into
  	 * account the visual tabulator length.
  	 *
  	 * @param seq the string to measure
  	 * @param tabLength the length of a tab
  	 * @return the visual length of <code>seq</code>
  	 */
  	private int computeVisualLength(CharSequence seq, int tabLength) {
  		int size= 0;
  
  		for (int i= 0; i < seq.length(); i++) {
  			char ch= seq.charAt(i);
  			if (ch == '\t') {
  				if (tabLength != 0)
  					size += tabLength - size % tabLength;
  				// else: size stays the same
  			} else {
  				size++;
  			}
  		}
  		return size;
  	}
  
  	/**
  	 * Returns the visual length of a given character taking into
  	 * account the visual tabulator length.
  	 *
  	 * @param ch the character to measure
  	 * @param tabLength the length of a tab
  	 * @return the visual length of <code>ch</code>
  	 */
  	private int computeVisualLength(char ch, int tabLength) {
  		if (ch == '\t')
  			return tabLength;
  		else
  			return 1;
  	}
  
  	/**
  	 * The preference setting for the visual tabulator display.
  	 *
  	 * @return the number of spaces displayed for a tabulator in the editor
  	 */
  	private int getVisualTabLengthPreference() {
  		return CodeFormatterUtil.getTabWidth(fProject);
  	}
  
  	/**
  	 * The preference setting that tells whether to insert spaces when pressing the Tab key.
  	 *
  	 * @return <code>true</code> if spaces are inserted when pressing the Tab key
  	 * @since 3.5
  	 */
  	private boolean isInsertingSpacesForTab() {
  		return JavaScriptCore.SPACE.equals(getCoreOption(fProject, DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR));
  	}
  
  	/**
  	 * Returns the possibly <code>project</code>-specific core preference defined under
  	 * <code>key</code>.
  	 *
  	 * @param project the project to get the preference from, or <code>null</code> to get the global
  	 *            preference
  	 * @param key the key of the preference
  	 * @return the value of the preference
  	 * @since 3.5
  	 */
  	private static String getCoreOption(IJavaScriptProject project, String key) {
  		if (project == null)
  			return JavaScriptCore.getOption(key);
  		return project.getOption(key, true);
  	}
  
  	private int getPeerPosition(IDocument document, DocumentCommand command) {
  		if (document.getLength() == 0)
  			return 0;
      	/*
      	 * Search for scope closers in the pasted text and find their opening peers
      	 * in the document.
      	 */
      	Document pasted= new Document(command.text);
      	installJavaStuff(pasted);
      	int firstPeer= command.offset;
  
      	JavaHeuristicScanner pScanner= new JavaHeuristicScanner(pasted);
      	JavaHeuristicScanner dScanner= new JavaHeuristicScanner(document);
  
      	// add scope relevant after context to peer search
      	int afterToken= dScanner.nextToken(command.offset + command.length, JavaHeuristicScanner.UNBOUND);
      	try {
  			switch (afterToken) {
  			case Symbols.TokenRBRACE:
  				pasted.replace(pasted.getLength(), 0, "}"); //$NON-NLS-1$
  				break;
  			case Symbols.TokenRPAREN:
  				pasted.replace(pasted.getLength(), 0, ")"); //$NON-NLS-1$
  				break;
  			case Symbols.TokenRBRACKET:
  				pasted.replace(pasted.getLength(), 0, "]"); //$NON-NLS-1$
  				break;
  			}
  		} catch (BadLocationException e) {
  			// cannot happen
  			Assert.isTrue(false);
  		}
  
      	int pPos= 0; // paste text position (increasing from 0)
      	int dPos= Math.max(0, command.offset - 1); // document position (decreasing from paste offset)
      	while (true) {
      		int token= pScanner.nextToken(pPos, JavaHeuristicScanner.UNBOUND);
     			pPos= pScanner.getPosition();
      		switch (token) {
      			case Symbols.TokenLBRACE:
      			case Symbols.TokenLBRACKET:
      			case Symbols.TokenLPAREN:
      				pPos= skipScope(pScanner, pPos, token);
      				if (pPos == JavaHeuristicScanner.NOT_FOUND)
      					return firstPeer;
      				break; // closed scope -> keep searching
      			case Symbols.TokenRBRACE:
      				int peer= dScanner.findOpeningPeer(dPos, '{', '}');
      				dPos= peer - 1;
      				if (peer == JavaHeuristicScanner.NOT_FOUND)
      					return firstPeer;
      				firstPeer= peer;
      				break; // keep searching
      			case Symbols.TokenRBRACKET:
      				peer= dScanner.findOpeningPeer(dPos, '[', ']');
      				dPos= peer - 1;
      				if (peer == JavaHeuristicScanner.NOT_FOUND)
      					return firstPeer;
      				firstPeer= peer;
      				break; // keep searching
      			case Symbols.TokenRPAREN:
      				peer= dScanner.findOpeningPeer(dPos, '(', ')');
      				dPos= peer - 1;
      				if (peer == JavaHeuristicScanner.NOT_FOUND)
      					return firstPeer;
      				firstPeer= peer;
      				break; // keep searching
      			case Symbols.TokenCASE:
      			case Symbols.TokenDEFAULT:
      				JavaIndenter indenter= new JavaIndenter(document, dScanner, fProject);
      				peer= indenter.findReferencePosition(dPos, false, false, false, true);
      				if (peer == JavaHeuristicScanner.NOT_FOUND)
      					return firstPeer;
      				firstPeer= peer;
      				break; // keep searching
  
      			case Symbols.TokenEOF:
      				return firstPeer;
      			default:
      				// keep searching
      		}
      	}
      }
  
      /**
       * Skips the scope opened by <code>token</code>.
       *
       * @param scanner the scanner
       * @param start the start position
       * @param token the token
       * @return the position after the scope or <code>JavaHeuristicScanner.NOT_FOUND</code>
       */
      private static int skipScope(JavaHeuristicScanner scanner, int start, int token) {
      	int openToken= token;
      	int closeToken;
      	switch (token) {
      		case Symbols.TokenLPAREN:
      			closeToken= Symbols.TokenRPAREN;
      			break;
      		case Symbols.TokenLBRACKET:
      			closeToken= Symbols.TokenRBRACKET;
      			break;
      		case Symbols.TokenLBRACE:
      			closeToken= Symbols.TokenRBRACE;
      			break;
      		default:
      			Assert.isTrue(false);
      			return -1; // dummy
      	}
  
      	int depth= 1;
      	int p= start;
  
      	while (true) {
      		int tok= scanner.nextToken(p, JavaHeuristicScanner.UNBOUND);
      		p= scanner.getPosition();
  
      		if (tok == openToken) {
      			depth++;
      		} else if (tok == closeToken) {
      			depth--;
      			if (depth == 0)
      				return p + 1;
      		} else if (tok == Symbols.TokenEOF) {
      			return JavaHeuristicScanner.NOT_FOUND;
      		}
      	}
      }
  
      private boolean isLineDelimiter(IDocument document, String text) {
  		String[] delimiters= document.getLegalLineDelimiters();
  		if (delimiters != null)
  			return TextUtilities.equals(delimiters, text) > -1;
  		return false;
  	}
  
  	private void smartIndentOnKeypress(IDocument document, DocumentCommand command) {
  		switch (command.text.charAt(0)) {
  			case '}':
  				smartIndentAfterClosingBracket(document, command);
  				break;
  			case '{':
  				smartIndentAfterOpeningBracket(document, command);
  				break;
  			case 'e':
  				smartIndentUponE(document, command);
  				break;
  		}
  	}
  
  	private void smartIndentUponE(IDocument d, DocumentCommand c) {
  		if (c.offset < 4 || d.getLength() == 0)
  			return;
  
  		try {
  			String content= d.get(c.offset - 3, 3);
  			if (content.equals("els")) { //$NON-NLS-1$
  				JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
  				int p= c.offset - 3;
  
  				// current line
  				int line= d.getLineOfOffset(p);
  				int lineOffset= d.getLineOffset(line);
  
  				// make sure we don't have any leading comments etc.
  				if (d.get(lineOffset, p - lineOffset).trim().length() != 0)
  					return;
  
  				// line of last Java code
  				int pos= scanner.findNonWhitespaceBackward(p - 1, JavaHeuristicScanner.UNBOUND);
  				if (pos == -1)
  					return;
  				int lastLine= d.getLineOfOffset(pos);
  
  				// only shift if the last java line is further up and is a braceless block candidate
  				if (lastLine < line) {
  
  					JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
  					int ref= indenter.findReferencePosition(p, true, false, false, false);
  					if (ref == JavaHeuristicScanner.NOT_FOUND)
  						return;
  					int refLine= d.getLineOfOffset(ref);
  					String indent= getIndentOfLine(d, refLine);
  
  					if (indent != null) {
  						c.text= indent.toString() + "else"; //$NON-NLS-1$
  						c.length += c.offset - lineOffset;
  						c.offset= lineOffset;
  					}
  				}
  
  				return;
  			}
  
  			if (content.equals("cas")) { //$NON-NLS-1$
  				JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
  				int p= c.offset - 3;
  
  				// current line
  				int line= d.getLineOfOffset(p);
  				int lineOffset= d.getLineOffset(line);
  
  				// make sure we don't have any leading comments etc.
  				if (d.get(lineOffset, p - lineOffset).trim().length() != 0)
  					return;
  
  				// line of last Java code
  				int pos= scanner.findNonWhitespaceBackward(p - 1, JavaHeuristicScanner.UNBOUND);
  				if (pos == -1)
  					return;
  				int lastLine= d.getLineOfOffset(pos);
  
  				// only shift if the last java line is further up and is a braceless block candidate
  				if (lastLine < line) {
  
  					JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
  					int ref= indenter.findReferencePosition(p, false, false, false, true);
  					if (ref == JavaHeuristicScanner.NOT_FOUND)
  						return;
  					int refLine= d.getLineOfOffset(ref);
  					int nextToken= scanner.nextToken(ref, JavaHeuristicScanner.UNBOUND);
  					String indent;
  					if (nextToken == Symbols.TokenCASE || nextToken == Symbols.TokenDEFAULT)
  						indent= getIndentOfLine(d, refLine);
  					else // at the brace of the switch
  						indent= indenter.computeIndentation(p).toString();
  
  					if (indent != null) {
  						c.text= indent.toString() + "case"; //$NON-NLS-1$
  						c.length += c.offset - lineOffset;
  						c.offset= lineOffset;
  					}
  				}
  
  				return;
  			}
  
  		} catch (BadLocationException e) {
  			JavaScriptPlugin.log(e);
  		}
  	}
  
  	/*
  	 * @see org.eclipse.jface.text.IAutoIndentStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
  	 */
  	public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
  		try {
  			if (c.doit == false)
  			return;
  
  			clearCachedValues();
  	
  			if (!fIsSmartMode) {
  				super.customizeDocumentCommand(d, c);
  				return;
  			}
  	
  			if (!fIsSmartTab && isRepresentingTab(c.text))
  				return;
  	
  			if (c.length == 0 && c.text != null && isLineDelimiter(d, c.text))
  				smartIndentAfterNewLine(d, c);
  			else if (c.text.length() == 1)
  				smartIndentOnKeypress(d, c);
  			else if (c.text.length() > 1 && getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SMART_PASTE))
  				if (fViewer == null || fViewer.getTextWidget() == null || !fViewer.getTextWidget().getBlockSelection())
  					smartPaste(d, c); // no smart backspace for paste
  		} catch(IllegalArgumentException e) {
  			// ignore
  		}
  
  	}
  
  	/**
  	 * Tells whether the given inserted string represents hitting the Tab key.
  	 *
  	 * @param text the text to check
  	 * @return <code>true</code> if the text represents hitting the Tab key
  	 * @since 3.5
  	 */
  	private boolean isRepresentingTab(String text) {
  		if (text == null)
  			return false;
  
  		if (isInsertingSpacesForTab()) {
  			if (text.length() == 0 || text.length() > getVisualTabLengthPreference())
  				return false;
  			for (int i= 0; i < text.length(); i++) {
  				if (text.charAt(i) != ' ')
  					return false;
  			}
  			return true;
  		} else
  			return text.length() == 1 && text.charAt(0) == '\t';
  	}
  
  	private static IPreferenceStore getPreferenceStore() {
  		return JavaScriptPlugin.getDefault().getCombinedPreferenceStore();
  	}
  
  	private boolean closeBrace() {
  		return fCloseBrace;
  	}
  
  	private void clearCachedValues() {
          IPreferenceStore preferenceStore= getPreferenceStore();
  		fCloseBrace= preferenceStore.getBoolean(PreferenceConstants.EDITOR_CLOSE_BRACES);
  		fIsSmartTab= preferenceStore.getBoolean(PreferenceConstants.EDITOR_SMART_TAB);
  		fIsSmartMode= computeSmartMode();
  	}
  
  	private boolean computeSmartMode() {
  		IWorkbenchPage page= JavaScriptPlugin.getActivePage();
  		if (page != null)  {
  			IEditorPart part= page.getActiveEditor();
  			if (part instanceof ITextEditorExtension3) {
  				ITextEditorExtension3 extension= (ITextEditorExtension3) part;
  				return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT;
  			}
  		}
  		return false;
  	}
  
  	private static CompilationUnitInfo getCompilationUnitForMethod(IDocument document, int offset) {
  		try {
  			JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
  
  			IRegion sourceRange= scanner.findSurroundingBlock(offset);
  			if (sourceRange == null)
  				return null;
  			String source= document.get(sourceRange.getOffset(), sourceRange.getLength());
  
  			StringBuffer contents= new StringBuffer();
  			contents.append("function ____f() {"); //$NON-NLS-1$
  			final int methodOffset= contents.length();
  			contents.append(source);
  			contents.append('}');
  
  			char[] buffer= contents.toString().toCharArray();
  
  			return new CompilationUnitInfo(buffer, sourceRange.getOffset() - methodOffset);
  
  		} catch (BadLocationException e) {
  			JavaScriptPlugin.log(e);
  		}
  
  		return null;
  	}
  
  	/**
  	 * Returns the block balance, i.e. zero if the blocks are balanced at <code>offset</code>, a
  	 * negative number if there are more closing than opening braces, and a positive number if there
  	 * are more opening than closing braces.
  	 * 
  	 * @param document the document
  	 * @param offset the offset
  	 * @param partitioning the partitioning
  	 * @return the block balance
  	 */
  	private static int getBlockBalance(IDocument document, int offset, String partitioning) {
  		if (offset < 1)
  			return -1;
  		if (offset >= document.getLength())
  			return 1;
  
  		int begin= offset;
  		int end= offset - 1;
  
  		JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
  
  		while (true) {
  			begin= scanner.findOpeningPeer(begin - 1, '{', '}');
  			end= scanner.findClosingPeer(end + 1, '{', '}');
  			if (begin == -1 && end == -1)
  				return 0;
  			if (begin == -1)
  				return -1;
  			if (end == -1)
  				return 1;
  		}
  	}
  
  	private static IRegion createRegion(ASTNode node, int delta) {
  		return node == null ? null : new Region(node.getStartPosition() + delta, node.getLength());
  	}
  
  	private static IRegion getToken(IDocument document, IRegion scanRegion, int tokenId)  {
  
  		try {
  
  			final String source= document.get(scanRegion.getOffset(), scanRegion.getLength());
  
  			fgScanner.setSource(source.toCharArray());
  
  			int id= fgScanner.getNextToken();
  			while (id != ITerminalSymbols.TokenNameEOF && id != tokenId)
  				id= fgScanner.getNextToken();
  
  			if (id == ITerminalSymbols.TokenNameEOF)
  				return null;
  
  			int tokenOffset= fgScanner.getCurrentTokenStartPosition();
  			int tokenLength= fgScanner.getCurrentTokenEndPosition() + 1 - tokenOffset; // inclusive end
  			return new Region(tokenOffset + scanRegion.getOffset(), tokenLength);
  
  		} catch (InvalidInputException x) {
  			return null;
  		} catch (BadLocationException x) {
  			return null;
  		}
  	}
  }
  