| /******************************************************************************* |
| * Copyright (c) 2000, 2012 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 |
| * QNX Software System |
| * Anton Leherbauer (Wind River Systems) |
| * Sergey Prigogin (Google) |
| * Andrew Ferguson (Symbian) |
| * Andrew Gvozdev |
| *******************************************************************************/ |
| package org.eclipse.cdt.internal.ui.text; |
| |
| 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.TextUtilities; |
| import org.eclipse.jface.text.rules.FastPartitioner; |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.part.MultiPageEditorPart; |
| import org.eclipse.ui.texteditor.ITextEditorExtension3; |
| |
| import org.eclipse.cdt.core.model.ICProject; |
| import org.eclipse.cdt.ui.CUIPlugin; |
| import org.eclipse.cdt.ui.PreferenceConstants; |
| import org.eclipse.cdt.ui.text.ICPartitions; |
| |
| import org.eclipse.cdt.internal.corext.util.CodeFormatterUtil; |
| |
| import org.eclipse.cdt.internal.ui.editor.IndentUtil; |
| import org.eclipse.cdt.internal.ui.text.CIndenter.MatchMode; |
| |
| /** |
| * Auto indent strategy sensitive to brackets. |
| */ |
| public class CAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy { |
| /** The line comment introducer. Value is "{@value}" */ |
| private static final String LINE_COMMENT= "//"; //$NON-NLS-1$ |
| // private static final GCCScannerExtensionConfiguration C_GNU_SCANNER_EXTENSION = new GCCScannerExtensionConfiguration(); |
| |
| // 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 String fPartitioning; |
| private final ICProject fProject; |
| |
| /* |
| * boolean to skip "Smart Insert" mode check because Generic Editor doesn't have a "Smart Insert" mode. |
| */ |
| private boolean alwaysUseSmartMode = false; |
| |
| /** |
| * Creates a new C 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 |
| */ |
| public CAutoIndentStrategy(String partitioning, ICProject project) { |
| fPartitioning = partitioning; |
| fProject = project; |
| } |
| |
| public CAutoIndentStrategy(String partitioning, ICProject project, boolean alwaysUseSmartMode) { |
| this(partitioning, project); |
| this.alwaysUseSmartMode = alwaysUseSmartMode; |
| } |
| |
| private int getBracketCount(IDocument d, int start, int end, boolean ignoreCloseBrackets) throws BadLocationException { |
| int bracketcount = 0; |
| while (start < end) { |
| char curr = d.getChar(start); |
| start++; |
| switch (curr) { |
| case '/' : |
| if (start < end) { |
| char next = d.getChar(start); |
| if (next == '*') { |
| // a comment starts, advance to the comment end |
| start = getCommentEnd(d, start + 1, end); |
| } else if (next == '/') { |
| // '//'-comment: nothing to do anymore on this line |
| start = end; |
| } |
| } |
| break; |
| case '*' : |
| if (start < end) { |
| char next = d.getChar(start); |
| if (next == '/') { |
| // we have been in a comment: forget what we read before |
| bracketcount = 0; |
| start++; |
| } |
| } |
| break; |
| case '{' : |
| bracketcount++; |
| ignoreCloseBrackets = false; |
| break; |
| case '}' : |
| if (!ignoreCloseBrackets) { |
| bracketcount--; |
| } |
| break; |
| case '"' : |
| case '\'' : |
| start = getStringEnd(d, start, end, curr); |
| break; |
| default : |
| } |
| } |
| return bracketcount; |
| } |
| |
| // ----------- bracket counting ------------------------------------------------------ |
| |
| private int getCommentEnd(IDocument d, int pos, int end) throws BadLocationException { |
| while (pos < end) { |
| char curr = d.getChar(pos); |
| pos++; |
| if (curr == '*') { |
| if (pos < end && d.getChar(pos) == '/') { |
| return pos + 1; |
| } |
| } |
| } |
| return end; |
| } |
| |
| 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); |
| } |
| return ""; //$NON-NLS-1$ |
| } |
| |
| private int getStringEnd(IDocument d, int pos, int end, char ch) throws BadLocationException { |
| while (pos < end) { |
| char curr = d.getChar(pos); |
| pos++; |
| if (curr == '\\') { |
| // ignore escaped characters |
| pos++; |
| } else if (curr == ch) { |
| return pos; |
| } |
| } |
| return end; |
| } |
| |
| 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); |
| |
| CHeuristicScanner scanner= new CHeuristicScanner(d); |
| ITypedRegion partition= TextUtilities.getPartition(d, fPartitioning, p, false); |
| if (ICPartitions.C_PREPROCESSOR.equals(partition.getType())) { |
| scanner = new CHeuristicScanner(d, fPartitioning, ICPartitions.C_PREPROCESSOR); |
| } |
| CIndenter indenter = new CIndenter(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, MatchMode.MATCH_BRACE); |
| 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) { |
| CUIPlugin.log(e); |
| } |
| } |
| |
| private void smartIndentAfterOpeningBracket(IDocument d, DocumentCommand c) { |
| if (c.offset < 1 || d.getLength() == 0) |
| return; |
| |
| int p = (c.offset == d.getLength() ? c.offset - 1 : c.offset); |
| |
| try { |
| CHeuristicScanner scanner= new CHeuristicScanner(d); |
| ITypedRegion partition= TextUtilities.getPartition(d, fPartitioning, p, false); |
| if (ICPartitions.C_PREPROCESSOR.equals(partition.getType())) { |
| scanner = new CHeuristicScanner(d, fPartitioning, ICPartitions.C_PREPROCESSOR); |
| } |
| // current line |
| int line = d.getLineOfOffset(c.offset); |
| int lineOffset = d.getLineOffset(line); |
| |
| // make sure we don't have any leading comments etc. |
| if (!d.get(lineOffset, c.offset - lineOffset).trim().isEmpty()) |
| return; |
| |
| // Line of last C code |
| int pos = scanner.findNonWhitespaceBackward(p, CHeuristicScanner.UNBOUND); |
| if (pos == -1) |
| return; |
| int lastLine = d.getLineOfOffset(pos); |
| |
| // Only shift if the last C line is further up and is a braceless block candidate |
| if (lastLine < line) { |
| CIndenter indenter = new CIndenter(d, scanner, fProject); |
| StringBuilder 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) { |
| CUIPlugin.log(e); |
| } |
| } |
| |
| private void smartIndentAfterNewLine(IDocument d, DocumentCommand c) { |
| int docLength = d.getLength(); |
| if (c.offset == -1 || docLength == 0) |
| return; |
| |
| int addIndent= 0; |
| CHeuristicScanner scanner= new CHeuristicScanner(d); |
| try { |
| ITypedRegion partition= TextUtilities.getPartition(d, fPartitioning, c.offset, false); |
| if (ICPartitions.C_PREPROCESSOR.equals(partition.getType()) && c.offset > 0 && d.getChar(c.offset-1) == '\\') { |
| scanner = new CHeuristicScanner(d, fPartitioning, ICPartitions.C_PREPROCESSOR); |
| addIndent= 1; |
| } |
| |
| int line = d.getLineOfOffset(c.offset); |
| IRegion reg = d.getLineInformation(line); |
| int start = reg.getOffset(); |
| int lineEnd = start + reg.getLength(); |
| |
| StringBuilder indent= null; |
| CIndenter indenter= new CIndenter(d, scanner, fProject); |
| if (getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_AUTO_INDENT)) { |
| indent= indenter.computeIndentation(c.offset); |
| } else { |
| // reuse existing indent |
| int wsEnd= findEndOfWhiteSpace(d, start, c.offset); |
| if (wsEnd > start) { |
| indent= new StringBuilder(d.get(start, wsEnd - start)); |
| addIndent= 0; |
| } |
| } |
| if (indent == null) { |
| indent= new StringBuilder(); |
| } |
| if (addIndent > 0 && indent.length() == 0) { |
| indent= indenter.createReusingIndent(indent, addIndent, 0); |
| } |
| |
| StringBuilder buf = new StringBuilder(c.text + indent); |
| int contentStart = findEndOfWhiteSpace(d, c.offset, lineEnd); |
| c.length = Math.max(contentStart - c.offset, 0); |
| |
| // insert closing brace on new line after an unclosed opening brace |
| if (getBracketCount(d, start, c.offset, true) > 0 && fCloseBrace && !isClosedBrace(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)); |
| StringBuilder reference = null; |
| int nonWS = findEndOfWhiteSpace(d, start, lineEnd); |
| if (nonWS < c.offset && d.getChar(nonWS) == '{') |
| reference = new StringBuilder(d.get(start, nonWS - start)); |
| else |
| reference = indenter.getReferenceIndentation(c.offset); |
| if (reference != null) |
| buf.append(reference); |
| buf.append('}'); |
| int bound= c.offset > 200 ? c.offset - 200 : CHeuristicScanner.UNBOUND; |
| int bracePos = scanner.findOpeningPeer(c.offset - 1, bound, '{', '}'); |
| if (bracePos != CHeuristicScanner.NOT_FOUND) { |
| if (scanner.looksLikeCompositeTypeDefinitionBackward(bracePos, bound) || |
| scanner.previousToken(bracePos - 1, bound) == Symbols.TokenEQUAL) { |
| 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 != CHeuristicScanner.NOT_FOUND && d.getChar(firstCharPos) == '{') { |
| c.caretOffset = c.offset + buf.length(); |
| c.shiftsCaret = false; |
| |
| StringBuilder reference = null; |
| int nonWS = findEndOfWhiteSpace(d, start, lineEnd); |
| if (nonWS < c.offset && d.getChar(nonWS) == '{') |
| reference = new StringBuilder(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) { |
| CUIPlugin.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 |
| |
| CHeuristicScanner scanner = new CHeuristicScanner(document); |
| int pos = offset; |
| int length = max; |
| int scanTo = scanner.scanForward(pos, length, '}'); |
| if (scanTo == -1) |
| scanTo = length; |
| |
| int closingParen = findClosingParenToLeft(scanner, pos) - 1; |
| |
| while (true) { |
| int startScan = closingParen + 1; |
| closingParen = scanner.scanForward(startScan, scanTo, ')'); |
| if (closingParen == -1) |
| break; |
| |
| int 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; |
| } |
| |
| 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 C 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(CHeuristicScanner scanner, int position) { |
| if (position < 1) |
| return position; |
| |
| if (scanner.previousToken(position - 1, CHeuristicScanner.UNBOUND) == Symbols.TokenRPAREN) |
| return scanner.getPosition() + 1; |
| return position; |
| } |
| |
| private boolean isClosedBrace(IDocument document, int offset, int length) { |
| return getBlockBalance(document, offset, fPartitioning) <= 0; |
| //TODO: Use smarter algorithm based on |
| // CompilationUnitInfo info = getCompilationUnitForMethod(document, offset, fPartitioning); |
| // if (info == null) |
| // return false; |
| // |
| // CodeReader reader = new CodeReader(info.buffer); |
| // ICodeReaderFactory fileCreator = CDOM.getInstance().getCodeReaderFactory(CDOM.PARSE_WORKING_COPY_WHENEVER_POSSIBLE); |
| // |
| // IScanner domScanner = new DOMScanner(reader, new ScannerInfo(), ParserMode.COMPLETE_PARSE, |
| // ParserLanguage.C, ParserFactory.createDefaultLogService(), |
| // C_GNU_SCANNER_EXTENSION, fileCreator); |
| // |
| // ISourceCodeParser parser = new GNUCPPSourceParser( |
| // domScanner, |
| // ParserMode.COMPLETE_PARSE, |
| // ParserUtil.getParserLogService(), |
| // new GPPParserExtensionConfiguration()); |
| // |
| // IASTTranslationUnit translationUnit = parser.parse(); |
| // final int relativeOffset = offset - info.delta; |
| // IASTNode node = translationUnit.selectNodeForLocation(reader.getPath(), relativeOffset, length); |
| // |
| // if (node == null) |
| // return false; |
| // |
| // if (node instanceof IASTCompoundStatement) { |
| // return getBlockBalance(document, offset, fPartitioning) <= 0; |
| // } else if (node instanceof IASTIfStatement) { |
| // IASTIfStatement ifStatement = (IASTIfStatement) node; |
| // IASTExpression expression = ifStatement.getConditionExpression(); |
| // IRegion expressionRegion = createRegion(expression, info.delta); |
| // IASTStatement thenStatement = ifStatement.getThenClause(); |
| // IRegion thenRegion = createRegion(thenStatement, info.delta); |
| // |
| // // Between expression and then statement |
| // if (expressionRegion.getOffset() + expressionRegion.getLength() <= offset && offset + length <= thenRegion.getOffset()) |
| // return thenStatement != null; |
| // |
| // IASTStatement elseStatement = ifStatement.getElseClause(); |
| // IRegion elseRegion = createRegion(elseStatement, info.delta); |
| // |
| // if (elseStatement != null) { |
| // int sourceOffset = thenRegion.getOffset() + thenRegion.getLength(); |
| // int sourceLength = elseRegion.getOffset() - sourceOffset; |
| // CHeuristicScanner scanner = new CHeuristicScanner(new SimpleDocument(info.buffer)); |
| // int pos = sourceOffset; |
| // int id; |
| // while ((id = scanner.nextToken(pos, sourceOffset + sourceLength - pos)) != CHeuristicScanner.TokenEOF) { |
| // if (id == CHeuristicScanner.TokenELSE) { |
| // pos = scanner.getPosition(); |
| // // Between 'else' token and else statement. |
| // return pos <= offset && offset + length < elseRegion.getOffset(); |
| // } |
| // } |
| // |
| // return true; |
| // } |
| // } else if (node instanceof IASTForStatement) { |
| // IASTExpression expression = ((IASTForStatement) node).getConditionExpression(); |
| // IRegion expressionRegion = createRegion(expression, info.delta); |
| // IASTStatement body = ((IASTForStatement) 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; |
| // } |
| // } else if (node instanceof IASTWhileStatement) { |
| // IASTExpression expression = ((IASTWhileStatement) node).getCondition(); |
| // IRegion expressionRegion = createRegion(expression, info.delta); |
| // IASTStatement body = ((IASTWhileStatement) 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; |
| // } |
| // } else if (node instanceof IASTDoStatement) { |
| // IASTDoStatement doStatement = (IASTDoStatement) node; |
| // IRegion doRegion = createRegion(doStatement, info.delta); |
| // IASTStatement body = doStatement.getBody(); |
| // IRegion bodyRegion = createRegion(body, info.delta); |
| // |
| // // Between 'do' and body statement. |
| // if (doRegion.getOffset() + doRegion.getLength() <= offset && offset + length <= bodyRegion.getOffset()) { |
| // return body != null; |
| // } |
| // } |
| // |
| // return true; |
| } |
| |
| private boolean isLineDelimiter(IDocument document, String text) { |
| String[] delimiters = document.getLegalLineDelimiters(); |
| if (delimiters != null) |
| return TextUtilities.equals(delimiters, text) > -1; |
| return false; |
| } |
| |
| /** |
| * Installs a C partitioner with <code>document</code>. |
| * |
| * @param document the document |
| */ |
| private static void installCPartitioner(Document document) { |
| String[] types= new String[] { |
| ICPartitions.C_MULTI_LINE_COMMENT, |
| ICPartitions.C_SINGLE_LINE_COMMENT, |
| ICPartitions.C_STRING, |
| ICPartitions.C_CHARACTER, |
| ICPartitions.C_PREPROCESSOR, |
| IDocument.DEFAULT_CONTENT_TYPE |
| }; |
| FastPartitioner partitioner= new FastPartitioner(new FastCPartitionScanner(), types); |
| partitioner.connect(document); |
| document.setDocumentPartitioner(ICPartitions.C_PARTITIONING, partitioner); |
| } |
| |
| /** |
| * Installs a C partitioner with <code>document</code>. |
| * |
| * @param document the document |
| */ |
| private static void removeCPartitioner(Document document) { |
| document.setDocumentPartitioner(ICPartitions.C_PARTITIONING, null); |
| } |
| |
| private void smartPaste(IDocument document, DocumentCommand command) { |
| int newOffset= command.offset; |
| int newLength= command.length; |
| String newText= command.text; |
| |
| try { |
| CHeuristicScanner scanner= new CHeuristicScanner(document); |
| CIndenter indenter= new CIndenter(document, scanner, fProject); |
| int offset= newOffset; |
| |
| // reference position to get the indent from |
| int refOffset= indenter.findReferencePosition(offset); |
| if (refOffset == CHeuristicScanner.NOT_FOUND) |
| return; |
| int peerOffset= getPeerPosition(document, command); |
| peerOffset= indenter.findReferencePosition(peerOffset); |
| if (peerOffset == CHeuristicScanner.NOT_FOUND) |
| return; |
| 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. |
| // Take up to 100 previous lines to preserve enough context. |
| int firstPrefixLine= Math.max(document.getLineOfOffset(refOffset) - 100, 0); |
| int prefixOffset= document.getLineInformation(firstPrefixLine).getOffset(); |
| String prefix= document.get(prefixOffset, newOffset - prefixOffset); |
| |
| // Handle the indentation computation inside a temporary document |
| Document temp= new Document(prefix + newText); |
| DocumentRewriteSession session= temp.startRewriteSession(DocumentRewriteSessionType.STRICTLY_SEQUENTIAL); |
| scanner= new CHeuristicScanner(temp); |
| indenter= new CIndenter(temp, scanner, fProject); |
| installCPartitioner(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; |
| StringBuilder addition= new StringBuilder(); |
| int insertLength= 0; |
| int first= document.computeNumberOfLines(prefix) + firstLine; // don't format first line |
| int lines= temp.getNumberOfLines(); |
| boolean changed= false; |
| boolean indentInsideLineComments= IndentUtil.indentInsideLineComments(fProject); |
| |
| 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= IndentUtil.getCurrentIndent(temp, l, indentInsideLineComments); |
| StringBuilder correct= new StringBuilder(IndentUtil.computeIndent(temp, l, indenter, scanner)); |
| |
| insertLength= subtractIndent(correct, current, addition); |
| // workaround for bug 181139 |
| 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; |
| } |
| removeCPartitioner(temp); |
| } else { |
| changed= insertLength != 0; |
| } |
| } |
| |
| // relatively indent all pasted lines |
| if (insertLength > 0) |
| addIndent(temp, l, addition, indentInsideLineComments); |
| else if (insertLength < 0) |
| cutIndent(temp, l, -insertLength, indentInsideLineComments); |
| } |
| |
| temp.stopRewriteSession(session); |
| newText= temp.get(prefix.length(), temp.getLength() - prefix.length()); |
| |
| command.offset= newOffset; |
| command.length= newLength; |
| command.text= newText; |
| } catch (BadLocationException e) { |
| CUIPlugin.log(e); |
| } |
| } |
| |
| /** |
| * 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 (migth 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 |
| * @return the difference in lenght of <code>correct</code> and <code>current</code> |
| */ |
| private int subtractIndent(CharSequence correct, CharSequence current, StringBuilder difference) { |
| int c1= computeVisualLength(correct); |
| int c2= computeVisualLength(current); |
| 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); |
| } |
| |
| 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 indentInsideLineComments option whether to indent inside line comments starting at column 0 |
| * @throws BadLocationException on concurrent document modification |
| */ |
| private static void addIndent(Document document, int line, CharSequence indent, boolean indentInsideLineComments) throws BadLocationException { |
| IRegion region= document.getLineInformation(line); |
| int insert= region.getOffset(); |
| int endOffset= region.getOffset() + region.getLength(); |
| |
| if (indentInsideLineComments) { |
| // go behind line comments |
| while (insert < endOffset - 2 && document.get(insert, 2).equals(LINE_COMMENT)) |
| insert += 2; |
| } |
| |
| // 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 indentInsideLineComments option whether to indent inside line comments starting at column 0 |
| * @throws BadLocationException on concurrent document modification |
| */ |
| private void cutIndent(Document document, int line, int toDelete, boolean indentInsideLineComments) throws BadLocationException { |
| IRegion region= document.getLineInformation(line); |
| int from= region.getOffset(); |
| int endOffset= region.getOffset() + region.getLength(); |
| |
| if (indentInsideLineComments) { |
| // 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); |
| if (toDelete >= 0) |
| to++; |
| else |
| break; |
| } |
| |
| document.replace(from, to - from, null); |
| } |
| |
| /** |
| * Returns the visual length of a given <code>CharSequence</code> taking into |
| * account the visual tabulator length. |
| * |
| * @param seq the string to measure |
| * @return the visual length of <code>seq</code> |
| */ |
| private int computeVisualLength(CharSequence seq) { |
| int size= 0; |
| int tablen= getVisualTabLengthPreference(); |
| |
| for (int i= 0; i < seq.length(); i++) { |
| char ch= seq.charAt(i); |
| if (ch == '\t') { |
| if (tablen != 0) |
| size += tablen - size % tablen; |
| // 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 |
| * @return the visual length of <code>ch</code> |
| */ |
| private int computeVisualLength(char ch) { |
| if (ch == '\t') |
| return getVisualTabLengthPreference(); |
| 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); |
| } |
| |
| 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); |
| installCPartitioner(pasted); |
| int firstPeer= command.offset; |
| |
| CHeuristicScanner pScanner= new CHeuristicScanner(pasted); |
| CHeuristicScanner dScanner= new CHeuristicScanner(document); |
| |
| // add scope relevant after context to peer search |
| int afterToken= dScanner.nextToken(command.offset + command.length, CHeuristicScanner.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, CHeuristicScanner.UNBOUND); |
| pPos= pScanner.getPosition(); |
| switch (token) { |
| case Symbols.TokenLBRACE: |
| case Symbols.TokenLBRACKET: |
| case Symbols.TokenLPAREN: |
| pPos= skipScope(pScanner, pPos, token); |
| if (pPos == CHeuristicScanner.NOT_FOUND) |
| return firstPeer; |
| break; // closed scope -> keep searching |
| case Symbols.TokenRBRACE: |
| int peer= dScanner.findOpeningPeer(dPos, '{', '}'); |
| dPos= peer - 1; |
| if (peer == CHeuristicScanner.NOT_FOUND) |
| return firstPeer; |
| firstPeer= peer; |
| break; // keep searching |
| case Symbols.TokenRBRACKET: |
| peer= dScanner.findOpeningPeer(dPos, '[', ']'); |
| dPos= peer - 1; |
| if (peer == CHeuristicScanner.NOT_FOUND) |
| return firstPeer; |
| firstPeer= peer; |
| break; // keep searching |
| case Symbols.TokenRPAREN: |
| peer= dScanner.findOpeningPeer(dPos, '(', ')'); |
| dPos= peer - 1; |
| if (peer == CHeuristicScanner.NOT_FOUND) |
| return firstPeer; |
| firstPeer= peer; |
| break; // keep searching |
| |
| case Symbols.TokenCASE: |
| case Symbols.TokenDEFAULT: |
| { |
| CIndenter indenter= new CIndenter(document, dScanner, fProject); |
| peer= indenter.findReferencePosition(dPos, false, MatchMode.MATCH_CASE); |
| if (peer == CHeuristicScanner.NOT_FOUND) |
| return firstPeer; |
| firstPeer= peer; |
| } |
| break; // keep searching |
| |
| case Symbols.TokenPUBLIC: |
| case Symbols.TokenPROTECTED: |
| case Symbols.TokenPRIVATE: |
| { |
| CIndenter indenter= new CIndenter(document, dScanner, fProject); |
| peer= indenter.findReferencePosition(dPos, false, MatchMode.MATCH_ACCESS_SPECIFIER); |
| if (peer == CHeuristicScanner.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> in <code>document</code>, |
| * returns either the position of the |
| * @param pos |
| * @param token |
| * @return the position after the scope |
| */ |
| private static int skipScope(CHeuristicScanner scanner, int pos, 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= pos; |
| |
| while (true) { |
| int tok= scanner.nextToken(p, CHeuristicScanner.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 CHeuristicScanner.NOT_FOUND; |
| } |
| } |
| } |
| |
| 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; |
| case ':': |
| smartIndentAfterColumn(document, command); |
| break; |
| case '#': |
| smartIndentAfterHash(document, command); |
| break; |
| } |
| } |
| |
| private void smartIndentUponE(IDocument doc, DocumentCommand c) { |
| if (c.offset < 4 || doc.getLength() == 0) |
| return; |
| |
| try { |
| String content = doc.get(c.offset - 3, 3); |
| if (content.equals("els")) { //$NON-NLS-1$ |
| CHeuristicScanner scanner = new CHeuristicScanner(doc); |
| int p = c.offset - 3; |
| |
| // current line |
| int line = doc.getLineOfOffset(p); |
| int lineOffset = doc.getLineOffset(line); |
| |
| // make sure we don't have any leading comments etc. |
| if (doc.get(lineOffset, p - lineOffset).trim().length() != 0) |
| return; |
| |
| // Line of last C code |
| int pos = scanner.findNonWhitespaceBackward(p - 1, CHeuristicScanner.UNBOUND); |
| if (pos == -1) |
| return; |
| int lastLine = doc.getLineOfOffset(pos); |
| |
| // Only shift if the last C line is further up and is a braceless block candidate |
| if (lastLine < line) { |
| CIndenter indenter = new CIndenter(doc, scanner, fProject); |
| int ref = indenter.findReferencePosition(p, true, MatchMode.REGULAR); |
| if (ref == CHeuristicScanner.NOT_FOUND) |
| return; |
| int refLine = doc.getLineOfOffset(ref); |
| String indent = getIndentOfLine(doc, 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$ |
| CHeuristicScanner scanner = new CHeuristicScanner(doc); |
| int p = c.offset - 3; |
| |
| // current line |
| int line = doc.getLineOfOffset(p); |
| int lineOffset = doc.getLineOffset(line); |
| |
| // make sure we don't have any leading comments etc. |
| if (doc.get(lineOffset, p - lineOffset).trim().length() != 0) |
| return; |
| |
| // Line of last C code |
| int pos = scanner.findNonWhitespaceBackward(p - 1, CHeuristicScanner.UNBOUND); |
| if (pos == -1) |
| return; |
| int lastLine = doc.getLineOfOffset(pos); |
| |
| // Only shift if the last C line is further up and is a braceless block candidate |
| if (lastLine < line) { |
| CIndenter indenter = new CIndenter(doc, scanner, fProject); |
| int ref = indenter.findReferencePosition(p, false, MatchMode.MATCH_CASE); |
| if (ref == CHeuristicScanner.NOT_FOUND) |
| return; |
| int refLine = doc.getLineOfOffset(ref); |
| int nextToken = scanner.nextToken(ref, CHeuristicScanner.UNBOUND); |
| String indent; |
| if (nextToken == Symbols.TokenCASE || nextToken == Symbols.TokenDEFAULT) |
| indent = getIndentOfLine(doc, 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) { |
| CUIPlugin.log(e); |
| } |
| } |
| |
| private void smartIndentAfterColumn(IDocument doc, DocumentCommand c) { |
| try { |
| int offset = c.offset; |
| // Current line |
| int line = doc.getLineOfOffset(offset); |
| IRegion startLine = doc.getLineInformationOfOffset(offset); |
| int lineOffset = startLine.getOffset(); |
| |
| CHeuristicScanner scanner = new CHeuristicScanner(doc); |
| int prevToken = scanner.previousToken(offset - 1, lineOffset); |
| switch (prevToken) { |
| case Symbols.TokenDEFAULT: |
| case Symbols.TokenPUBLIC: |
| case Symbols.TokenPROTECTED: |
| case Symbols.TokenPRIVATE: |
| break; |
| |
| default: |
| return; |
| } |
| |
| int p = scanner.getPosition() + 1; |
| |
| // Make sure we don't have any leading comments etc. |
| if (doc.get(lineOffset, p - lineOffset).trim().length() != 0) |
| return; |
| |
| // Line of last C code |
| int pos = scanner.findNonWhitespaceBackward(p - 1, CHeuristicScanner.UNBOUND); |
| if (pos == -1) |
| return; |
| int lastLine = doc.getLineOfOffset(pos); |
| |
| // Only shift if the last C line is further up and is a braceless block candidate |
| if (lastLine < line) { |
| CIndenter indenter = new CIndenter(doc, scanner, fProject); |
| int ref; |
| if (prevToken == Symbols.TokenDEFAULT) |
| ref = indenter.findReferencePosition(p, false, MatchMode.MATCH_CASE); |
| else |
| ref = indenter.findReferencePosition(p, false, MatchMode.MATCH_ACCESS_SPECIFIER); |
| if (ref == CHeuristicScanner.NOT_FOUND) |
| return; |
| int refLine = doc.getLineOfOffset(ref); |
| int nextToken = scanner.nextToken(ref, CHeuristicScanner.UNBOUND); |
| String indent; |
| if (nextToken == Symbols.TokenCASE || nextToken == Symbols.TokenDEFAULT || |
| nextToken == Symbols.TokenPUBLIC || nextToken == Symbols.TokenPROTECTED || |
| nextToken == Symbols.TokenPRIVATE) { |
| indent = getIndentOfLine(doc, refLine); |
| } else { // at the brace of the switch or the class |
| indent = indenter.computeIndentation(p).toString(); |
| } |
| |
| if (indent != null) { |
| c.text = indent.toString() + doc.get(p, offset - p) + c.text; |
| c.length += c.offset - lineOffset; |
| c.offset = lineOffset; |
| } |
| } |
| |
| return; |
| } catch (BadLocationException e) { |
| CUIPlugin.log(e); |
| } |
| } |
| |
| private void smartIndentAfterHash(IDocument doc, DocumentCommand c) { |
| try { |
| ITypedRegion partition= TextUtilities.getPartition(doc, fPartitioning, c.offset, false); |
| if (IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) { |
| IRegion startLine= doc.getLineInformationOfOffset(c.offset); |
| String indent= doc.get(startLine.getOffset(), c.offset - startLine.getOffset()); |
| if (indent.trim().length() == 0) { |
| c.offset -= indent.length(); |
| c.length += indent.length(); |
| } |
| } |
| } catch (BadLocationException e) { |
| CUIPlugin.log(e); |
| } |
| } |
| |
| @Override |
| public void customizeDocumentCommand(IDocument d, DocumentCommand c) { |
| if (!c.doit) |
| return; |
| |
| clearCachedValues(); |
| if (!fIsSmartMode) { |
| super.customizeDocumentCommand(d, c); |
| return; |
| } |
| |
| boolean isNewLine= c.length == 0 && c.text != null && isLineDelimiter(d, c.text); |
| if (isNewLine) { |
| smartIndentAfterNewLine(d, c); |
| } else if (c.text.length() == 1 && getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_AUTO_INDENT)) { |
| smartIndentOnKeypress(d, c); |
| } else if (c.text.length() > 1 |
| && getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SMART_PASTE) |
| && c.text.trim().length() != 0) { |
| smartPaste(d, c); // no smart backspace for paste |
| } |
| } |
| |
| private static IPreferenceStore getPreferenceStore() { |
| return CUIPlugin.getDefault().getCombinedPreferenceStore(); |
| } |
| |
| private void clearCachedValues() { |
| IPreferenceStore preferenceStore = getPreferenceStore(); |
| fCloseBrace = preferenceStore.getBoolean(PreferenceConstants.EDITOR_CLOSE_BRACES); |
| fIsSmartMode = computeSmartMode(); |
| } |
| |
| private boolean computeSmartMode() { |
| IWorkbenchPage page = CUIPlugin.getActivePage(); |
| if (page != null) { |
| if (alwaysUseSmartMode) { |
| return true; |
| } |
| IEditorPart part = page.getActiveEditor(); |
| if (part instanceof MultiPageEditorPart) { |
| part= (IEditorPart)part.getAdapter(ITextEditorExtension3.class); |
| } |
| if (part instanceof ITextEditorExtension3) { |
| ITextEditorExtension3 extension = (ITextEditorExtension3) part; |
| return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT; |
| } |
| if (part == null) { |
| // TODO: Remove this if statement once CAutoIndentTest is fixed so that getActiveEditor does not return null. |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // private static CompilationUnitInfo getCompilationUnitForMethod(IDocument document, int offset, String partitioning) { |
| // try { |
| // CHeuristicScanner scanner = new CHeuristicScanner(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("};"); //$NON-NLS-1$ |
| // |
| // char[] buffer = contents.toString().toCharArray(); |
| // return new CompilationUnitInfo(buffer, sourceRange.getOffset() - methodOffset); |
| // } catch (BadLocationException e) { |
| // CUIPlugin.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 |
| * @param offset |
| * @param 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; |
| |
| CHeuristicScanner scanner = new CHeuristicScanner(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(IASTNode node, int delta) { |
| // IASTNodeLocation nodeLocation = node.getNodeLocations()[0]; |
| // return node == null ? null : new Region(nodeLocation.getNodeOffset() + delta, nodeLocation.getNodeLength()); |
| // } |
| } |