| /******************************************************************************* |
| * Copyright (c) 2000, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * 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.jdt.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.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.ToolFactory; |
| import org.eclipse.jdt.core.compiler.IProblem; |
| import org.eclipse.jdt.core.compiler.IScanner; |
| import org.eclipse.jdt.core.compiler.ITerminalSymbols; |
| import org.eclipse.jdt.core.compiler.InvalidInputException; |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.ASTParser; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| import org.eclipse.jdt.core.dom.DoStatement; |
| import org.eclipse.jdt.core.dom.Expression; |
| import org.eclipse.jdt.core.dom.ForStatement; |
| import org.eclipse.jdt.core.dom.IfStatement; |
| import org.eclipse.jdt.core.dom.NodeFinder; |
| import org.eclipse.jdt.core.dom.Statement; |
| import org.eclipse.jdt.core.dom.WhileStatement; |
| import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; |
| |
| import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil; |
| |
| import org.eclipse.jdt.ui.PreferenceConstants; |
| import org.eclipse.jdt.ui.text.IJavaPartitions; |
| |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| import org.eclipse.jdt.internal.corext.dom.IASTSharedValues; |
| import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; |
| import org.eclipse.jdt.internal.ui.text.FastJavaPartitionScanner; |
| import org.eclipse.jdt.internal.ui.text.FastJavaPartitioner; |
| import org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner; |
| import org.eclipse.jdt.internal.ui.text.JavaIndenter; |
| import org.eclipse.jdt.internal.ui.text.Symbols; |
| |
| |
| /** |
| * 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 boolean fIsSmartIndentAfterNewline; |
| |
| private String fPartitioning; |
| private final IJavaProject 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, IJavaProject 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 |
| StringBuilder replaceText= new StringBuilder(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) { |
| JavaPlugin.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) { |
| JavaPlugin.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); |
| |
| StringBuilder buf= new StringBuilder(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 (IJavaPartitions.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; |
| |
| int pos= c.offset - 1; |
| char ch= d.getChar(pos); |
| while (ch == ' ' || ch == '\t') { |
| pos--; |
| ch= d.getChar(pos); |
| } |
| // 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 || copyContent(d, pos + 1, fPartitioning, lineEnd)) { |
| 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) { |
| JavaPlugin.log(e); |
| } |
| } |
| |
| /** |
| * Checks if it is required to copy the old content of the line after caret position to new line |
| * before inserting the closing brace. |
| * |
| * @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 <code>true</code> if the old content of the line after caret position has to be |
| * copied to new line before inserting the closing brace, <code>false</code> otherwise |
| */ |
| private boolean copyContent(IDocument document, int offset, String partitioning, int max) { |
| // find the opening parenthesis for every closing parenthesis on the current line after offset |
| // return true 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; |
| int openingParen= -1; |
| while (true) { |
| int startScan= closingParen + 1; |
| closingParen= scanner.scanForward(startScan, scanTo, ')'); |
| if (closingParen == -1) { |
| if (openingParen != -1) |
| return false; |
| try { |
| int p= findEndOfWhiteSpace(document, pos, scanTo); |
| char ch= document.getChar(p); |
| if (ch == ',' || ch == ';') |
| return false; |
| } catch (BadLocationException e) { |
| // ignore |
| } |
| 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) { |
| openingParen= -1; |
| continue; |
| } |
| |
| if (looksLikeAnonymousClassDef(document, partitioning, scanner, openingParen - 1)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * 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; |
| |
| CompilationUnit compilationUnit= null; |
| try { |
| ASTParser parser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL); |
| parser.setSource(info.buffer); |
| compilationUnit= (CompilationUnit) 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 void installJavaStuff(Document document) { |
| String[] types= new String[] { |
| IJavaPartitions.JAVA_DOC, |
| IJavaPartitions.JAVA_MULTI_LINE_COMMENT, |
| IJavaPartitions.JAVA_SINGLE_LINE_COMMENT, |
| IJavaPartitions.JAVA_STRING, |
| IJavaPartitions.JAVA_CHARACTER, |
| IJavaPartitions.JAVA_MULTI_LINE_STRING, |
| IDocument.DEFAULT_CONTENT_TYPE |
| }; |
| FastPartitioner partitioner= new FastJavaPartitioner(new FastJavaPartitionScanner(fProject), types); |
| partitioner.connect(document); |
| document.setDocumentPartitioner(IJavaPartitions.JAVA_PARTITIONING, partitioner); |
| } |
| |
| /** |
| * Installs a java partitioner with <code>document</code>. |
| * |
| * @param document the document |
| */ |
| private static void removeJavaStuff(Document document) { |
| document.setDocumentPartitioner(IJavaPartitions.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); |
| if (peerOffset != JavaHeuristicScanner.NOT_FOUND) |
| 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 firstLineInsertLength= 0; |
| int firstLineIndent= 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) { |
| firstLineInsertLength= insertLength; |
| firstLineIndent= current.length(); |
| } |
| if (l != first && temp.get(lineOffset, lineLength).trim().length() != 0) { |
| isIndentDetected= true; |
| if (firstLineIndent >= current.length()) |
| insertLength= firstLineInsertLength; |
| 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; |
| } |
| } 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); |
| |
| } |
| |
| removeJavaStuff(temp); |
| temp.stopRewriteSession(session); |
| newText= temp.get(prefix.length(), temp.getLength() - prefix.length()); |
| |
| command.offset= newOffset; |
| command.length= newLength; |
| command.text= newText; |
| |
| } catch (BadLocationException e) { |
| JavaPlugin.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, IJavaPartitions.JAVA_PARTITIONING, to, true); |
| if (type.equals(IJavaPartitions.JAVA_DOC) || type.equals(IJavaPartitions.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 JavaCore.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(IJavaProject project, String key) { |
| if (project == null) |
| return JavaCore.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) { |
| JavaPlugin.log(e); |
| } |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.IAutoIndentStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand) |
| */ |
| @Override |
| public void customizeDocumentCommand(IDocument d, DocumentCommand c) { |
| 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)) { |
| if (fIsSmartIndentAfterNewline) |
| smartIndentAfterNewLine(d, c); |
| else |
| super.customizeDocumentCommand(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 |
| |
| } |
| |
| /** |
| * 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 JavaPlugin.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); |
| fIsSmartIndentAfterNewline= preferenceStore.getBoolean(PreferenceConstants.EDITOR_SMART_INDENT_AFTER_NEWLINE); |
| fIsSmartMode= computeSmartMode(); |
| } |
| |
| private boolean computeSmartMode() { |
| IWorkbenchPage page= JavaPlugin.getActivePage(); |
| if (page != null) { |
| IEditorPart part= page.getActiveEditor(); |
| if (part instanceof ITextEditorExtension3) { |
| ITextEditorExtension3 extension= (ITextEditorExtension3) part; |
| return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT; |
| } else if (part != null && EditorUtility.isCompareEditorInput(part.getEditorInput())) { |
| ITextEditorExtension3 extension = part.getAdapter(ITextEditorExtension3.class); |
| if (extension != null) |
| 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()); |
| |
| StringBuilder contents= new StringBuilder(); |
| contents.append("class ____C{void ____m()"); //$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) { |
| JavaPlugin.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 | BadLocationException x) { |
| return null; |
| } |
| } |
| } |