diff --git a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF index 73f6b776b56..b78d7f4807b 100644 --- a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF +++ b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF @@ -91,7 +91,7 @@ Export-Package: org.eclipse.cdt.core, org.eclipse.cdt.internal.core.settings.model;x-internal:=true, org.eclipse.cdt.internal.core.util;x-internal:=true, org.eclipse.cdt.internal.errorparsers;x-internal:=true, - org.eclipse.cdt.internal.formatter;x-internal:=true, + org.eclipse.cdt.internal.formatter;x-friends:="org.eclipse.cdt.ui", org.eclipse.cdt.internal.formatter.align;x-internal:=true, org.eclipse.cdt.internal.formatter.scanner;x-friends:="org.eclipse.cdt.ui", org.eclipse.cdt.utils, diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/changegenerator/ChangeGenerator.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/changegenerator/ChangeGenerator.java index ce28e387a5c..bdd4d9e059a 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/changegenerator/ChangeGenerator.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/changegenerator/ChangeGenerator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2012 Institute for Software, HSR Hochschule fuer Technik + * Copyright (c) 2008, 2013 Institute for Software, HSR Hochschule fuer Technik * Rapperswil, University of applied sciences and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -14,15 +14,12 @@ package org.eclipse.cdt.internal.core.dom.rewrite.changegenerator; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; -import org.eclipse.cdt.core.CCorePlugin; -import org.eclipse.cdt.core.ToolFactory; import org.eclipse.cdt.core.dom.ast.ASTVisitor; import org.eclipse.cdt.core.dom.ast.IASTArrayModifier; import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier; @@ -46,9 +43,6 @@ import org.eclipse.cdt.core.dom.ast.IASTTypeId; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDefinition; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition; -import org.eclipse.cdt.core.formatter.CodeFormatter; -import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants; -import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.internal.core.dom.rewrite.ASTModification; import org.eclipse.cdt.internal.core.dom.rewrite.ASTModification.ModificationKind; @@ -59,20 +53,14 @@ import org.eclipse.cdt.internal.core.dom.rewrite.astwriter.ASTWriter; import org.eclipse.cdt.internal.core.dom.rewrite.astwriter.ContainerNode; import org.eclipse.cdt.internal.core.dom.rewrite.astwriter.ProblemRuntimeException; import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.NodeCommentMap; +import org.eclipse.cdt.internal.formatter.ChangeFormatter; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.Assert; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.Document; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.Region; -import org.eclipse.jface.text.TextUtilities; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.CompositeChange; import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.text.edits.DeleteEdit; import org.eclipse.text.edits.InsertEdit; -import org.eclipse.text.edits.MalformedTreeException; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; @@ -125,7 +113,7 @@ public class ChangeGenerator extends ASTVisitor { IASTTranslationUnit ast = rootNode.getTranslationUnit(); String source = ast.getRawSignature(); ITranslationUnit tu = ast.getOriginatingTranslationUnit(); - formatChangedCode(source, tu); + rootEdit = ChangeFormatter.formatChangedCode(source, tu, rootEdit); TextFileChange subchange= ASTRewriteAnalyzer.createCTextFileChange((IFile) tu.getResource()); subchange.setEdit(rootEdit); change.add(subchange); @@ -317,195 +305,6 @@ public class ChangeGenerator extends ASTVisitor { processedOffset = edit.getExclusiveEnd(); } - private TextEdit clippedEdit(TextEdit edit, IRegion region) { - if ((edit.getOffset() < region.getOffset() && edit.getExclusiveEnd() <= region.getOffset()) || - edit.getOffset() >= endOffset(region)) { - return null; - } - int offset = Math.max(edit.getOffset(), region.getOffset()); - int length = Math.min(endOffset(edit), endOffset(region)) - offset; - if (offset == edit.getOffset() && length == edit.getLength()) { - // InsertEdit always satisfies the above condition. - return edit; - } - if (edit instanceof DeleteEdit) { - return new DeleteEdit(offset, length); - } if (edit instanceof ReplaceEdit) { - String replacement = ((ReplaceEdit) edit).getText(); - int start = Math.max(offset - edit.getOffset(), 0); - int end = Math.min(endOffset(region) - offset, replacement.length()); - if (end <= start) { - return new DeleteEdit(offset, length); - } - return new ReplaceEdit(offset, length, replacement.substring(start, end)); - } else { - throw new IllegalArgumentException("Unexpected edit type: " + edit.getClass().getSimpleName()); //$NON-NLS-1$ - } - } - - /** - * Applies the C++ code formatter to the code affected by refactoring. - * - * @param code The code being modified. - * @param tu The translation unit containing the code. - */ - private void formatChangedCode(String code, ITranslationUnit tu) { - IDocument document = new Document(code); - try { - TextEdit edit = rootEdit.copy(); - // Apply refactoring changes to a temporary document. - edit.apply(document, TextEdit.UPDATE_REGIONS); - - // Expand regions affected by the changes to cover complete lines. We calculate two - // sets of regions, reflecting the state of the document before and after - // the refactoring changes. - TextEdit[] appliedEdits = edit.getChildren(); - TextEdit[] edits = rootEdit.removeChildren(); - IRegion[] regions = new IRegion[appliedEdits.length]; - int numRegions = 0; - int prevEnd = -1; - for (int i = 0; i < appliedEdits.length; i++) { - edit = appliedEdits[i]; - int offset = edit.getOffset(); - int end = offset + edit.getLength(); - int newOffset = document.getLineInformationOfOffset(offset).getOffset(); - edit = edits[i]; - int originalEnd = edit.getExclusiveEnd(); - // Expand to the end of the line unless the end of the edit region is at - // the beginning of line both, before and after the change. - int newEnd = (originalEnd == 0 || code.charAt(originalEnd - 1) == '\n') && end == newOffset ? - end : endOffset(document.getLineInformationOfOffset(end)); - if (newOffset <= prevEnd) { - numRegions--; - newOffset = regions[numRegions].getOffset(); - } - prevEnd = newEnd; - regions[numRegions] = new Region(newOffset, newEnd - newOffset); - numRegions++; - } - - if (numRegions < regions.length) { - regions = Arrays.copyOf(regions, numRegions); - } - - // Calculate formatting changes for the regions after the refactoring changes. - ICProject project = tu.getCProject(); - Map options = new HashMap(project.getOptions(true)); - options.put(DefaultCodeFormatterConstants.FORMATTER_TRANSLATION_UNIT, tu); - // Allow all comments to be indented. - options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_NEVER_INDENT_LINE_COMMENTS_ON_FIRST_COLUMN, - DefaultCodeFormatterConstants.FALSE); - CodeFormatter formatter = ToolFactory.createCodeFormatter(options); - code = document.get(); - TextEdit[] formatEdits = formatter.format(CodeFormatter.K_TRANSLATION_UNIT, code, - regions, TextUtilities.getDefaultLineDelimiter(document)); - - TextEdit combinedFormatEdit = new MultiTextEdit(); - for (TextEdit formatEdit : formatEdits) { - combinedFormatEdit = TextEditUtil.merge(combinedFormatEdit, formatEdit); - } - formatEdits = TextEditUtil.flatten(combinedFormatEdit).removeChildren(); - - MultiTextEdit result = new MultiTextEdit(); - int delta = 0; - TextEdit edit1 = null; - TextEdit edit2 = null; - int i = 0; - int j = 0; - while (true) { - if (edit1 == null && i < edits.length) - edit1 = edits[i++]; - if (edit2 == null && j < formatEdits.length) - edit2 = formatEdits[j++]; - if (edit1 == null) { - if (edit2 == null) - break; - edit2.moveTree(-delta); - result.addChild(edit2); - edit2 = null; - } else if (edit2 == null) { - delta += TextEditUtil.delta(edit1); - result.addChild(edit1); - edit1 = null; - } else { - if (edit2.getExclusiveEnd() - delta <= edit1.getOffset()) { - edit2.moveTree(-delta); - result.addChild(edit2); - edit2 = null; - } else { - TextEdit piece = clippedEdit(edit2, new Region(-1, edit1.getOffset() + delta)); - if (piece != null) { - piece.moveTree(-delta); - result.addChild(piece); - } - int d = TextEditUtil.delta(edit1); - Region region = new Region(edit1.getOffset() + delta, edit1.getLength() + d); - int end = endOffset(region); - MultiTextEdit format = new MultiTextEdit(); - while ((piece = clippedEdit(edit2, region)) != null) { - format.addChild(piece); - // The warning "The variable edit2 may be null at this location" is bogus. - // Make the compiler happy: - if (edit2 != null) { - if (edit2.getExclusiveEnd() >= end || j >= formatEdits.length) { - break; - } - } - edit2 = formatEdits[j++]; - } - if (format.hasChildren()) { - format.moveTree(-delta); - edit1 = applyEdit(format, edit1); - } - delta += d; - result.addChild(edit1); - edit1 = null; - - edit2 = clippedEdit(edit2, new Region(end, Integer.MAX_VALUE - end)); - } - } - } - rootEdit = result; - } catch (MalformedTreeException e) { - CCorePlugin.log(e); - } catch (BadLocationException e) { - CCorePlugin.log(e); - } - } - - /** - * Applies source edit to the target one and returns the combined edit. - */ - private TextEdit applyEdit(TextEdit source, TextEdit target) - throws MalformedTreeException, BadLocationException { - source.moveTree(-target.getOffset()); - String text; - if (target instanceof InsertEdit) { - text = ((InsertEdit) target).getText(); - } else if (target instanceof ReplaceEdit) { - text = ((ReplaceEdit) target).getText(); - } else { - text = ""; //$NON-NLS-1$ - } - - IDocument document = new Document(text); - source.apply(document, TextEdit.NONE); - text = document.get(); - if (target.getLength() == 0) { - return new InsertEdit(target.getOffset(), text); - } else { - return new ReplaceEdit(target.getOffset(), target.getLength(), text); - } - } - - private int endOffset(IRegion region) { - return region.getOffset() + region.getLength(); - } - - private int endOffset(TextEdit edit) { - return edit.getOffset() + edit.getLength(); - } - private int endOffset(IASTFileLocation nodeLocation) { return nodeLocation.getNodeOffset() + nodeLocation.getNodeLength(); } diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/util/ASTNodes.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/util/ASTNodes.java index 05b7e7aed52..c4785d1668f 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/util/ASTNodes.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/util/ASTNodes.java @@ -42,7 +42,7 @@ public class ASTNodes { * Returns the offset of the beginning of the next line after the node, or the end-of-file * offset if there is no line delimiter after the node. */ - public static int skipToNextLineAfterNode(char[] text, IASTNode node) { + public static int skipToNextLineAfterNode(String text, IASTNode node) { return TextUtil.skipToNextLine(text, getNodeEndOffset(node)); } } diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/util/TextUtil.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/util/TextUtil.java index 2060abb34e5..3e0ab14a3ce 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/util/TextUtil.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/util/TextUtil.java @@ -21,9 +21,9 @@ public class TextUtil { * Returns the offset of the beginning of the next line after the given offset, * or the end-of-file offset if there is no line delimiter after the given offset. */ - public static int skipToNextLine(char[] text, int offset) { - while (offset < text.length) { - if (text[offset++] == '\n') + public static int skipToNextLine(String text, int offset) { + while (offset < text.length()) { + if (text.charAt(offset++) == '\n') break; } return offset; @@ -32,21 +32,9 @@ public class TextUtil { /** * Returns the offset of the beginning of the line containing the given offset. */ - public static int getLineStart(char[] text, int offset) { + public static int getLineStart(String text, int offset) { while (--offset >= 0) { - if (text[offset] == '\n') - break; - } - return offset + 1; - } - - /** - * Skips whitespace characters to the left of the given offset without leaving the current line. - */ - public static int skipWhitespaceToTheLeft(char[] text, int offset) { - while (--offset >= 0) { - char c = text[offset]; - if (c == '\n' || !Character.isWhitespace(c)) + if (text.charAt(offset) == '\n') break; } return offset + 1; @@ -56,13 +44,13 @@ public class TextUtil { * Returns {@code true} the line prior to the line corresponding to the given {@code offset} * does not contain non-whitespace characters. */ - public static boolean isPreviousLineBlank(char[] text, int offset) { + public static boolean isPreviousLineBlank(String text, int offset) { while (--offset >= 0) { - if (text[offset] == '\n') + if (text.charAt(offset) == '\n') break; } while (--offset >= 0) { - char c = text[offset]; + char c = text.charAt(offset); if (c == '\n') return true; if (!Character.isWhitespace(c)) diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/ChangeFormatter.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/ChangeFormatter.java new file mode 100644 index 00000000000..de393ea8b1c --- /dev/null +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/ChangeFormatter.java @@ -0,0 +1,232 @@ +/******************************************************************************* + * Copyright (c) 2013 Google, Inc 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: + * Sergey Prigogin (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.internal.formatter; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.ToolFactory; +import org.eclipse.cdt.core.formatter.CodeFormatter; +import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants; +import org.eclipse.cdt.core.model.ICProject; +import org.eclipse.cdt.core.model.ITranslationUnit; +import org.eclipse.cdt.internal.core.dom.rewrite.changegenerator.TextEditUtil; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextUtilities; +import org.eclipse.text.edits.DeleteEdit; +import org.eclipse.text.edits.InsertEdit; +import org.eclipse.text.edits.MalformedTreeException; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.text.edits.ReplaceEdit; +import org.eclipse.text.edits.TextEdit; + +/** + * Applies the C++ code formatter to the code affected by refactoring. + */ +public class ChangeFormatter { + /** + * Applies the C++ code formatter to the code affected by refactoring. + * + * @param code The code being modified. + * @param tu The translation unit containing the code. + */ + public static MultiTextEdit formatChangedCode(String code, ITranslationUnit tu, MultiTextEdit rootEdit) { + IDocument document = new Document(code); + try { + TextEdit edit = rootEdit.copy(); + // Apply refactoring changes to a temporary document. + edit.apply(document, TextEdit.UPDATE_REGIONS); + + // Expand regions affected by the changes to cover complete lines. We calculate two + // sets of regions, reflecting the state of the document before and after + // the refactoring changes. + TextEdit[] appliedEdits = edit.getChildren(); + TextEdit[] edits = rootEdit.copy().removeChildren(); + IRegion[] regions = new IRegion[appliedEdits.length]; + int numRegions = 0; + int prevEnd = -1; + for (int i = 0; i < appliedEdits.length; i++) { + edit = appliedEdits[i]; + int offset = edit.getOffset(); + int end = offset + edit.getLength(); + int newOffset = document.getLineInformationOfOffset(offset).getOffset(); + edit = edits[i]; + int originalEnd = edit.getExclusiveEnd(); + // Expand to the end of the line unless the end of the edit region is at + // the beginning of line both, before and after the change. + IRegion lineInfo = document.getLineInformationOfOffset(end); + int newEnd = lineInfo.getOffset(); + newEnd = (originalEnd == 0 || code.charAt(originalEnd - 1) == '\n') && end == newEnd ? + end : endOffset(lineInfo); + if (newOffset <= prevEnd) { + numRegions--; + newOffset = regions[numRegions].getOffset(); + } + prevEnd = newEnd; + regions[numRegions] = new Region(newOffset, newEnd - newOffset); + numRegions++; + } + + if (numRegions < regions.length) { + regions = Arrays.copyOf(regions, numRegions); + } + + // Calculate formatting changes for the regions after the refactoring changes. + ICProject project = tu.getCProject(); + Map options = new HashMap(project.getOptions(true)); + options.put(DefaultCodeFormatterConstants.FORMATTER_TRANSLATION_UNIT, tu); + // Allow all comments to be indented. + options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_NEVER_INDENT_LINE_COMMENTS_ON_FIRST_COLUMN, + DefaultCodeFormatterConstants.FALSE); + CodeFormatter formatter = ToolFactory.createCodeFormatter(options); + code = document.get(); + TextEdit[] formatEdits = formatter.format(CodeFormatter.K_TRANSLATION_UNIT, code, + regions, TextUtilities.getDefaultLineDelimiter(document)); + + TextEdit combinedFormatEdit = new MultiTextEdit(); + for (TextEdit formatEdit : formatEdits) { + combinedFormatEdit = TextEditUtil.merge(combinedFormatEdit, formatEdit); + } + formatEdits = TextEditUtil.flatten(combinedFormatEdit).removeChildren(); + + MultiTextEdit result = new MultiTextEdit(); + int delta = 0; + TextEdit edit1 = null; + TextEdit edit2 = null; + int i = 0; + int j = 0; + while (true) { + if (edit1 == null && i < edits.length) + edit1 = edits[i++]; + if (edit2 == null && j < formatEdits.length) + edit2 = formatEdits[j++]; + if (edit1 == null) { + if (edit2 == null) + break; + edit2.moveTree(-delta); + result.addChild(edit2); + edit2 = null; + } else if (edit2 == null) { + delta += TextEditUtil.delta(edit1); + result.addChild(edit1); + edit1 = null; + } else { + if (edit2.getExclusiveEnd() - delta <= edit1.getOffset()) { + edit2.moveTree(-delta); + result.addChild(edit2); + edit2 = null; + } else { + TextEdit piece = clippedEdit(edit2, new Region(-1, edit1.getOffset() + delta)); + if (piece != null) { + piece.moveTree(-delta); + result.addChild(piece); + } + int d = TextEditUtil.delta(edit1); + Region region = new Region(edit1.getOffset() + delta, edit1.getLength() + d); + int end = endOffset(region); + MultiTextEdit format = new MultiTextEdit(); + while ((piece = clippedEdit(edit2, region)) != null) { + format.addChild(piece); + // The warning "The variable edit2 may be null at this location" is bogus. + // Make the compiler happy: + if (edit2 != null) { + if (edit2.getExclusiveEnd() >= end || j >= formatEdits.length) { + break; + } + } + edit2 = formatEdits[j++]; + } + if (format.hasChildren()) { + format.moveTree(-delta); + edit1 = applyEdit(format, edit1); + } + delta += d; + result.addChild(edit1); + edit1 = null; + + edit2 = clippedEdit(edit2, new Region(end, Integer.MAX_VALUE - end)); + } + } + } + return result; + } catch (MalformedTreeException e) { + CCorePlugin.log(e); + } catch (BadLocationException e) { + CCorePlugin.log(e); + } + return rootEdit; + } + + private static TextEdit clippedEdit(TextEdit edit, IRegion region) { + if ((edit.getOffset() < region.getOffset() && edit.getExclusiveEnd() <= region.getOffset()) || + edit.getOffset() >= endOffset(region)) { + return null; + } + int offset = Math.max(edit.getOffset(), region.getOffset()); + int length = Math.min(endOffset(edit), endOffset(region)) - offset; + if (offset == edit.getOffset() && length == edit.getLength()) { + // InsertEdit always satisfies the above condition. + return edit; + } + if (edit instanceof DeleteEdit) { + return new DeleteEdit(offset, length); + } if (edit instanceof ReplaceEdit) { + String replacement = ((ReplaceEdit) edit).getText(); + int start = Math.max(offset - edit.getOffset(), 0); + int end = Math.min(endOffset(region) - offset, replacement.length()); + if (end <= start) { + return new DeleteEdit(offset, length); + } + return new ReplaceEdit(offset, length, replacement.substring(start, end)); + } else { + throw new IllegalArgumentException("Unexpected edit type: " + edit.getClass().getSimpleName()); //$NON-NLS-1$ + } + } + + /** + * Applies source edit to the target one and returns the combined edit. + */ + private static TextEdit applyEdit(TextEdit source, TextEdit target) + throws MalformedTreeException, BadLocationException { + source.moveTree(-target.getOffset()); + String text; + if (target instanceof InsertEdit) { + text = ((InsertEdit) target).getText(); + } else if (target instanceof ReplaceEdit) { + text = ((ReplaceEdit) target).getText(); + } else { + text = ""; //$NON-NLS-1$ + } + + IDocument document = new Document(text); + source.apply(document, TextEdit.NONE); + text = document.get(); + if (target.getLength() == 0) { + return new InsertEdit(target.getOffset(), text); + } else { + return new ReplaceEdit(target.getOffset(), target.getLength(), text); + } + } + + private static int endOffset(TextEdit edit) { + return edit.getOffset() + edit.getLength(); + } + + private static int endOffset(IRegion region) { + return region.getOffset() + region.getLength(); + } +} diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/scanner/SimpleScanner.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/scanner/SimpleScanner.java index e4517d1523a..448d0f2d5c2 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/scanner/SimpleScanner.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/scanner/SimpleScanner.java @@ -580,7 +580,7 @@ public class SimpleScanner { c = getChar(); switch (c) { case '/': { - matchSinglelineComment(); + matchSinglelineComment(true); return newToken(Token.tLINECOMMENT); } case '*': { @@ -749,7 +749,7 @@ public class SimpleScanner { ungetChar(c); result= newPreprocessorToken(); } else { - matchSinglelineComment(); + matchSinglelineComment(false); result= newToken(Token.tLINECOMMENT); } fPreprocessorToken= 0; @@ -804,7 +804,7 @@ public class SimpleScanner { int next = getChar(); if (next == '/') { // single line comment - matchSinglelineComment(); + matchSinglelineComment(false); break; } else if (next == '*') { // multiline comment @@ -828,12 +828,12 @@ public class SimpleScanner { } } - private void matchSinglelineComment() { + private void matchSinglelineComment(boolean includeNewline) { int c = getChar(); while (c != '\n' && c != EOFCHAR) { c = getChar(); } - if (c == EOFCHAR) { + if (c == EOFCHAR || !includeNewline) { ungetChar(c); } } @@ -852,10 +852,11 @@ public class SimpleScanner { state = 1; break; case 1 : - if (c == '/') + if (c == '/') { state = 2; - else if (c != '*') + } else if (c != '*') { state = 0; + } break; } c = getChar(); diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/includes/BindingClassifierTest.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/includes/BindingClassifierTest.java index 1f1701912d2..9f1ee71eea1 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/includes/BindingClassifierTest.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/includes/BindingClassifierTest.java @@ -199,7 +199,7 @@ public class BindingClassifierTest extends OneSourceMultipleHeadersTestCase { public void testFunctionCallWithPointerParameter_1() throws Exception { getPreferenceStore().setValue(PreferenceConstants.FORWARD_DECLARE_FUNCTIONS, true); assertDefined(); - assertDeclared("f", "g"); + assertDeclared("f", "A", "g"); } // typedef int A; @@ -210,7 +210,7 @@ public class BindingClassifierTest extends OneSourceMultipleHeadersTestCase { // } public void testFunctionCallWithPointerParameter_2() throws Exception { getPreferenceStore().setValue(PreferenceConstants.FORWARD_DECLARE_FUNCTIONS, true); - assertDefined(); + assertDefined("A"); assertDeclared("f"); } @@ -224,7 +224,7 @@ public class BindingClassifierTest extends OneSourceMultipleHeadersTestCase { public void testFunctionCallWithReferenceParameter() throws Exception { getPreferenceStore().setValue(PreferenceConstants.FORWARD_DECLARE_FUNCTIONS, true); assertDefined(); - assertDeclared("f", "g"); + assertDeclared("f", "A", "g"); } // struct A { @@ -240,7 +240,7 @@ public class BindingClassifierTest extends OneSourceMultipleHeadersTestCase { // A header declaring the function is responsible for defining the parameter type that // provides constructor that can be used for implicit conversion. assertDefined(); - assertDeclared("f"); + assertDeclared("f", "A"); } // struct A {}; diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/includes/IncludeOrganizerTest.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/includes/IncludeOrganizerTest.java index 28d761f9f54..5b8cf2fb5c4 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/includes/IncludeOrganizerTest.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/includes/IncludeOrganizerTest.java @@ -11,7 +11,6 @@ package org.eclipse.cdt.ui.tests.refactoring.includes; import java.util.Collections; -import java.util.List; import junit.framework.Test; @@ -19,7 +18,6 @@ import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.text.edits.MultiTextEdit; -import org.eclipse.text.edits.TextEdit; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.ui.PreferenceConstants; @@ -74,14 +72,9 @@ public class IncludeOrganizerTest extends IncludesTestBase { private String organizeIncludes(ITranslationUnit tu) throws Exception { IHeaderChooser headerChooser = new FirstHeaderChooser(); IncludeOrganizer organizer = new IncludeOrganizer(tu, index, LINE_DELIMITER, headerChooser); - List edits = organizer.organizeIncludes(ast); + MultiTextEdit edit = organizer.organizeIncludes(ast); IDocument document = new Document(new String(tu.getContents())); - if (!edits.isEmpty()) { - // Apply text edits. - MultiTextEdit edit = new MultiTextEdit(); - edit.addChildren(edits.toArray(new TextEdit[edits.size()])); - edit.apply(document); - } + edit.apply(document); return document.get(); } @@ -443,4 +436,49 @@ public class IncludeOrganizerTest extends IncludesTestBase { public void testSymbolToDeclareIsDefinedInIncludedHeader() throws Exception { assertExpectedResults(); } + + //h1.h + //namespace ns3 { + //class C {}; + //namespace ns2 { + //class A {}; + //class B {}; + //namespace ns1 { + //C* f(const A& a, B* b) { return nullptr; } + //} // ns1 + //} // ns2 + //} // ns3 + + //source.cpp + //#include "h1.h" + //void test(ns3::ns2::A& a) { + // ns3::C* c = ns3::ns2::ns1::f(a, nullptr); + //} + //==================== + //namespace ns3 { + //class C; + //namespace ns2 { + //class A; + //class B; + //} /* namespace ns2 */ + //} /* namespace ns3 */ + //namespace ns3 { + //namespace ns2 { + //namespace ns1 { + //C * f(const A &a, B *b); + //} /* namespace ns1 */ + //} /* namespace ns2 */ + //} /* namespace ns3 */ + // + //void test(ns3::ns2::A& a) { + // ns3::C* c = ns3::ns2::ns1::f(a, nullptr); + //} + public void testForwardDeclarations() throws Exception { + // TODO(sprigogin): Move ns1 outside of other namespaces after IncludeOrganizer starts using ASTWriter. + IPreferenceStore preferenceStore = getPreferenceStore(); + preferenceStore.setValue(PreferenceConstants.INCLUDES_UNUSED_STATEMENTS_DISPOSITION, + UnusedStatementDisposition.REMOVE.toString()); + preferenceStore.setValue(PreferenceConstants.FORWARD_DECLARE_FUNCTIONS, true); + assertExpectedResults(); + } } \ No newline at end of file diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java index 30c6a2e7f34..971db070a25 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java @@ -818,6 +818,17 @@ public class CodeFormatterTest extends BaseUITestCase { assertFormatterResult(); } + //#include "header.h" // comment + // + //class C; + + //#include "header.h" // comment + // + //class C; + public void testPreserveBlankLineAfterInclude() throws Exception { + assertFormatterResult(); + } + //void f() { throw 42; } //void f() { diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/OrganizeIncludesAction.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/OrganizeIncludesAction.java index 6c8a5161a84..30fd09f1201 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/OrganizeIncludesAction.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/OrganizeIncludesAction.java @@ -11,9 +11,6 @@ *******************************************************************************/ package org.eclipse.cdt.internal.ui.editor; -import java.util.ArrayList; -import java.util.List; - import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; @@ -22,7 +19,6 @@ import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.text.edits.MalformedTreeException; import org.eclipse.text.edits.MultiTextEdit; -import org.eclipse.text.edits.TextEdit; import org.eclipse.text.undo.DocumentUndoManagerRegistry; import org.eclipse.text.undo.IDocumentUndoManager; import org.eclipse.ui.IEditorInput; @@ -70,7 +66,7 @@ public class OrganizeIncludesAction extends TextEditorAction { final IHeaderChooser headerChooser = new InteractiveHeaderChooser( CEditorMessages.OrganizeIncludes_label, editor.getSite().getShell()); final String lineDelimiter = getLineDelimiter(editor); - final List edits = new ArrayList(); + final MultiTextEdit[] holder = new MultiTextEdit[1]; SharedASTJob job = new SharedASTJob(CEditorMessages.OrganizeIncludes_action, tu) { @Override public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) throws CoreException { @@ -79,7 +75,7 @@ public class OrganizeIncludesAction extends TextEditorAction { try { index.acquireReadLock(); IncludeOrganizer organizer = new IncludeOrganizer(tu, index, lineDelimiter, headerChooser); - edits.addAll(organizer.organizeIncludes(ast)); + holder[0] = organizer.organizeIncludes(ast); return Status.OK_STATUS; } catch (InterruptedException e) { return Status.CANCEL_STATUS; @@ -90,10 +86,9 @@ public class OrganizeIncludesAction extends TextEditorAction { }; IStatus status = BusyCursorJobRunner.execute(job); if (status.isOK()) { - if (!edits.isEmpty()) { - // Apply text edits. - MultiTextEdit edit = new MultiTextEdit(); - edit.addChildren(edits.toArray(new TextEdit[edits.size()])); + MultiTextEdit edit = holder[0]; + if (edit.hasChildren()) { + // Apply the text edit. IEditorInput editorInput = editor.getEditorInput(); IDocument document = editor.getDocumentProvider().getDocument(editorInput); IDocumentUndoManager manager= DocumentUndoManagerRegistry.getDocumentUndoManager(document); diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/BindingClassifier.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/BindingClassifier.java index fb3fd5c600c..6e9dba71aba 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/BindingClassifier.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/BindingClassifier.java @@ -63,6 +63,7 @@ import org.eclipse.cdt.core.dom.ast.IASTStatement; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IASTUnaryExpression; import org.eclipse.cdt.core.dom.ast.IASTWhileStatement; +import org.eclipse.cdt.core.dom.ast.IBasicType; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.ICompositeType; import org.eclipse.cdt.core.dom.ast.IEnumeration; @@ -162,6 +163,7 @@ public class BindingClassifier { * comparing the declared parameters with the actual arguments. */ private void processFunctionParameters(IFunction function, IASTInitializerClause[] arguments) { + boolean functionIsDefined = fProcessedDefinedBindings.contains(function); IParameter[] parameters = function.getParameters(); for (int i = 0; i < parameters.length && i < arguments.length; i++) { IType parameterType = parameters[i].getType(); @@ -172,6 +174,9 @@ public class BindingClassifier { // A declaration is sufficient if the argument type matches the parameter type. // We don't need to provide a declaration of the parameter type since it is // a responsibility of the header declaring the function. + if (!functionIsDefined) { + declareType(parameterType); + } continue; } @@ -185,6 +190,8 @@ public class BindingClassifier { fAst.getDeclarationsInAST(function).length != 0 || !hasConvertingConstructor((ICPPClassType) parameterType, argument)) { defineTypeExceptTypedefOrNonFixedEnum(parameterType); + } else if (!functionIsDefined) { + declareType(parameterType); } } } @@ -247,7 +254,10 @@ public class BindingClassifier { if (!constructor.isExplicit()) { ICPPParameter[] parameters = constructor.getParameters(); if (parameters.length != 0 && CPPFunction.getRequiredArgumentCount(parameters) <= 1) { - IType type = getNestedType(parameters[0].getType(), REF | ALLCVQ); + IType type = parameters[0].getType(); + if (type instanceof IBasicType && ((IBasicType) type).getKind() == IBasicType.Kind.eVoid) + continue; + type = getNestedType(type, REF | ALLCVQ); if (!classType.isSameType(type)) return true; } @@ -341,6 +351,12 @@ public class BindingClassifier { return bindings; } + private void declareType(IType type) { + IBinding binding = getTypeBinding(type); + if (binding != null) + declareBinding(binding); + } + /** * Adds the given binding to the list of bindings which have to be forward declared. * diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeCreationContext.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeCreationContext.java index 7660d460d96..cb8738e268e 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeCreationContext.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeCreationContext.java @@ -37,6 +37,7 @@ public class IncludeCreationContext extends InclusionContext { private final Set fHeadersToInclude; private final Set fHeadersAlreadyIncluded; private final Set fHeadersIncludedPreviously; + private String fSourceContents; public IncludeCreationContext(ITranslationUnit tu, IIndex index) { super(tu); @@ -46,8 +47,11 @@ public class IncludeCreationContext extends InclusionContext { fHeadersIncludedPreviously = new HashSet(); } - public char[] getSourceContents() { - return getTranslationUnit().getContents(); + public String getSourceContents() { + if (fSourceContents == null) { + fSourceContents = new String(getTranslationUnit().getContents()); + } + return fSourceContents; } public IIndex getIndex() { diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeCreator.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeCreator.java index 0e7ff78165a..8761bb53d86 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeCreator.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeCreator.java @@ -230,7 +230,7 @@ public class IncludeCreator { List usingDeclarations, IASTTranslationUnit ast, ITextSelection selection) { NodeCommentMap commentedNodeMap = ASTCommenter.getCommentedNodeMap(ast); - char[] contents = fContext.getSourceContents(); + String contents = fContext.getSourceContents(); IRegion includeRegion = IncludeOrganizer.getSafeIncludeReplacementRegion(contents, ast, commentedNodeMap); @@ -292,7 +292,7 @@ public class IncludeCreator { if (previousNode != null) { offset = ASTNodes.skipToNextLineAfterNode(contents, previousNode); flushEditBuffer(offset, text, rootEdit); - if (contents[offset - 1] != '\n') + if (contents.charAt(offset - 1) != '\n') text.append(fLineDelimiter); } if (include.getStyle().isBlankLineNeededAfter(previousInclude.getStyle(), preferences.includeStyles)) @@ -365,7 +365,7 @@ public class IncludeCreator { if (previousNode != null) { offset = ASTNodes.skipToNextLineAfterNode(contents, previousNode); flushEditBuffer(offset, text, rootEdit); - if (contents[offset - 1] != '\n') + if (contents.charAt(offset - 1) != '\n') text.append(fLineDelimiter); } } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeOrganizer.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeOrganizer.java index 8105552b2b8..3e9879201f0 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeOrganizer.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeOrganizer.java @@ -37,8 +37,8 @@ import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.text.edits.DeleteEdit; import org.eclipse.text.edits.InsertEdit; +import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; -import org.eclipse.text.edits.TextEdit; import com.ibm.icu.text.Collator; @@ -80,6 +80,7 @@ import org.eclipse.cdt.core.parser.Keywords; import org.eclipse.cdt.core.parser.util.CharArrayIntMap; import org.eclipse.cdt.core.parser.util.CharArrayUtils; import org.eclipse.cdt.ui.CUIPlugin; +import org.eclipse.cdt.ui.CodeGeneration; import org.eclipse.cdt.utils.PathUtil; import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.ASTCommenter; @@ -92,6 +93,7 @@ import org.eclipse.cdt.internal.core.parser.scanner.IncludeGuardDetection; import org.eclipse.cdt.internal.core.parser.scanner.Lexer.LexerOptions; import org.eclipse.cdt.internal.corext.codemanipulation.IncludeInfo; import org.eclipse.cdt.internal.corext.codemanipulation.StyledInclude; +import org.eclipse.cdt.internal.formatter.ChangeFormatter; /** * Organizes the include directives and forward declarations of a source or header file. @@ -129,6 +131,54 @@ public class IncludeOrganizer { } } + private static enum DeclarationType { TYPE, FUNCTION, VARIABLE, NAMESPACE } + + private static class ForwardDeclarationNode implements Comparable { + final String name; + final String declaration; + final DeclarationType type; + final List children; + + /** + * Creates a namespace node. + */ + ForwardDeclarationNode(String name) { + this.name = name; + this.declaration = null; + this.type = DeclarationType.NAMESPACE; + this.children = new ArrayList(); + } + + /** + * Creates a declaration node. + */ + ForwardDeclarationNode(String name, String declaration, DeclarationType type) { + this.name = name; + this.declaration = declaration; + this.type = type; + this.children = null; + } + + ForwardDeclarationNode findOrAddChild(ForwardDeclarationNode node) { + int i = Collections.binarySearch(children, node); + if (i >= 0) + return children.get(i); + children.add(-(i + 1), node); + return node; + } + + @Override + public int compareTo(ForwardDeclarationNode other) { + int c = type.ordinal() - other.type.ordinal(); + if (c != 0) + return c; + c = COLLATOR.compare(name, other.name); + if (declaration == null || c != 0) + return c; + return COLLATOR.compare(declaration, other.declaration); + } + } + private final IHeaderChooser fHeaderChooser; private final IncludeCreationContext fContext; private final String fLineDelimiter; @@ -145,7 +195,7 @@ public class IncludeOrganizer { * @param ast The AST translation unit to process. * @throws CoreException */ - public List organizeIncludes(IASTTranslationUnit ast) throws CoreException { + public MultiTextEdit organizeIncludes(IASTTranslationUnit ast) throws CoreException { // Process the given translation unit with the inclusion resolver. BindingClassifier bindingClassifier = new BindingClassifier(fContext); bindingClassifier.classifyNodeContents(ast); @@ -195,7 +245,7 @@ public class IncludeOrganizer { IncludePreferences preferences = fContext.getPreferences(); boolean allowReordering = preferences.allowReordering || existingIncludes.length == 0; - List edits = new ArrayList(); + MultiTextEdit rootEdit = new MultiTextEdit(); @SuppressWarnings("unchecked") List[] groupedPrototypes = @@ -218,10 +268,10 @@ public class IncludeOrganizer { && isContainedInRegion(prototype.getExistingInclude(), includeReplacementRegion)) { switch (preferences.unusedStatementsDisposition) { case REMOVE: - createDelete(prototype.getExistingInclude(), edits); + createDelete(prototype.getExistingInclude(), rootEdit); break; case COMMENT_OUT: - createCommentOut(prototype.getExistingInclude(), edits); + createCommentOut(prototype.getExistingInclude(), rootEdit); break; case KEEP: break; @@ -262,13 +312,6 @@ public class IncludeOrganizer { } } - // Stores the forward declarations for composite types and enumerations as text. - List typeForwardDeclarations = new ArrayList(); - // Stores the forward declarations for C-style functions as text. - List functionForwardDeclarations = new ArrayList(); - - createForwardDeclarations(ast, bindingClassifier, typeForwardDeclarations, functionForwardDeclarations); - // Create the source code to insert into the editor. StringBuilder buf = new StringBuilder(); @@ -277,50 +320,41 @@ public class IncludeOrganizer { buf.append(fLineDelimiter); } - if (buf.length() != 0 && !typeForwardDeclarations.isEmpty()) - buf.append(fLineDelimiter); - for (String declaration : typeForwardDeclarations) { - buf.append(declaration); - buf.append(fLineDelimiter); - } - - if (buf.length() != 0 && !functionForwardDeclarations.isEmpty()) - buf.append(fLineDelimiter); - for (String declaration : functionForwardDeclarations) { - buf.append(declaration); - buf.append(fLineDelimiter); - } - int offset = includeReplacementRegion.getOffset(); int length = includeReplacementRegion.getLength(); if (allowReordering) { if (buf.length() != 0) { if (offset != 0 && !TextUtil.isPreviousLineBlank(fContext.getSourceContents(), offset)) buf.insert(0, fLineDelimiter); // Blank line before. - if (!isBlankLineOrEndOfFile(offset + length)) - buf.append(fLineDelimiter); // Blank line after. } String text = buf.toString(); // TODO(sprigogin): Add a diff algorithm and produce narrower replacements. - if (!CharArrayUtils.equals(fContext.getSourceContents(), offset, length, text)) { - edits.add(new ReplaceEdit(offset, length, text)); + if (text.length() != length || + !fContext.getSourceContents().regionMatches(offset, text, 0, length)) { + rootEdit.addChild(new ReplaceEdit(offset, length, text)); } } else if (buf.length() != 0) { offset += length; - if (!isBlankLineOrEndOfFile(offset)) - buf.append(fLineDelimiter); // Blank line after. - edits.add(new InsertEdit(offset, buf.toString())); + rootEdit.addChild(new InsertEdit(offset, buf.toString())); } - return edits; + createForwardDeclarations(ast, bindingClassifier, + includeReplacementRegion.getOffset() + includeReplacementRegion.getLength(), + buf.length() != 0, rootEdit); + + return ChangeFormatter.formatChangedCode(new String(fContext.getSourceContents()), fContext.getTranslationUnit(), rootEdit); } /** * Creates forward declarations by examining the list of bindings which have to be declared. + * @param pendingBlankLine */ private void createForwardDeclarations(IASTTranslationUnit ast, BindingClassifier classifier, - List forwardDeclarations, List functionForwardDeclarations) throws CoreException { + int offset, boolean pendingBlankLine, MultiTextEdit rootEdit) throws CoreException { + ForwardDeclarationNode typeDeclarationsRoot = new ForwardDeclarationNode(""); //$NON-NLS-1$ + ForwardDeclarationNode nonTypeDeclarationsRoot = new ForwardDeclarationNode(""); //$NON-NLS-1$ + IIndexFileSet reachableHeaders = ast.getIndexFileSet(); Set bindings = removeBindingsDefinedInIncludedHeaders(ast, classifier.getBindingsToDeclare(), reachableHeaders); @@ -328,32 +362,10 @@ public class IncludeOrganizer { // Create the text of the forward declaration of this binding. StringBuilder declarationText = new StringBuilder(); - // Consider the namespace(s) of the binding. - List scopeNames = new ArrayList(); - try { - IScope scope = binding.getScope(); - while (scope != null && scope.getKind() == EScopeKind.eNamespace) { - IName scopeName = scope.getScopeName(); - if (scopeName != null) { - scopeNames.add(scopeName); - } - scope = scope.getParent(); - } - } catch (DOMException e) { - } - - Collections.reverse(scopeNames); - for (IName scopeName : scopeNames) { - declarationText.append("namespace "); //$NON-NLS-1$ - declarationText.append(scopeName.toString()); - declarationText.append(" { "); //$NON-NLS-1$ - } - - // Initialize the list which should be used to store the declaration. - List forwardDeclarationListToUse = forwardDeclarations; - + DeclarationType declarationType; // Check the type of the binding and create a corresponding forward declaration text. if (binding instanceof ICompositeType) { + declarationType = DeclarationType.TYPE; // Forward declare a composite type. ICompositeType compositeType = (ICompositeType) binding; @@ -404,16 +416,19 @@ public class IncludeOrganizer { // Append the semicolon. declarationText.append(';'); } else if (binding instanceof IEnumeration) { + declarationType = DeclarationType.TYPE; // Forward declare an enumeration class (C++11 syntax). declarationText.append("enum class "); //$NON-NLS-1$ declarationText.append(binding.getName()); declarationText.append(';'); } else if (binding instanceof IFunction && !(binding instanceof ICPPMethod)) { + declarationType = DeclarationType.FUNCTION; // Forward declare a C-style function. IFunction function = (IFunction) binding; // Append return type and function name. IFunctionType functionType = function.getType(); + // TODO(sprigogin): Switch to ASTWriter since ASTTypeUtil doesn't properly handle namespaces. declarationText.append(ASTTypeUtil.getType(functionType.getReturnType(), false)); declarationText.append(' '); declarationText.append(function.getName()); @@ -436,10 +451,8 @@ public class IncludeOrganizer { } declarationText.append(");"); //$NON-NLS-1$ - - // Add this forward declaration to the separate function forward declaration list. - forwardDeclarationListToUse = functionForwardDeclarations; } else if (binding instanceof IVariable) { + declarationType = DeclarationType.VARIABLE; IVariable variable = (IVariable) binding; IType variableType = variable.getType(); declarationText.append("extern "); //$NON-NLS-1$ @@ -451,41 +464,95 @@ public class IncludeOrganizer { CUIPlugin.log(new IllegalArgumentException( "Unexpected type of binding " + binding.getName() + //$NON-NLS-1$ " - " + binding.getClass().getSimpleName())); //$NON-NLS-1$ + continue; } - // Append the closing curly brackets from the namespaces (if any). - for (int i = 0; i < scopeNames.size(); i++) { - declarationText.append(" }"); //$NON-NLS-1$ + // Consider the namespace(s) of the binding. + List namespaces = new ArrayList(); + try { + IScope scope = binding.getScope(); + while (scope != null && scope.getKind() == EScopeKind.eNamespace) { + IName scopeName = scope.getScopeName(); + if (scopeName != null) { + namespaces.add(new String(scopeName.getSimpleID())); + } + scope = scope.getParent(); + } + } catch (DOMException e) { } - // Add the forward declaration to the corresponding list. - forwardDeclarationListToUse.add(declarationText.toString()); + ForwardDeclarationNode parentNode = declarationType == DeclarationType.TYPE ? + typeDeclarationsRoot : nonTypeDeclarationsRoot; + + Collections.reverse(namespaces); + for (String ns : namespaces) { + ForwardDeclarationNode node = new ForwardDeclarationNode(ns); + parentNode = parentNode.findOrAddChild(node); + } + + ForwardDeclarationNode node = + new ForwardDeclarationNode(binding.getName(), declarationText.toString(), declarationType); + parentNode.findOrAddChild(node); } - Collections.sort(forwardDeclarations, COLLATOR); - Collections.sort(functionForwardDeclarations, COLLATOR); + StringBuilder buf = new StringBuilder(); + + for (ForwardDeclarationNode node : typeDeclarationsRoot.children) { + if (pendingBlankLine) { + buf.append(fLineDelimiter); + pendingBlankLine = false; + } + printNode(node, buf); + } + + for (ForwardDeclarationNode node : nonTypeDeclarationsRoot.children) { + if (pendingBlankLine) { + buf.append(fLineDelimiter); + pendingBlankLine = false; + } + printNode(node, buf); + } + + if ((pendingBlankLine || buf.length() != 0) && !isBlankLineOrEndOfFile(offset)) + buf.append(fLineDelimiter); + + if (buf.length() != 0) + rootEdit.addChild(new InsertEdit(offset, buf.toString())); } - private void createCommentOut(IASTPreprocessorIncludeStatement include, List edits) { + private void printNode(ForwardDeclarationNode node, StringBuilder buf) throws CoreException { + if (node.declaration == null) { + buf.append(CodeGeneration.getNamespaceBeginContent(fContext.getTranslationUnit(), node.name, fLineDelimiter)); + for (ForwardDeclarationNode child : node.children) { + printNode(child, buf); + } + buf.append(CodeGeneration.getNamespaceEndContent(fContext.getTranslationUnit(), node.name, fLineDelimiter)); + } else { + buf.append(node.declaration); + } + buf.append(fLineDelimiter); + } + + private void createCommentOut(IASTPreprocessorIncludeStatement include, MultiTextEdit rootEdit) { IASTFileLocation location = include.getFileLocation(); int offset = location.getNodeOffset(); if (fContext.getTranslationUnit().isCXXLanguage()) { offset = TextUtil.getLineStart(fContext.getSourceContents(), offset); - edits.add(new InsertEdit(offset, "//")); //$NON-NLS-1$ + rootEdit.addChild(new InsertEdit(offset, "//")); //$NON-NLS-1$ } else { - edits.add(new InsertEdit(offset, "/*")); //$NON-NLS-1$ + rootEdit.addChild(new InsertEdit(offset, "/*")); //$NON-NLS-1$ int endOffset = offset + location.getNodeLength(); - edits.add(new InsertEdit(endOffset, "*/")); //$NON-NLS-1$ + rootEdit.addChild(new InsertEdit(endOffset, "*/")); //$NON-NLS-1$ } } - private void createDelete(IASTPreprocessorIncludeStatement include, List edits) { + private void createDelete(IASTPreprocessorIncludeStatement include, MultiTextEdit rootEdit) { IASTFileLocation location = include.getFileLocation(); int offset = location.getNodeOffset(); int endOffset = offset + location.getNodeLength(); offset = TextUtil.getLineStart(fContext.getSourceContents(), offset); endOffset = TextUtil.skipToNextLine(fContext.getSourceContents(), endOffset); - edits.add(new DeleteEdit(offset, endOffset - offset)); + rootEdit.addChild(new DeleteEdit(offset, endOffset - offset)); } private void updateIncludePrototypes(Map includePrototypes, @@ -498,7 +565,7 @@ public class IncludeOrganizer { } } - static IRegion getSafeIncludeReplacementRegion(char[] contents, IASTTranslationUnit ast, + static IRegion getSafeIncludeReplacementRegion(String contents, IASTTranslationUnit ast, NodeCommentMap commentMap) { int maxSafeOffset = ast.getFileLocation().getNodeLength(); IASTDeclaration[] declarations = ast.getDeclarations(true); @@ -592,9 +659,9 @@ public class IncludeOrganizer { * {@code offset} and the end of the line. */ private boolean isBlankLineOrEndOfFile(int offset) { - char[] contents = fContext.getSourceContents(); - while (offset < contents.length) { - char c = contents[offset++]; + String contents = fContext.getSourceContents(); + while (offset < contents.length()) { + char c = contents.charAt(offset++); if (c == '\n') return true; if (!Character.isWhitespace(c)) @@ -610,20 +677,20 @@ public class IncludeOrganizer { private String getPrecedingWhitespace(IASTNode node) { int offset = getNodeOffset(node); if (offset >= 0) { - char[] contents = fContext.getSourceContents(); + String contents = fContext.getSourceContents(); int i = offset; while (--i >= 0) { - char c = contents[i]; + char c = contents.charAt(i); if (c == '\n' || !Character.isWhitespace(c)) break; } i++; - return new String(contents, i, offset - i); + return contents.substring(i, offset); } return ""; //$NON-NLS-1$ } - private static int skipStandaloneCommentBlock(char[] contents, int offset, int endOffset, + private static int skipStandaloneCommentBlock(String contents, int offset, int endOffset, IASTComment[] comments, NodeCommentMap commentMap) { Map inverseLeadingMap = new HashMap(); for (Map.Entry> entry : commentMap.getLeadingMap().entrySet()) {