From d962e2eee70bad6d141f8ac5441edaa94ca048ec Mon Sep 17 00:00:00 2001 From: Sergey Prigogin Date: Mon, 16 Jun 2014 16:27:00 -0700 Subject: [PATCH] Automatic update of include statements and include guards when header files are renamed or moved. --- .../org.eclipse.cdt.core/META-INF/MANIFEST.MF | 2 +- .../cdt/internal/core/model/WorkingCopy.java | 3 +- .../core/dom/parser/ASTTranslationUnit.java | 48 -- .../core/dom/rewrite/util/ASTNodes.java | 68 ++- .../core/dom/rewrite/util/TextUtil.java | 30 ++ core/org.eclipse.cdt.core/pom.xml | 2 +- .../refactoring/RefactoringTestBase.java | 46 +- .../ui/tests/refactoring/TestSourceFile.java | 22 +- .../includes/IncludeOrganizerTest.java | 4 +- .../refactoring/rename/RenameMacroTests.java | 7 +- .../RenameMoveHeaderRefactoringTest.java | 252 +++++++++ core/org.eclipse.cdt.ui/plugin.properties | 4 +- core/org.eclipse.cdt.ui/plugin.xml | 62 ++- .../codemanipulation/InclusionContext.java | 57 +- .../corext/codemanipulation/StubUtility.java | 2 +- .../codemanipulation/StyledInclude.java | 2 +- .../internal/ui/editor/AddIncludeAction.java | 16 +- .../ui/editor/OrganizeIncludesAction.java | 16 +- .../includes/IncludeCreationContext.java | 8 - .../refactoring/includes/IncludeCreator.java | 42 +- .../includes/IncludeOrganizer.java | 205 +------- .../ui/refactoring/includes/IncludeUtil.java | 288 +++++++++- .../rename/HeaderFileMoveParticipant.java | 116 +++++ .../rename/HeaderFileReferenceAdjuster.java | 491 ++++++++++++++++++ .../rename/HeaderFileRenameParticipant.java | 127 +++++ .../ui/refactoring/rename/RenameMessages.java | 6 +- .../rename/RenameMessages.properties | 8 +- ...ava => SourceFolderRenameParticipant.java} | 17 +- .../eclipse/cdt/ui/PreferenceConstants.java | 2 +- .../cdt/ui/refactoring/CTextFileChange.java | 6 +- 30 files changed, 1580 insertions(+), 379 deletions(-) create mode 100644 core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/rename/RenameMoveHeaderRefactoringTest.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileMoveParticipant.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileReferenceAdjuster.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileRenameParticipant.java rename core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/{RenameSourceFolder.java => SourceFolderRenameParticipant.java} (79%) diff --git a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF index a07fce70c98..19f857dabf7 100644 --- a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF +++ b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.cdt.core; singleton:=true -Bundle-Version: 5.7.0.qualifier +Bundle-Version: 5.8.0.qualifier Bundle-Activator: org.eclipse.cdt.core.CCorePlugin Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/model/WorkingCopy.java b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/model/WorkingCopy.java index 4c22330bb6c..f0a306046bf 100644 --- a/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/model/WorkingCopy.java +++ b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/model/WorkingCopy.java @@ -77,7 +77,8 @@ public class WorkingCopy extends TranslationUnit implements IWorkingCopy { CommitWorkingCopyOperation op= new CommitWorkingCopyOperation(this, force); op.runOperation(monitor); } else { - String contents = this.getSource(); + IBuffer buffer = getBuffer(); + String contents = buffer.getContents(); if (contents == null) return; diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ASTTranslationUnit.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ASTTranslationUnit.java index f89158eec9a..2f14cdc2cd8 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ASTTranslationUnit.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ASTTranslationUnit.java @@ -502,54 +502,6 @@ public abstract class ASTTranslationUnit extends ASTNode implements IASTTranslat return fSizeofCalculator; } - /** - * Returns the offset of the given node, or -1 if the node is not part of the translation - * unit file or doesn't have a file-location. - * @see IASTNode#getFileLocation() - */ - public static int getNodeOffset(IASTNode node) { - if (!node.isPartOfTranslationUnitFile()) - return -1; - IASTFileLocation nodeLocation = node.getFileLocation(); - return nodeLocation != null ? nodeLocation.getNodeOffset() : -1; - } - - /** - * Returns the end offset of the given node, or -1 if the node is not part of the translation - * unit file or doesn't have a file-location. - * @see IASTNode#getFileLocation() - */ - public static int getNodeEndOffset(IASTNode node) { - if (!node.isPartOfTranslationUnitFile()) - return -1; - IASTFileLocation nodeLocation = node.getFileLocation(); - return nodeLocation != null ? nodeLocation.getNodeOffset() + nodeLocation.getNodeLength() : -1; - } - - /** - * Returns the 1-based starting line number of the given node, or 0 if the node is not part of - * the translation unit file or doesn't have a file-location. - * @see IASTNode#getFileLocation() - */ - public static int getStartingLineNumber(IASTNode node) { - if (!node.isPartOfTranslationUnitFile()) - return 0; - IASTFileLocation nodeLocation = node.getFileLocation(); - return nodeLocation != null ? nodeLocation.getStartingLineNumber() : 0; - } - - /** - * Returns the 1-based ending line number of the given node, or 0 if the node is not part of - * the translation unit file or doesn't have a file-location. - * @see IASTNode#getFileLocation() - */ - public static int getEndingLineNumber(IASTNode node) { - if (!node.isPartOfTranslationUnitFile()) - return 0; - IASTFileLocation nodeLocation = node.getFileLocation(); - return nodeLocation != null ? nodeLocation.getEndingLineNumber() : 0; - } - @Override public boolean hasNodesOmitted() { return fNodesOmitted; 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 c4785d1668f..e9dad3ea7c4 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012 Google, Inc and others. + * Copyright (c) 2012, 2014 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 @@ -10,8 +10,6 @@ *******************************************************************************/ package org.eclipse.cdt.internal.core.dom.rewrite.util; -import static org.eclipse.cdt.internal.core.dom.parser.ASTTranslationUnit.getNodeEndOffset; - import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTNode; @@ -24,18 +22,51 @@ public class ASTNodes { } /** - * Returns the offset of an AST node. + * Returns the offset of the given node, or -1 if the node is not part of the translation + * unit file or doesn't have a file-location. + * @see IASTNode#getFileLocation() */ public static int offset(IASTNode node) { - return node.getFileLocation().getNodeOffset(); + if (!node.isPartOfTranslationUnitFile()) + return -1; + IASTFileLocation nodeLocation = node.getFileLocation(); + return nodeLocation != null ? nodeLocation.getNodeOffset() : -1; } /** - * Returns the exclusive end offset of an AST node. + * Returns the end offset of the given node, or -1 if the node is not part of the translation + * unit file or doesn't have a file-location. + * @see IASTNode#getFileLocation() */ public static int endOffset(IASTNode node) { - IASTFileLocation location = node.getFileLocation(); - return location.getNodeOffset() + location.getNodeLength(); + if (!node.isPartOfTranslationUnitFile()) + return -1; + IASTFileLocation nodeLocation = node.getFileLocation(); + return nodeLocation != null ? nodeLocation.getNodeOffset() + nodeLocation.getNodeLength() : -1; + } + + /** + * Returns the 1-based starting line number of the given node, or 0 if the node is not part of + * the translation unit file or doesn't have a file-location. + * @see IASTNode#getFileLocation() + */ + public static int getStartingLineNumber(IASTNode node) { + if (!node.isPartOfTranslationUnitFile()) + return 0; + IASTFileLocation nodeLocation = node.getFileLocation(); + return nodeLocation != null ? nodeLocation.getStartingLineNumber() : 0; + } + + /** + * Returns the 1-based ending line number of the given node, or 0 if the node is not part of + * the translation unit file or doesn't have a file-location. + * @see IASTNode#getFileLocation() + */ + public static int getEndingLineNumber(IASTNode node) { + if (!node.isPartOfTranslationUnitFile()) + return 0; + IASTFileLocation nodeLocation = node.getFileLocation(); + return nodeLocation != null ? nodeLocation.getEndingLineNumber() : 0; } /** @@ -43,6 +74,25 @@ public class ASTNodes { * offset if there is no line delimiter after the node. */ public static int skipToNextLineAfterNode(String text, IASTNode node) { - return TextUtil.skipToNextLine(text, getNodeEndOffset(node)); + return TextUtil.skipToNextLine(text, endOffset(node)); + } + + /** + * Returns the whitespace preceding the given node. The newline character in not considered + * whitespace for the purpose of this method. + */ + public static String getPrecedingWhitespaceInLine(String text, IASTNode node) { + int offset = offset(node); + if (offset >= 0) { + int i = offset; + while (--i >= 0) { + char c = text.charAt(i); + if (c == '\n' || !Character.isWhitespace(c)) + break; + } + i++; + return text.substring(i, offset); + } + return ""; //$NON-NLS-1$ } } 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 5eadb0cab73..7a60c21a2dc 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 @@ -40,6 +40,17 @@ public class TextUtil { return offset + 1; } + /** + * Returns the offset of the beginning of the line before the one containing the given offset, + * or the beginning of the line containing the given offset if it is first in the text. + */ + public static int getPreviousLineStart(String text, int offset) { + offset = getLineStart(text, offset); + if (offset != 0) + offset = getLineStart(text, offset); + return offset; + } + /** * Returns {@code true} if the line corresponding to the given {@code offset} does not contain * non-whitespace characters. @@ -80,4 +91,23 @@ public class TextUtil { } return true; } + + /** + * Returns the beginning offset of the first blank line contained between the two given offsets. + * Returns -1, if not found. + */ + public static int findBlankLine(String text, int startOffset, int endOffset) { + int blankOffset = startOffset == 0 || text.charAt(startOffset - 1) == '\n' ? startOffset : -1; + while (startOffset < endOffset) { + char c = text.charAt(startOffset++); + if (c == '\n') { + if (blankOffset >= 0) + return blankOffset; + blankOffset = startOffset; + } else if (!Character.isWhitespace(c)) { + blankOffset = -1; + } + } + return -1; + } } diff --git a/core/org.eclipse.cdt.core/pom.xml b/core/org.eclipse.cdt.core/pom.xml index ad938e62449..acc289e8631 100644 --- a/core/org.eclipse.cdt.core/pom.xml +++ b/core/org.eclipse.cdt.core/pom.xml @@ -11,7 +11,7 @@ ../../pom.xml - 5.7.0-SNAPSHOT + 5.8.0-SNAPSHOT org.eclipse.cdt.core eclipse-plugin diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/RefactoringTestBase.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/RefactoringTestBase.java index 70109e01c1b..d7583cc6c8b 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/RefactoringTestBase.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/RefactoringTestBase.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2013 Google, Inc and others. + * Copyright (c) 2012, 2014 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 @@ -17,9 +17,11 @@ import java.io.StringReader; import java.net.URI; import java.util.LinkedHashSet; import java.util.Set; +import java.util.regex.Pattern; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Path; @@ -42,6 +44,7 @@ import org.eclipse.cdt.core.model.CoreModel; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.core.testplugin.CProjectHelper; +import org.eclipse.cdt.core.testplugin.TestScannerProvider; import org.eclipse.cdt.core.testplugin.util.BaseTestCase; import org.eclipse.cdt.core.testplugin.util.TestSourceReader; import org.eclipse.cdt.ui.CUIPlugin; @@ -55,6 +58,7 @@ import org.eclipse.cdt.internal.ui.refactoring.CRefactoringContext; * Common base for refactoring tests. */ public abstract class RefactoringTestBase extends BaseTestCase { + private static final Pattern FILENAME_PATTERN = Pattern.compile("^(\\w+/)*\\w+\\.\\w+$"); /** Allows empty files to be created during test setup. */ protected boolean createEmptyFiles = true; /** See {@link PreferenceConstants.CLASS_MEMBER_ASCENDING_VISIBILITY_ORDER} */ @@ -67,7 +71,7 @@ public abstract class RefactoringTestBase extends BaseTestCase { private boolean cpp = true; private ICProject cproject; - private final Set testFiles = new LinkedHashSet(); + private final Set testFiles = new LinkedHashSet<>(); private TestSourceFile selectedFile; private TextSelection selection; private TestSourceFile historyScript; @@ -87,22 +91,41 @@ public abstract class RefactoringTestBase extends BaseTestCase { cproject = cpp ? CProjectHelper.createCCProject(getName() + System.currentTimeMillis(), "bin", IPDOMManager.ID_NO_INDEXER) : CProjectHelper.createCProject(getName() + System.currentTimeMillis(), "bin", IPDOMManager.ID_NO_INDEXER); + TestScannerProvider.sLocalIncludes = new String[] { cproject.getProject().getLocation().toOSString() }; + Bundle bundle = CTestPlugin.getDefault().getBundle(); CharSequence[] testData = TestSourceReader.getContentsForTest(bundle, "ui", getClass(), getName(), 0); for (CharSequence contents : testData) { TestSourceFile testFile = null; + boolean firstAfterDelimiter = false; boolean expectedResult = false; BufferedReader reader = new BufferedReader(new StringReader(contents.toString())); String line; while ((line = reader.readLine()) != null) { String trimmedLine = line.trim(); if (testFile == null) { - assertTrue("Invalid file name \"" + trimmedLine + "\"", trimmedLine.matches("^(\\w+/)*\\w+\\.\\w+$")); - testFile = new TestSourceFile(trimmedLine); + if (isResultDelimiter(trimmedLine)) { + expectedResult = true; + firstAfterDelimiter = true; + testFile = new TestSourceFile(null); + } else { + assertTrue("Invalid file name \"" + trimmedLine + "\"", + FILENAME_PATTERN.matcher(trimmedLine).matches()); + testFile = new TestSourceFile(trimmedLine); + } } else if (isResultDelimiter(trimmedLine)) { expectedResult = true; + firstAfterDelimiter = true; } else if (expectedResult) { + if (firstAfterDelimiter) { + firstAfterDelimiter = false; + if (FILENAME_PATTERN.matcher(trimmedLine).matches()) { + testFile.setExpectedName(trimmedLine); + continue; + } + } + assertTrue(testFile.getExpectedName() != null); testFile.addLineToExpectedSource(line); } else { testFile.addLineToSource(line); @@ -110,7 +133,7 @@ public abstract class RefactoringTestBase extends BaseTestCase { } reader.close(); - if (createEmptyFiles || !testFile.getSource().isEmpty()) { + if (testFile.getName() != null && (createEmptyFiles || !testFile.getSource().isEmpty())) { TestSourceReader.createFile(cproject.getProject(), new Path(testFile.getName()), testFile.getSource()); } @@ -149,7 +172,7 @@ public abstract class RefactoringTestBase extends BaseTestCase { executeRefactoring(false); } - private void executeRefactoring(boolean expectedSuccess) throws Exception { + protected void executeRefactoring(boolean expectedSuccess) throws Exception { if (ascendingVisibilityOrder) { getPreferenceStore().setValue(PreferenceConstants.CLASS_MEMBER_ASCENDING_VISIBILITY_ORDER, ascendingVisibilityOrder); @@ -169,6 +192,11 @@ public abstract class RefactoringTestBase extends BaseTestCase { executeRefactoring(refactoring, context, true, expectedSuccess); } + protected void executeRefactoring(Refactoring refactoring, boolean expectedSuccess) + throws CoreException, Exception { + executeRefactoring(refactoring, null, false, expectedSuccess); + } + protected void executeRefactoring(Refactoring refactoring, RefactoringContext context, boolean withUserInput, boolean expectedSuccess) throws CoreException, Exception { try { @@ -232,6 +260,10 @@ public abstract class RefactoringTestBase extends BaseTestCase { return cproject; } + protected IProject getProject() { + return cproject.getProject(); + } + protected TestSourceFile getSelectedTestFile() { return selectedFile; } @@ -348,7 +380,7 @@ public abstract class RefactoringTestBase extends BaseTestCase { protected void compareFiles() throws Exception { for (TestSourceFile testFile : testFiles) { String expectedSource = testFile.getExpectedSource(); - IFile file = cproject.getProject().getFile(new Path(testFile.getName())); + IFile file = cproject.getProject().getFile(new Path(testFile.getExpectedName())); String actualSource = getFileContents(file); expectedSource= expectedSource.replace("\r\n", "\n"); actualSource= actualSource.replace("\r\n", "\n"); diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/TestSourceFile.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/TestSourceFile.java index d1c75ee130c..8d321db8f82 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/TestSourceFile.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/TestSourceFile.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2012 Institute for Software, HSR Hochschule fuer Technik + * Copyright (c) 2008, 2014 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 @@ -12,6 +12,7 @@ *******************************************************************************/ package org.eclipse.cdt.ui.tests.refactoring; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -27,6 +28,7 @@ public class TestSourceFile { private static final Pattern SELECTION_END = Pattern.compile("/\\*\\$\\$\\*/"); //$NON-NLS-1$ private final String name; + private String expectedName; private final StringBuilder source = new StringBuilder(); private final StringBuilder expectedSource = new StringBuilder(); private int selectionStart = -1; @@ -45,6 +47,20 @@ public class TestSourceFile { return source.toString(); } + /** + * Returns the expected name after refactoring. + */ + public String getExpectedName() { + return expectedName == null ? name : expectedName; + } + + public void setExpectedName(String name) { + expectedName = name; + } + + /** + * Returns the expected contents after refactoring. + */ public String getExpectedSource() { if (expectedSource.length() == 0) { return getSource(); @@ -80,7 +96,7 @@ public class TestSourceFile { @Override public int hashCode() { - return name.hashCode(); + return Objects.hash(name, expectedName); } @Override @@ -90,6 +106,6 @@ public class TestSourceFile { if (obj == null || getClass() != obj.getClass()) return false; TestSourceFile other = (TestSourceFile) obj; - return name.equals(other.name); + return Objects.equals(name, other.name) && Objects.equals(expectedName, other.expectedName); } } 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 a5d3ca06d3a..2c7d06db48e 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 @@ -71,7 +71,7 @@ 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); + IncludeOrganizer organizer = new IncludeOrganizer(tu, index, headerChooser); MultiTextEdit edit = organizer.organizeIncludes(ast); IDocument document = new Document(new String(tu.getContents())); edit.apply(document); @@ -597,4 +597,4 @@ public class IncludeOrganizerTest extends IncludesTestBase { 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/refactoring/rename/RenameMacroTests.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/rename/RenameMacroTests.java index 4649da8f955..b0f8c541e44 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/rename/RenameMacroTests.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/rename/RenameMacroTests.java @@ -61,7 +61,7 @@ public class RenameMacroTests extends RenameTestBase { IFile cpp= importFile("test.cpp", contents); //$NON-NLS-1$ int offset1= contents.indexOf("HALLO"); //$NON-NLS-1$ - int offset2= contents.indexOf("HALLO", offset1+1); //$NON-NLS-1$ + int offset2= contents.indexOf("HALLO", offset1 + 1); //$NON-NLS-1$ Change ch= getRefactorChanges(cpp, offset1, "WELT"); //$NON-NLS-1$ assertTotalChanges(6, ch); @@ -120,7 +120,6 @@ public class RenameMacroTests extends RenameTestBase { importFile("other.cpp", buf.toString()); //$NON-NLS-1$ waitForIndexer(); - int offset1= contents.indexOf("MACRO"); //$NON-NLS-1$ // conflicts after renaming @@ -130,7 +129,7 @@ public class RenameMacroTests extends RenameTestBase { "New element: w1 \n" + "Conflicting element type: Local variable"); //$NON-NLS-1$ status= checkConditions(cpp, contents.indexOf("par1"), "MACRO"); //$NON-NLS-1$ //$NON-NLS-2$ - assertRefactoringError(status, "'MACRO' conflicts with the name of an existing macro!"); //$NON-NLS-1$ + assertRefactoringError(status, "'MACRO' conflicts with the name of an existing macro."); //$NON-NLS-1$ status= checkConditions(cpp, offset1, "par1"); //$NON-NLS-1$ assertRefactoringError(status, "A conflict was encountered during refactoring. \n" + "Type of problem: Name conflict \n" + @@ -201,7 +200,7 @@ public class RenameMacroTests extends RenameTestBase { IFile cpp= importFile("test.cpp", contents); //$NON-NLS-1$ int offset1= contents.indexOf("_guard"); //$NON-NLS-1$ - int offset2= contents.indexOf("_guard", offset1+1); //$NON-NLS-1$ + int offset2= contents.indexOf("_guard", offset1 + 1); //$NON-NLS-1$ Change ch= getRefactorChanges(cpp, offset2, "WELT"); //$NON-NLS-1$ assertTotalChanges(2, 0, 1, ch); int off= offset1; diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/rename/RenameMoveHeaderRefactoringTest.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/rename/RenameMoveHeaderRefactoringTest.java new file mode 100644 index 00000000000..67bcd7c907d --- /dev/null +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/rename/RenameMoveHeaderRefactoringTest.java @@ -0,0 +1,252 @@ +/******************************************************************************* + * Copyright (c) 2014 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.ui.tests.refactoring.rename; + +import junit.framework.Test; + +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IResource; +import org.eclipse.ltk.core.refactoring.participants.MoveRefactoring; +import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring; +import org.eclipse.ltk.internal.core.refactoring.resource.MoveResourcesProcessor; +import org.eclipse.ltk.internal.core.refactoring.resource.RenameResourceProcessor; + +import org.eclipse.cdt.ui.PreferenceConstants; +import org.eclipse.cdt.ui.tests.refactoring.RefactoringTestBase; + +import org.eclipse.cdt.internal.ui.refactoring.CRefactoring; + +/** + * Tests for + * {@link org.eclipse.cdt.internal.ui.refactoring.rename.HeaderFileRenameParticipant} and + * {@link org.eclipse.cdt.internal.ui.refactoring.rename.HeaderFileMoveParticipant}. + */ +public class RenameMoveHeaderRefactoringTest extends RefactoringTestBase { + + public RenameMoveHeaderRefactoringTest() { + super(); + } + + public RenameMoveHeaderRefactoringTest(String name) { + super(name); + } + + public static Test suite() { + return suite(RenameMoveHeaderRefactoringTest.class); + } + + @Override + protected void resetPreferences() { + super.resetPreferences(); + getPreferenceStore().setToDefault(PreferenceConstants.FUNCTION_OUTPUT_PARAMETERS_BEFORE_INPUT); + getPreferenceStore().setToDefault(PreferenceConstants.FUNCTION_PASS_OUTPUT_PARAMETERS_BY_POINTER); + } + + @Override + protected CRefactoring createRefactoring() { + throw new UnsupportedOperationException(); + } + + // test1.h + //#ifndef TEST1_H_ + //#define TEST1_H_ + // + //class A {}; + // + //#endif // TEST1_H_ + //==================== + // test.h + //#ifndef TEST_H_ + //#define TEST_H_ + // + //class A {}; + // + //#endif // TEST_H_ + + // test.cpp + //#include + //#include "test1.h" /* Comment1 */ // Comment2 + //==================== + // test.cpp + //#include "test.h" /* Comment1 */ // Comment2 + // + //#include + public void testFileRename() throws Exception { + IResource resource = getProject().getFile("test1.h"); + RenameResourceProcessor processor = new RenameResourceProcessor(resource); + processor.setNewResourceName("test.h"); + RenameRefactoring refactoring = new RenameRefactoring(processor); + executeRefactoring(refactoring, true); + compareFiles(); + } + + // header1.h + //#ifndef HEADER1_H_ + //#define HEADER1_H_ + // + //class A {}; + // + //#endif // HEADER1_H_ + //==================== + // dir/header1.h + //#ifndef DIR_HEADER1_H_ + //#define DIR_HEADER1_H_ + // + //class A {}; + // + //#endif // DIR_HEADER1_H_ + + // source1.cpp + //#include "header1.h" + //==================== + // source1.cpp + //#include "dir/header1.h" + public void testFileMove() throws Exception { + IResource resource = getProject().getFile("header1.h"); + MoveResourcesProcessor processor = new MoveResourcesProcessor(new IResource[] { resource }); + IFolder destination = getProject().getFolder("dir"); + destination.create(true, true, npm()); + processor.setDestination(destination); + MoveRefactoring refactoring = new MoveRefactoring(processor); + executeRefactoring(refactoring, true); + compareFiles(); + } + + // dir1/header1.h + //#ifndef DIR1_HEADER1_H_ + //#define DIR1_HEADER1_H_ + // + //#include "dir1/header2.h" + // + //#endif // DIR1_HEADER1_H_ + //==================== + // dir3/header1.h + //#ifndef DIR3_HEADER1_H_ + //#define DIR3_HEADER1_H_ + // + //#include "dir3/header2.h" + // + //#endif // DIR3_HEADER1_H_ + + // dir1/header2.h + //#if !defined(DIR1_HEADER2_H_) + //#define DIR1_HEADER2_H_ + // + //class A {}; + // + //#endif /* DIR1_HEADER2_H_ */ + //==================== + // dir3/header2.h + //#if !defined(DIR3_HEADER2_H_) + //#define DIR3_HEADER2_H_ + // + //class A {}; + // + //#endif /* DIR3_HEADER2_H_ */ + + // dir1/source1.cpp + //#include + // + //#include "dir1/header1.h" + //==================== + // dir3/source1.cpp + //#include + // + //#include "dir3/header1.h" + + // header2.cpp + //#include "dir1/header1.h" + //#include "dir2/header3.h" + // + //#ifdef SOMETHING + // #include "dir1/header2.h" + //#endif + //==================== + //#include "dir2/header3.h" + //#include "dir3/header1.h" + // + //#ifdef SOMETHING + // #include "dir3/header2.h" + //#endif + public void testFolderRename() throws Exception { + IFolder resource = getProject().getFolder("dir1"); + RenameResourceProcessor processor = new RenameResourceProcessor(resource); + processor.setNewResourceName("dir3"); + RenameRefactoring refactoring = new RenameRefactoring(processor); + executeRefactoring(refactoring, true); + compareFiles(); + } + + // dir1/header1.h + //#ifndef DIR1_HEADER1_H_ + //#define DIR1_HEADER1_H_ + // + //#include "dir1/header2.h" + // + //#endif // DIR1_HEADER1_H_ + //==================== + // dir3/dir1/header1.h + //#ifndef DIR3_DIR1_HEADER1_H_ + //#define DIR3_DIR1_HEADER1_H_ + // + //#include "dir3/dir1/header2.h" + // + //#endif // DIR3_DIR1_HEADER1_H_ + + // dir1/header2.h + //#if !defined(DIR1_HEADER2_H_) + //#define DIR1_HEADER2_H_ + // + //class A {}; + // + //#endif /* DIR1_HEADER2_H_ */ + //==================== + // dir3/dir1/header2.h + //#if !defined(DIR3_DIR1_HEADER2_H_) + //#define DIR3_DIR1_HEADER2_H_ + // + //class A {}; + // + //#endif /* DIR3_DIR1_HEADER2_H_ */ + + // dir1/source1.cpp + //#include + // + //#include "dir1/header1.h" + //==================== + // dir3/dir1/source1.cpp + //#include + // + //#include "dir3/dir1/header1.h" + + // header2.cpp + //#include "dir1/header1.h" + //#include "dir2/header3.h" + // + //int x = 0; + //#include "dir1/header2.h" + //==================== + //#include "dir2/header3.h" + //#include "dir3/dir1/header1.h" + // + //int x = 0; + //#include "dir3/dir1/header2.h" + public void testFolderMove() throws Exception { + IFolder resource = getProject().getFolder("dir1"); + MoveResourcesProcessor processor = new MoveResourcesProcessor(new IResource[] { resource }); + IFolder destination = getProject().getFolder("dir3"); + destination.create(true, true, npm()); + processor.setDestination(destination); + MoveRefactoring refactoring = new MoveRefactoring(processor); + executeRefactoring(refactoring, true); + compareFiles(); + } +} diff --git a/core/org.eclipse.cdt.ui/plugin.properties b/core/org.eclipse.cdt.ui/plugin.properties index be1b67e5126..28069ba36a4 100755 --- a/core/org.eclipse.cdt.ui/plugin.properties +++ b/core/org.eclipse.cdt.ui/plugin.properties @@ -604,7 +604,9 @@ preferenceKeywords.smarttyping=editor typing type close comment tabs indentation historyAction.label = History... createScriptAction.label = Create Script... applyScriptAction.label = Apply Script... -renameParticipant.name = Source Folder Rename +renameFolderParticipant.name = Source Folder Rename +headerFileMoveParticipant.name = Header File Move +headerFileRenameParticipant.name = Header File Rename FormatAction.label= &Format IndentAction.label= Correct &Indentation diff --git a/core/org.eclipse.cdt.ui/plugin.xml b/core/org.eclipse.cdt.ui/plugin.xml index c50d54b410c..547eabd1ae1 100644 --- a/core/org.eclipse.cdt.ui/plugin.xml +++ b/core/org.eclipse.cdt.ui/plugin.xml @@ -4396,23 +4396,55 @@ id="org.eclipse.cdt.internal.ui.refactoring.extractfunction.ExtractFunctionRefactoring"> - - + + - - - - - - - - + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/InclusionContext.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/InclusionContext.java index 0a8a28b58cb..13a9065b265 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/InclusionContext.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/InclusionContext.java @@ -19,6 +19,7 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; +import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.core.parser.IScannerInfo; @@ -44,6 +45,8 @@ public class InclusionContext { private final Map fIncludeResolutionCache; private final Map fInverseIncludeResolutionCache; private final IncludePreferences fPreferences; + private String fSourceContents; + private String fLineDelimiter; public InclusionContext(ITranslationUnit tu) { fTu = tu; @@ -141,7 +144,41 @@ public class InclusionContext { return include; } - public IncludeGroupStyle getIncludeStyle(IPath headerPath) { + /** + * Returns the include directive that resolves to the given header file, or {@code null} if + * the file is not on the include search path. Current directory is not considered to be a part + * of the include path by this method. + */ + public IncludeInfo getIncludeForHeaderFile(IPath fullPath, boolean isSystem) { + IncludeInfo include = fInverseIncludeResolutionCache.get(fullPath); + if (include != null) + return include; + String headerLocation = fullPath.toOSString(); + String shortestInclude = null; + for (IncludeSearchPathElement pathElement : fIncludeSearchPath.getElements()) { + if (isSystem && pathElement.isForQuoteIncludesOnly()) + continue; + String includeDirective = pathElement.getIncludeDirective(headerLocation); + if (includeDirective != null && + (shortestInclude == null || shortestInclude.length() > includeDirective.length())) { + shortestInclude = includeDirective; + } + } + if (shortestInclude == null) { + if (fIncludeSearchPath.isInhibitUseOfCurrentFileDirectory() || + !fCurrentDirectory.isPrefixOf(fullPath)) { + return null; + } + shortestInclude = fullPath.setDevice(null).removeFirstSegments(fCurrentDirectory.segmentCount()).toString(); + } + include = new IncludeInfo(shortestInclude, isSystem); + // Don't put an include to fullPath to fIncludeResolutionCache since it may be wrong + // if the header was included by #include_next. + fInverseIncludeResolutionCache.put(fullPath, include); + return include; + } + + public IncludeGroupStyle getIncludeStyle(IPath headerPath) { IncludeKind includeKind; IncludeInfo includeInfo = getIncludeForHeaderFile(headerPath); if (includeInfo != null && includeInfo.isSystem()) { @@ -252,4 +289,22 @@ public class InclusionContext { return null; return relativePath.toString(); } + + public String getSourceContents() { + if (fSourceContents == null) { + fSourceContents = new String(fTu.getContents()); + } + return fSourceContents; + } + + public String getLineDelimiter() { + if (fLineDelimiter == null) { + try { + fLineDelimiter = StubUtility.getLineDelimiterUsed(fTu); + } catch (CModelException e) { + fLineDelimiter = System.getProperty("line.separator", "\n"); //$NON-NLS-1$//$NON-NLS-2$ + } + } + return fLineDelimiter; + } } \ No newline at end of file diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/StubUtility.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/StubUtility.java index a937b0ade25..c40bb7ab417 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/StubUtility.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/StubUtility.java @@ -666,7 +666,7 @@ public class StubUtility { return projectStore.findTemplateById(id); } - private static String generateIncludeGuardSymbol(IResource file, ICProject cproject) { + public static String generateIncludeGuardSymbol(IResource file, ICProject cproject) { int scheme = PreferenceConstants.getPreference( PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME, cproject, PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME_FILE_NAME); diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/StyledInclude.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/StyledInclude.java index 3d444f7e340..a339776b5b9 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/StyledInclude.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/StyledInclude.java @@ -92,6 +92,6 @@ public class StyledInclude { /** For debugging only */ @Override public String toString() { - return header != null ? header.toPortableString() : includeInfo.toString(); + return header != null ? header.toString() : includeInfo.toString(); } } \ No newline at end of file diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/AddIncludeAction.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/AddIncludeAction.java index 6f87d569a17..8f0c6311290 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/AddIncludeAction.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/AddIncludeAction.java @@ -99,7 +99,6 @@ public class AddIncludeAction extends TextEditorAction { return; } - final String lineDelimiter = getLineDelimiter(editor); final MultiTextEdit[] holder = new MultiTextEdit[1]; SharedASTJob job = new SharedASTJob(CEditorMessages.AddInclude_action, tu) { @Override @@ -113,7 +112,7 @@ public class AddIncludeAction extends TextEditorAction { IIndexManager.ADD_DEPENDENCIES | IIndexManager.ADD_EXTENSION_FRAGMENTS_ADD_IMPORT); try { index.acquireReadLock(); - IncludeCreator creator = new IncludeCreator(tu, index, lineDelimiter, fAmbiguityResolver); + IncludeCreator creator = new IncludeCreator(tu, index, fAmbiguityResolver); holder[0] = creator.createInclude(ast, (ITextSelection) selection); return Status.OK_STATUS; } catch (InterruptedException e) { @@ -154,19 +153,6 @@ public class AddIncludeAction extends TextEditorAction { } } - private static String getLineDelimiter(ITextEditor editor) { - try { - IEditorInput editorInput = editor.getEditorInput(); - IDocument document = editor.getDocumentProvider().getDocument(editorInput); - String delim= document.getLineDelimiter(0); - if (delim != null) { - return delim; - } - } catch (BadLocationException e) { - } - return System.getProperty("line.separator", "\n"); //$NON-NLS-1$//$NON-NLS-2$ - } - @Override public void update() { ITextEditor editor = getTextEditor(); 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 15d005e044f..0a629647abd 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 @@ -66,7 +66,6 @@ public class OrganizeIncludesAction extends TextEditorAction { final IHeaderChooser headerChooser = new InteractiveHeaderChooser( CEditorMessages.OrganizeIncludes_label, editor.getSite().getShell()); - final String lineDelimiter = getLineDelimiter(editor); final MultiTextEdit[] holder = new MultiTextEdit[1]; SharedASTJob job = new SharedASTJob(CEditorMessages.OrganizeIncludes_action, tu) { @Override @@ -80,7 +79,7 @@ public class OrganizeIncludesAction extends TextEditorAction { IIndexManager.ADD_DEPENDENCIES | IIndexManager.ADD_EXTENSION_FRAGMENTS_ADD_IMPORT); try { index.acquireReadLock(); - IncludeOrganizer organizer = new IncludeOrganizer(tu, index, lineDelimiter, headerChooser); + IncludeOrganizer organizer = new IncludeOrganizer(tu, index, headerChooser); holder[0] = organizer.organizeIncludes(ast); return Status.OK_STATUS; } catch (InterruptedException e) { @@ -115,19 +114,6 @@ public class OrganizeIncludesAction extends TextEditorAction { } } - private static String getLineDelimiter(ITextEditor editor) { - try { - IEditorInput editorInput = editor.getEditorInput(); - IDocument document = editor.getDocumentProvider().getDocument(editorInput); - String delim= document.getLineDelimiter(0); - if (delim != null) { - return delim; - } - } catch (BadLocationException e) { - } - return System.getProperty("line.separator", "\n"); //$NON-NLS-1$//$NON-NLS-2$ - } - @Override public void update() { ITextEditor editor = getTextEditor(); 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 24bd35a4f25..149d861a8e5 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,7 +37,6 @@ 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); @@ -47,13 +46,6 @@ public class IncludeCreationContext extends InclusionContext { fHeadersIncludedPreviously = new HashSet<>(); } - public String getSourceContents() { - if (fSourceContents == null) { - fSourceContents = new String(getTranslationUnit().getContents()); - } - return fSourceContents; - } - public final IIndex getIndex() { return fIndex; } 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 af223c3b18b..92f1ae3fba0 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 @@ -14,7 +14,6 @@ package org.eclipse.cdt.internal.ui.refactoring.includes; import static org.eclipse.cdt.core.index.IndexLocationFactory.getAbsolutePath; -import static org.eclipse.cdt.internal.ui.refactoring.includes.IncludeUtil.isContainedInRegion; import java.net.URI; import java.util.ArrayDeque; @@ -32,7 +31,6 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; @@ -98,13 +96,10 @@ import org.eclipse.cdt.internal.ui.CHelpProviderManager; public class IncludeCreator { private static final Collator COLLATOR = Collator.getInstance(); - private final String fLineDelimiter; private final IElementSelector fAmbiguityResolver; private final IncludeCreationContext fContext; - public IncludeCreator(ITranslationUnit tu, IIndex index, String lineDelimiter, - IElementSelector ambiguityResolver) { - fLineDelimiter = lineDelimiter; + public IncludeCreator(ITranslationUnit tu, IIndex index, IElementSelector ambiguityResolver) { fAmbiguityResolver = ambiguityResolver; fContext = new IncludeCreationContext(tu, index); } @@ -237,7 +232,7 @@ public class IncludeCreator { NodeCommentMap commentedNodeMap = ASTCommenter.getCommentedNodeMap(ast); String contents = fContext.getSourceContents(); IRegion includeRegion = - IncludeOrganizer.getSafeIncludeReplacementRegion(contents, ast, commentedNodeMap); + IncludeUtil.getSafeIncludeReplacementRegion(contents, ast, commentedNodeMap); IncludePreferences preferences = fContext.getPreferences(); @@ -258,21 +253,8 @@ public class IncludeCreator { } Collections.sort(styledIncludes, preferences); - // Populate list of existing includes in the include insertion region. - List mergedIncludes = new ArrayList<>(); - for (IASTPreprocessorIncludeStatement include : existingIncludes) { - if (include.isPartOfTranslationUnitFile() && isContainedInRegion(include, includeRegion)) { - String name = new String(include.getName().getSimpleID()); - IncludeInfo includeInfo = new IncludeInfo(name, include.isSystemInclude()); - String path = include.getPath(); - // An empty path means that the include was not resolved. - IPath header = path.isEmpty() ? null : Path.fromOSString(path); - IncludeGroupStyle style = - header != null ? fContext.getIncludeStyle(header) : fContext.getIncludeStyle(includeInfo); - StyledInclude prototype = new StyledInclude(header, includeInfo, style, include); - mergedIncludes.add(prototype); - } - } + List mergedIncludes = + IncludeUtil.getIncludesInRegion(existingIncludes, includeRegion, fContext); if (preferences.allowReordering) { // Since the order of existing include statements may not match the include order @@ -299,23 +281,23 @@ public class IncludeCreator { offset = ASTNodes.skipToNextLineAfterNode(contents, previousNode); flushEditBuffer(offset, text, rootEdit); if (contents.charAt(offset - 1) != '\n') - text.append(fLineDelimiter); + text.append(fContext.getLineDelimiter()); } if (include.getStyle().isBlankLineNeededAfter(previousInclude.getStyle(), preferences.includeStyles)) { if (TextUtil.isLineBlank(contents, offset)) { offset = TextUtil.skipToNextLine(contents, offset); } else { - text.append(fLineDelimiter); + text.append(fContext.getLineDelimiter()); } } } text.append(include.getIncludeInfo().composeIncludeStatement()); - text.append(fLineDelimiter); + text.append(fContext.getLineDelimiter()); } else { if (previousInclude != null && previousInclude.getExistingInclude() == null && include.getStyle().isBlankLineNeededAfter(previousInclude.getStyle(), preferences.includeStyles) && !TextUtil.isPreviousLineBlank(contents, ASTNodes.offset(existingInclude))) { - text.append(fLineDelimiter); + text.append(fContext.getLineDelimiter()); } flushEditBuffer(offset, text, rootEdit); } @@ -323,7 +305,7 @@ public class IncludeCreator { } if (includeRegion.getLength() == 0 && !TextUtil.isLineBlank(contents, includeRegion.getOffset()) && !includes.isEmpty()) { - text.append(fLineDelimiter); + text.append(fContext.getLineDelimiter()); } flushEditBuffer(offset, text, rootEdit); @@ -359,7 +341,7 @@ public class IncludeCreator { if (mergedUsingDeclarations.isEmpty()) { offset = includeRegion.getOffset() + includeRegion.getLength(); - text.append(fLineDelimiter); // Blank line between includes and using declarations. + text.append(fContext.getLineDelimiter()); // Blank line between includes and using declarations. } else { offset = commentedNodeMap.getOffsetIncludingComments(mergedUsingDeclarations.get(0).existingDeclaration); } @@ -382,11 +364,11 @@ public class IncludeCreator { offset = ASTNodes.skipToNextLineAfterNode(contents, previousNode); flushEditBuffer(offset, text, rootEdit); if (contents.charAt(offset - 1) != '\n') - text.append(fLineDelimiter); + text.append(fContext.getLineDelimiter()); } } text.append(using.composeDirective()); - text.append(fLineDelimiter); + text.append(fContext.getLineDelimiter()); } else { flushEditBuffer(offset, text, rootEdit); } 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 9efbd106b3d..6a5a64d94f4 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 @@ -12,10 +12,6 @@ package org.eclipse.cdt.internal.ui.refactoring.includes; import static org.eclipse.cdt.core.index.IndexLocationFactory.getAbsolutePath; -import static org.eclipse.cdt.internal.core.dom.parser.ASTTranslationUnit.getEndingLineNumber; -import static org.eclipse.cdt.internal.core.dom.parser.ASTTranslationUnit.getNodeEndOffset; -import static org.eclipse.cdt.internal.core.dom.parser.ASTTranslationUnit.getNodeOffset; -import static org.eclipse.cdt.internal.core.dom.parser.ASTTranslationUnit.getStartingLineNumber; import static org.eclipse.cdt.internal.ui.refactoring.includes.IncludeUtil.isContainedInRegion; import java.util.ArrayList; @@ -34,7 +30,6 @@ import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; 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; @@ -47,13 +42,9 @@ import org.eclipse.cdt.core.dom.ast.ASTTypeUtil; import org.eclipse.cdt.core.dom.ast.DOMException; import org.eclipse.cdt.core.dom.ast.EScopeKind; import org.eclipse.cdt.core.dom.ast.IASTComment; -import org.eclipse.cdt.core.dom.ast.IASTDeclaration; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTName; -import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement; -import org.eclipse.cdt.core.dom.ast.IASTPreprocessorPragmaStatement; -import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.ICompositeType; @@ -77,10 +68,7 @@ import org.eclipse.cdt.core.index.IIndexFileSet; import org.eclipse.cdt.core.index.IIndexInclude; import org.eclipse.cdt.core.index.IIndexName; import org.eclipse.cdt.core.model.ITranslationUnit; -import org.eclipse.cdt.core.parser.Keywords; import org.eclipse.cdt.core.parser.util.ArrayUtil; -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; @@ -88,10 +76,7 @@ import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.ASTCommenter; import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.NodeCommentMap; import org.eclipse.cdt.internal.core.dom.rewrite.util.ASTNodes; import org.eclipse.cdt.internal.core.dom.rewrite.util.TextUtil; -import org.eclipse.cdt.internal.core.parser.scanner.CharArray; import org.eclipse.cdt.internal.core.parser.scanner.ILocationResolver; -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; @@ -182,11 +167,8 @@ public class IncludeOrganizer { private final IHeaderChooser fHeaderChooser; private final IncludeCreationContext fContext; - private final String fLineDelimiter; - public IncludeOrganizer(ITranslationUnit tu, IIndex index, String lineDelimiter, - IHeaderChooser headerChooser) { - fLineDelimiter = lineDelimiter; + public IncludeOrganizer(ITranslationUnit tu, IIndex index, IHeaderChooser headerChooser) { fHeaderChooser = headerChooser; fContext = new IncludeCreationContext(tu, index); } @@ -240,7 +222,7 @@ public class IncludeOrganizer { NodeCommentMap commentedNodeMap = ASTCommenter.getCommentedNodeMap(ast); IRegion includeReplacementRegion = - getSafeIncludeReplacementRegion(fContext.getSourceContents(), ast, commentedNodeMap); + IncludeUtil.getSafeIncludeReplacementRegion(fContext.getSourceContents(), ast, commentedNodeMap); IncludePreferences preferences = fContext.getPreferences(); boolean allowReordering = preferences.allowReordering || existingIncludes.length == 0; @@ -300,7 +282,7 @@ public class IncludeOrganizer { List comments = commentedNodeMap.getTrailingCommentsForNode(include); StringBuilder buf = new StringBuilder(); for (IASTComment comment : comments) { - buf.append(getPrecedingWhitespace(comment)); + buf.append(ASTNodes.getPrecedingWhitespaceInLine(fContext.getSourceContents(), comment)); buf.append(comment.getRawSignature()); } trailingComment = buf.toString(); @@ -318,7 +300,7 @@ public class IncludeOrganizer { StringBuilder buf = new StringBuilder(); for (String include : includeDirectives) { buf.append(include); - buf.append(fLineDelimiter); + buf.append(fContext.getLineDelimiter()); } int offset = includeReplacementRegion.getOffset(); @@ -326,7 +308,7 @@ public class IncludeOrganizer { if (allowReordering) { if (buf.length() != 0) { if (offset != 0 && !TextUtil.isPreviousLineBlank(fContext.getSourceContents(), offset)) - buf.insert(0, fLineDelimiter); // Blank line before. + buf.insert(0, fContext.getLineDelimiter()); // Blank line before. } String text = buf.toString(); @@ -500,7 +482,7 @@ public class IncludeOrganizer { for (ForwardDeclarationNode node : typeDeclarationsRoot.children) { if (pendingBlankLine) { - buf.append(fLineDelimiter); + buf.append(fContext.getLineDelimiter()); pendingBlankLine = false; } printNode(node, buf); @@ -508,14 +490,14 @@ public class IncludeOrganizer { for (ForwardDeclarationNode node : nonTypeDeclarationsRoot.children) { if (pendingBlankLine) { - buf.append(fLineDelimiter); + buf.append(fContext.getLineDelimiter()); pendingBlankLine = false; } printNode(node, buf); } if ((pendingBlankLine || buf.length() != 0) && !isBlankLineOrEndOfFile(offset)) - buf.append(fLineDelimiter); + buf.append(fContext.getLineDelimiter()); if (buf.length() != 0) rootEdit.addChild(new InsertEdit(offset, buf.toString())); @@ -523,15 +505,15 @@ public class IncludeOrganizer { private void printNode(ForwardDeclarationNode node, StringBuilder buf) throws CoreException { if (node.declaration == null) { - buf.append(CodeGeneration.getNamespaceBeginContent(fContext.getTranslationUnit(), node.name, fLineDelimiter)); + buf.append(CodeGeneration.getNamespaceBeginContent(fContext.getTranslationUnit(), node.name, fContext.getLineDelimiter())); for (ForwardDeclarationNode child : node.children) { printNode(child, buf); } - buf.append(CodeGeneration.getNamespaceEndContent(fContext.getTranslationUnit(), node.name, fLineDelimiter)); + buf.append(CodeGeneration.getNamespaceEndContent(fContext.getTranslationUnit(), node.name, fContext.getLineDelimiter())); } else { buf.append(node.declaration); } - buf.append(fLineDelimiter); + buf.append(fContext.getLineDelimiter()); } private void createCommentOut(IASTPreprocessorIncludeStatement include, MultiTextEdit rootEdit) { @@ -566,95 +548,6 @@ public class IncludeOrganizer { } } - static IRegion getSafeIncludeReplacementRegion(String contents, IASTTranslationUnit ast, - NodeCommentMap commentMap) { - int maxSafeOffset = ast.getFileLocation().getNodeLength(); - IASTDeclaration[] declarations = ast.getDeclarations(true); - if (declarations.length != 0) - maxSafeOffset = declarations[0].getFileLocation().getNodeOffset(); - - boolean topCommentSkipped = false; - int includeOffset = -1; - int includeEndOffset = -1; - int includeGuardStatementsToSkip = getNumberOfIncludeGuardStatementsToSkip(ast); - int includeGuardEndOffset = -1; - for (IASTPreprocessorStatement statement : ast.getAllPreprocessorStatements()) { - if (statement.isPartOfTranslationUnitFile()) { - IASTFileLocation fileLocation = statement.getFileLocation(); - int offset = fileLocation.getNodeOffset(); - if (offset >= maxSafeOffset) - break; - int endOffset = offset + fileLocation.getNodeLength(); - - if (includeGuardStatementsToSkip > 0) { - --includeGuardStatementsToSkip; - includeGuardEndOffset = endOffset; - if (!commentMap.getLeadingCommentsForNode(statement).isEmpty()) { - topCommentSkipped = true; - } - } else if (statement instanceof IASTPreprocessorIncludeStatement) { - if (includeOffset < 0) - includeOffset = offset; - includeEndOffset = endOffset; - includeGuardStatementsToSkip = 0; // Just in case - } else { - break; - } - } - } - if (includeOffset < 0) { - if (includeGuardEndOffset >= 0) { - includeOffset = TextUtil.skipToNextLine(contents, includeGuardEndOffset); - } else { - includeOffset = 0; - } - if (!topCommentSkipped) { - // Skip the first comment block near the top of the file. - includeOffset = skipStandaloneCommentBlock(contents, includeOffset, maxSafeOffset, ast.getComments(), commentMap); - } - includeEndOffset = includeOffset; - } else { - includeEndOffset = TextUtil.skipToNextLine(contents, includeEndOffset); - } - return new Region(includeOffset, includeEndOffset - includeOffset); - } - - private static int getNumberOfIncludeGuardStatementsToSkip(IASTTranslationUnit ast) { - IASTPreprocessorStatement statement = findFirstPreprocessorStatement(ast); - if (statement == null) - return 0; - - int num = 0; - int offset = 0; - if (isPragmaOnce(statement)) { - num++; - offset = getNodeEndOffset(statement); - } - char[] contents = ast.getRawSignature().toCharArray(); - if (offset != 0) - contents = Arrays.copyOfRange(contents, offset, contents.length); - CharArrayIntMap ppKeywords= new CharArrayIntMap(40, -1); - Keywords.addKeywordsPreprocessor(ppKeywords); - if (IncludeGuardDetection.detectIncludeGuard(new CharArray(contents), new LexerOptions(), ppKeywords) != null) { - num += 2; - } - return num; - } - - private static IASTPreprocessorStatement findFirstPreprocessorStatement(IASTTranslationUnit ast) { - for (IASTPreprocessorStatement statement : ast.getAllPreprocessorStatements()) { - if (statement.isPartOfTranslationUnitFile()) - return statement; - } - return null; - } - - private static boolean isPragmaOnce(IASTPreprocessorStatement statement) { - if (!(statement instanceof IASTPreprocessorPragmaStatement)) - return false; - return CharArrayUtils.equals(((IASTPreprocessorPragmaStatement) statement).getMessage(), "once"); //$NON-NLS-1$ - } - /** * Returns {@code true} if there are no non-whitespace characters between the given * {@code offset} and the end of the line. @@ -671,82 +564,6 @@ public class IncludeOrganizer { return true; } - /** - * Returns the whitespace preceding the given node. The newline character in not considered - * whitespace for the purpose of this method. - */ - private String getPrecedingWhitespace(IASTNode node) { - int offset = getNodeOffset(node); - if (offset >= 0) { - String contents = fContext.getSourceContents(); - int i = offset; - while (--i >= 0) { - char c = contents.charAt(i); - if (c == '\n' || !Character.isWhitespace(c)) - break; - } - i++; - return contents.substring(i, offset); - } - return ""; //$NON-NLS-1$ - } - - 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()) { - IASTNode node = entry.getKey(); - if (getNodeOffset(node) <= endOffset) { - for (IASTComment comment : entry.getValue()) { - inverseLeadingMap.put(comment, node); - } - } - } - Map inverseFreestandingMap = new HashMap<>(); - for (Map.Entry> entry : commentMap.getFreestandingMap().entrySet()) { - IASTNode node = entry.getKey(); - if (getNodeEndOffset(node) < endOffset) { - for (IASTComment comment : entry.getValue()) { - inverseFreestandingMap.put(comment, node); - } - } - } - - for (int i = 0; i < comments.length; i++) { - IASTComment comment = comments[i]; - int commentOffset = getNodeOffset(comment); - if (commentOffset >= offset) { - if (commentOffset >= endOffset) - break; - IASTNode node = inverseLeadingMap.get(comment); - if (node != null) { - List leadingComments = commentMap.getLeadingMap().get(node); - IASTComment previous = leadingComments.get(0); - for (int j = 1; j < leadingComments.size(); j++) { - comment = leadingComments.get(j); - if (getStartingLineNumber(comment) > getEndingLineNumber(previous) + 1) - return ASTNodes.skipToNextLineAfterNode(contents, previous); - previous = comment; - } - if (getStartingLineNumber(node) > getEndingLineNumber(previous) + 1) - return ASTNodes.skipToNextLineAfterNode(contents, previous); - } - node = inverseFreestandingMap.get(comment); - if (node != null) { - List freestandingComments = commentMap.getFreestandingMap().get(node); - IASTComment previous = freestandingComments.get(0); - for (int j = 1; j < freestandingComments.size(); j++) { - comment = freestandingComments.get(j); - if (getStartingLineNumber(comment) > getEndingLineNumber(previous) + 1) - return ASTNodes.skipToNextLineAfterNode(contents, previous); - previous = comment; - } - } - } - } - return offset; - } - private Set removeBindingsDefinedInIncludedHeaders(IASTTranslationUnit ast, Set bindings, IIndexFileSet reachableHeaders) throws CoreException { List requests = createInclusionRequests(ast, bindings, true, reachableHeaders); diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeUtil.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeUtil.java index f831c8cec31..6ac73041739 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeUtil.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/includes/IncludeUtil.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2013 Google, Inc and others. + * Copyright (c) 2012, 2014 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 @@ -10,19 +10,45 @@ *******************************************************************************/ package org.eclipse.cdt.internal.ui.refactoring.includes; -import static org.eclipse.cdt.internal.core.dom.parser.ASTTranslationUnit.getNodeEndOffset; -import static org.eclipse.cdt.internal.core.dom.parser.ASTTranslationUnit.getNodeOffset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.dom.ast.IASTComment; +import org.eclipse.cdt.core.dom.ast.IASTDeclaration; +import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTNode; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorPragmaStatement; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement; +import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.index.IIndexFile; import org.eclipse.cdt.core.index.IIndexFileLocation; import org.eclipse.cdt.core.index.IndexLocationFactory; +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.internal.core.dom.rewrite.commenthandler.NodeCommentMap; +import org.eclipse.cdt.internal.core.dom.rewrite.util.ASTNodes; +import org.eclipse.cdt.internal.core.dom.rewrite.util.TextUtil; +import org.eclipse.cdt.internal.core.parser.scanner.CharArray; +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.InclusionContext; +import org.eclipse.cdt.internal.corext.codemanipulation.StyledInclude; public class IncludeUtil { /** Not instantiatable. All methods are static. */ @@ -72,7 +98,259 @@ public class IncludeUtil { } public static boolean isContainedInRegion(IASTNode node, IRegion region) { - return getNodeOffset(node) >= region.getOffset() - && getNodeEndOffset(node) <= region.getOffset() + region.getLength(); + return ASTNodes.offset(node) >= region.getOffset() + && ASTNodes.endOffset(node) <= region.getOffset() + region.getLength(); + } + + /** + * Returns the region containing nothing but include statements located before the first + * statement that may depend on includes. + * + * @param contents the contents of the translation unit + * @param ast the AST + * @param commentMap comments of the translation unit + * @return the include region, possibly empty + */ + public static IRegion getSafeIncludeReplacementRegion(String contents, IASTTranslationUnit ast, + NodeCommentMap commentMap) { + int maxSafeOffset = ast.getFileLocation().getNodeLength(); + IASTDeclaration[] declarations = ast.getDeclarations(true); + if (declarations.length != 0) + maxSafeOffset = declarations[0].getFileLocation().getNodeOffset(); + + boolean topCommentSkipped = false; + int includeOffset = -1; + int includeEndOffset = -1; + int includeGuardStatementsToSkip = getNumberOfIncludeGuardStatementsToSkip(contents, ast); + int includeGuardEndOffset = -1; + for (IASTPreprocessorStatement statement : ast.getAllPreprocessorStatements()) { + if (statement.isPartOfTranslationUnitFile()) { + IASTFileLocation fileLocation = statement.getFileLocation(); + int offset = fileLocation.getNodeOffset(); + if (offset >= maxSafeOffset) + break; + int endOffset = offset + fileLocation.getNodeLength(); + + if (includeGuardStatementsToSkip > 0) { + --includeGuardStatementsToSkip; + includeGuardEndOffset = endOffset; + if (!commentMap.getLeadingCommentsForNode(statement).isEmpty()) { + topCommentSkipped = true; + } + } else if (statement instanceof IASTPreprocessorIncludeStatement) { + if (includeOffset < 0) + includeOffset = offset; + includeEndOffset = endOffset; + includeGuardStatementsToSkip = 0; // Just in case + } else { + break; + } + } + } + if (includeOffset < 0) { + if (includeGuardEndOffset >= 0) { + includeOffset = TextUtil.skipToNextLine(contents, includeGuardEndOffset); + } else { + includeOffset = 0; + } + if (!topCommentSkipped) { + // Skip the first comment block near the top of the file. + includeOffset = skipStandaloneCommentBlock(contents, includeOffset, maxSafeOffset, + ast.getComments(), commentMap); + } + includeEndOffset = includeOffset; + } else { + includeEndOffset = TextUtil.skipToNextLine(contents, includeEndOffset); + } + return new Region(includeOffset, includeEndOffset - includeOffset); + } + + /** + * Returns the include statements within the given region. + * + * @param existingIncludes the include statements to choose from + * @param region the region to select includes within + * @param inclusionContext the inclusion context + * @return a list of {@link StyledInclude} objects representing the includes + */ + public static List getIncludesInRegion(IASTPreprocessorIncludeStatement[] existingIncludes, + IRegion region, InclusionContext inclusionContext) { + // Populate a list of existing includes in the include insertion region. + List includes = new ArrayList<>(); + for (IASTPreprocessorIncludeStatement include : existingIncludes) { + if (include.isPartOfTranslationUnitFile() && isContainedInRegion(include, region)) { + String name = new String(include.getName().getSimpleID()); + IncludeInfo includeInfo = new IncludeInfo(name, include.isSystemInclude()); + String path = include.getPath(); + // An empty path means that the include was not resolved. + IPath header = path.isEmpty() ? null : Path.fromOSString(path); + IncludeGroupStyle style = + header != null ? inclusionContext.getIncludeStyle(header) : inclusionContext.getIncludeStyle(includeInfo); + StyledInclude prototype = new StyledInclude(header, includeInfo, style, include); + includes.add(prototype); + } + } + return includes; + } + + /** + * Searches for the include guard in the file and, if found, returns its value and occurrences + * in the file. + * + * @param contents the contents of the translation unit + * @param ast the AST + * @param includeGuardPositions the list of include guard occurrences that is populated by + * the method + * @return the include guard, or {@code null} if not found + */ + public static String findIncludeGuard(String contents, IASTTranslationUnit ast, + List includeGuardPositions) { + includeGuardPositions.clear(); + IASTPreprocessorStatement[] preprocessorStatements = ast.getAllPreprocessorStatements(); + int i = 0; + while (true) { + if (i >= preprocessorStatements.length) + return null; + if (preprocessorStatements[i].isPartOfTranslationUnitFile()) + break; + i++; + } + IASTPreprocessorStatement statement = preprocessorStatements[i]; + + int offset = 0; + if (isPragmaOnce(statement)) { + offset = ASTNodes.endOffset(statement); + i++; + } + char[] guardChars = detectIncludeGuard(contents, offset); + if (guardChars == null) + return null; + String guard = new String(guardChars); + int count = 0; + IASTPreprocessorStatement lastStatement = null; + for (; i < preprocessorStatements.length; i++) { + statement = preprocessorStatements[i]; + if (statement.isPartOfTranslationUnitFile()) { + if (count < 2) { + findGuardInRange(contents, guard, ASTNodes.offset(statement), + ASTNodes.endOffset(statement), includeGuardPositions); + count++; + } else { + lastStatement = statement; + } + } + } + if (lastStatement != null) { + findGuardInRange(contents, guard, ASTNodes.offset(lastStatement), + contents.length(), includeGuardPositions); + } + return guard; + } + + private static int getNumberOfIncludeGuardStatementsToSkip(String contents, IASTTranslationUnit ast) { + IASTPreprocessorStatement statement = findFirstPreprocessorStatement(ast); + if (statement == null) + return 0; + + int num = 0; + int offset = 0; + if (isPragmaOnce(statement)) { + num++; + offset = ASTNodes.endOffset(statement); + } + char[] guard = detectIncludeGuard(contents, offset); + if (guard != null) { + num += 2; + } + return num; + } + + private static char[] detectIncludeGuard(String contents, int offset) { + char[] contentsChars = contents.toCharArray(); + if (offset != 0) + contentsChars = Arrays.copyOfRange(contentsChars, offset, contentsChars.length); + CharArrayIntMap ppKeywords= new CharArrayIntMap(40, -1); + Keywords.addKeywordsPreprocessor(ppKeywords); + char[] guardChars = IncludeGuardDetection.detectIncludeGuard( + new CharArray(contentsChars), new LexerOptions(), ppKeywords); + return guardChars; + } + + private static void findGuardInRange(String contents, String guard, int offset, int endOffset, + List includeGuardPositions) { + int pos = contents.indexOf(guard, offset); + if (pos >= 0 && pos + guard.length() <= endOffset) { + includeGuardPositions.add(new Region(pos, guard.length())); + } + } + + 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()) { + IASTNode node = entry.getKey(); + if (ASTNodes.offset(node) <= endOffset) { + for (IASTComment comment : entry.getValue()) { + inverseLeadingMap.put(comment, node); + } + } + } + Map inverseFreestandingMap = new HashMap<>(); + for (Map.Entry> entry : commentMap.getFreestandingMap().entrySet()) { + IASTNode node = entry.getKey(); + if (ASTNodes.endOffset(node) < endOffset) { + for (IASTComment comment : entry.getValue()) { + inverseFreestandingMap.put(comment, node); + } + } + } + + for (int i = 0; i < comments.length; i++) { + IASTComment comment = comments[i]; + int commentOffset = ASTNodes.offset(comment); + if (commentOffset >= offset) { + if (commentOffset >= endOffset) + break; + IASTNode node = inverseLeadingMap.get(comment); + if (node != null) { + List leadingComments = commentMap.getLeadingMap().get(node); + IASTComment previous = leadingComments.get(0); + for (int j = 1; j < leadingComments.size(); j++) { + comment = leadingComments.get(j); + if (ASTNodes.getStartingLineNumber(comment) > ASTNodes.getEndingLineNumber(previous) + 1) + return ASTNodes.skipToNextLineAfterNode(contents, previous); + previous = comment; + } + if (ASTNodes.getStartingLineNumber(node) > ASTNodes.getEndingLineNumber(previous) + 1) + return ASTNodes.skipToNextLineAfterNode(contents, previous); + } + node = inverseFreestandingMap.get(comment); + if (node != null) { + List freestandingComments = commentMap.getFreestandingMap().get(node); + IASTComment previous = freestandingComments.get(0); + for (int j = 1; j < freestandingComments.size(); j++) { + comment = freestandingComments.get(j); + if (ASTNodes.getStartingLineNumber(comment) > ASTNodes.getEndingLineNumber(previous) + 1) + return ASTNodes.skipToNextLineAfterNode(contents, previous); + previous = comment; + } + } + } + } + return offset; + } + + private static IASTPreprocessorStatement findFirstPreprocessorStatement(IASTTranslationUnit ast) { + for (IASTPreprocessorStatement statement : ast.getAllPreprocessorStatements()) { + if (statement.isPartOfTranslationUnitFile()) + return statement; + } + return null; + } + + private static boolean isPragmaOnce(IASTPreprocessorStatement statement) { + if (!(statement instanceof IASTPreprocessorPragmaStatement)) + return false; + return CharArrayUtils.equals(((IASTPreprocessorPragmaStatement) statement).getMessage(), "once"); //$NON-NLS-1$ } } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileMoveParticipant.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileMoveParticipant.java new file mode 100644 index 00000000000..942e9187232 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileMoveParticipant.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2014 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.ui.refactoring.rename; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceProxy; +import org.eclipse.core.resources.IResourceProxyVisitor; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Status; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; +import org.eclipse.ltk.core.refactoring.participants.MoveArguments; +import org.eclipse.ltk.core.refactoring.participants.MoveParticipant; + +/** + * Updates include statements and include guards in response to a file or a folder move. + */ +public class HeaderFileMoveParticipant extends MoveParticipant { + private IResource movedResource; + private Change change; + + public HeaderFileMoveParticipant() { + } + + @Override + protected boolean initialize(Object element) { + if (element instanceof IResource) { + this.movedResource = (IResource) element; + return true; + } + return false; + } + + @Override + public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) + throws OperationCanceledException { + MoveArguments args = getArguments(); + if (!args.getUpdateReferences()) + return null; + if (movedResource.isLinked()) + return null; + + Object destinationResource = args.getDestination(); + if (!(destinationResource instanceof IContainer)) + return null; + final IContainer destination = (IContainer) destinationResource; + final IPath destinationLocation = destination.getLocation(); + if (destinationLocation.equals(movedResource.getLocation().removeLastSegments(1))) + return null; + + try { + // Maps the affected files to new, not yet existing, files. + final Map movedFiles = new HashMap<>(); + if (movedResource instanceof IContainer) { + final int prefixLength = movedResource.getFullPath().segmentCount() - 1; + ((IContainer) movedResource).accept(new IResourceProxyVisitor() { + @Override + public boolean visit(IResourceProxy proxy) throws CoreException { + if (proxy.isLinked()) + return false; + if (proxy.getType() == IResource.FILE) { + IFile file = (IFile) proxy.requestResource(); + movedFiles.put(file, destination.getFile(file.getFullPath().removeFirstSegments(prefixLength))); + return false; + } + return true; + } + }, IResource.NONE); + } else if (movedResource instanceof IFile) { + IFile file = (IFile) movedResource; + movedFiles.put(file, destination.getFile(new Path(movedResource.getName()))); + } + + HeaderFileReferenceAdjuster includeAdjuster = new HeaderFileReferenceAdjuster(movedFiles); + change = includeAdjuster.createChange(context, pm); + } catch (CoreException e) { + return RefactoringStatus.create(e.getStatus()); + } + return RefactoringStatus.create(Status.OK_STATUS); + } + + @Override + public Change createPreChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { + pm.done(); + return change; + } + + @Override + public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { + pm.done(); + return null; + } + + @Override + public String getName() { + return RenameMessages.HeaderFileMoveParticipant_name; + } +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileReferenceAdjuster.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileReferenceAdjuster.java new file mode 100644 index 00000000000..c4fc275e8f2 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileReferenceAdjuster.java @@ -0,0 +1,491 @@ +/******************************************************************************* + * Copyright (c) 2014 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.ui.refactoring.rename; + +import static org.eclipse.cdt.internal.ui.editor.ASTProvider.WAIT_ACTIVE_ONLY; +import static org.eclipse.cdt.internal.ui.editor.ASTProvider.getASTProvider; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.preferences.IPreferencesService; +import org.eclipse.core.runtime.preferences.IScopeContext; +import org.eclipse.jface.text.IRegion; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.CompositeChange; +import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; +import org.eclipse.ltk.core.refactoring.participants.ValidateEditChecker; +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 org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.dom.ast.IASTComment; +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.core.dom.ast.IASTNode; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement; +import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; +import org.eclipse.cdt.core.index.IIndex; +import org.eclipse.cdt.core.index.IIndexFile; +import org.eclipse.cdt.core.index.IIndexFileLocation; +import org.eclipse.cdt.core.index.IIndexInclude; +import org.eclipse.cdt.core.index.IIndexManager; +import org.eclipse.cdt.core.index.IndexLocationFactory; +import org.eclipse.cdt.core.model.CoreModel; +import org.eclipse.cdt.core.model.ICProject; +import org.eclipse.cdt.core.model.ITranslationUnit; +import org.eclipse.cdt.core.model.IWorkingCopy; +import org.eclipse.cdt.ui.CUIPlugin; +import org.eclipse.cdt.ui.IWorkingCopyManager; +import org.eclipse.cdt.ui.PreferenceConstants; +import org.eclipse.cdt.ui.refactoring.CTextFileChange; + +import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.ASTCommenter; +import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.NodeCommentMap; +import org.eclipse.cdt.internal.core.dom.rewrite.util.ASTNodes; +import org.eclipse.cdt.internal.core.dom.rewrite.util.TextUtil; +import org.eclipse.cdt.internal.corext.codemanipulation.IncludeInfo; +import org.eclipse.cdt.internal.corext.codemanipulation.StubUtility; +import org.eclipse.cdt.internal.corext.codemanipulation.StyledInclude; + +import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeCreationContext; +import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeGroupStyle; +import org.eclipse.cdt.internal.ui.refactoring.includes.IncludePreferences; +import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeUtil; + +/** + * Updates include statements and include guards in response to file or folder move or rename. + */ +public class HeaderFileReferenceAdjuster { + private static final int PARSE_MODE = ITranslationUnit.AST_SKIP_ALL_HEADERS + | ITranslationUnit.AST_CONFIGURE_USING_SOURCE_CONTEXT + | ITranslationUnit.AST_SKIP_FUNCTION_BODIES + | ITranslationUnit.AST_PARSE_INACTIVE_CODE; + + private final Map movedFiles; + private final Map movedFilesByLocation; + private IIndex index; + private int indexLockCount; + + /** + * @param movedFiles keys are moved files, values are new, not yet existing, files + */ + public HeaderFileReferenceAdjuster(Map movedFiles) { + this.movedFiles = movedFiles; + this.movedFilesByLocation = new HashMap<>(); + for (Entry entry : movedFiles.entrySet()) { + this.movedFilesByLocation.put(entry.getKey().getLocation().toOSString(), entry.getValue().getLocation()); + } + } + + public Change createChange(CheckConditionsContext context, IProgressMonitor pm) + throws CoreException, OperationCanceledException { + SubMonitor progress = SubMonitor.convert(pm, 10); + CompositeChange change = null; + Set affectedFiles = new HashSet<>(); + IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + + lockIndex(); + try { + for (Entry entry : movedFiles.entrySet()) { + IFile oldFile = entry.getKey(); + IFile newFile = entry.getValue(); + if (areIncludeGuardsAffected(oldFile, newFile)) + affectedFiles.add(oldFile); + + IIndexFileLocation indexFileLocation = IndexLocationFactory.getWorkspaceIFL(oldFile); + IIndexFile[] indexFiles = index.getFiles(indexFileLocation); + for (IIndexFile indexFile : indexFiles) { + IIndexInclude[] includes = index.findIncludedBy(indexFile); + for (IIndexInclude include : includes) { + IIndexFileLocation includeLocation = include.getIncludedByLocation(); + String path = includeLocation.getFullPath(); + if (path != null) { + IResource resource = workspaceRoot.findMember(path); + if (resource.getType() == IResource.FILE) { + IFile includer = (IFile) resource; + affectedFiles.add(includer); + } + } + } + } + } + + IWorkingCopyManager workingCopyManager = CUIPlugin.getDefault().getWorkingCopyManager(); + IWorkingCopy[] workingCopies = workingCopyManager.getSharedWorkingCopies(); + progress.worked(1); + progress = SubMonitor.convert(progress.newChild(9), workingCopies.length + affectedFiles.size()); + + List changes = new ArrayList<>(); + ValidateEditChecker checker= (ValidateEditChecker) context.getChecker(ValidateEditChecker.class); + for (ITranslationUnit tu : workingCopies) { + addFileChange(tu, changes, checker, progress.newChild(1)); + } + + CoreModel coreModel = CoreModel.getDefault(); + for (IFile file : affectedFiles) { + ITranslationUnit tu = (ITranslationUnit) coreModel.create(file); + if (workingCopyManager.findSharedWorkingCopy(tu) != null) + continue; + addFileChange(tu, changes, checker, progress.newChild(1)); + } + + if (!changes.isEmpty()) { + change = new CompositeChange("", changes.toArray(new Change[changes.size()])); //$NON-NLS-1$ + change.markAsSynthetic(); + } + } finally { + unlockIndex(); + pm.done(); + } + return change; + } + + private void addFileChange(ITranslationUnit tu, List changes, ValidateEditChecker checker, + IProgressMonitor pm) throws CoreException { + TextEdit edit = createEdit(tu, pm); + if (edit != null) { + CTextFileChange fileChange = new CTextFileChange(tu.getElementName(), tu); + fileChange.setEdit(edit); + changes.add(fileChange); + checker.addFile(fileChange.getFile()); + } + } + + private TextEdit createEdit(ITranslationUnit tu, IProgressMonitor pm) + throws CoreException, OperationCanceledException { + checkCanceled(pm); + + IASTTranslationUnit sharedAst = null; + + SubMonitor progress = SubMonitor.convert(pm, 2); + try { + IASTTranslationUnit ast = + getASTProvider().acquireSharedAST(tu, index, WAIT_ACTIVE_ONLY, progress.newChild(1)); + if (ast == null) { + checkCanceled(pm); + ast= tu.getAST(index, PARSE_MODE); + if (ast == null) + return null; + } else { + sharedAst = ast; + } + return createEdit(ast, tu, progress.newChild(1)); + } finally { + if (sharedAst != null) { + getASTProvider().releaseSharedAST(sharedAst); + } + pm.done(); + } + } + + private TextEdit createEdit(IASTTranslationUnit ast, ITranslationUnit tu, IProgressMonitor pm) + throws CoreException, OperationCanceledException { + IncludeCreationContext context = new IncludeCreationContext(tu, index); + String contents = context.getSourceContents(); + + MultiTextEdit rootEdit = createIncludeGuardEdit(ast, tu, contents); + + Map affectedIncludes = new IdentityHashMap<>(); + IASTPreprocessorIncludeStatement[] existingIncludes = ast.getIncludeDirectives(); + for (IASTPreprocessorIncludeStatement include : existingIncludes) { + if (include.isPartOfTranslationUnitFile()) { + String location; + if (include.isActive()) { + location = include.getPath(); + if (location.isEmpty()) + continue; // Unresolved include. + } else { + String name = new String(include.getName().getSimpleID()); + IncludeInfo includeInfo = new IncludeInfo(name, include.isSystemInclude()); + IPath path = context.resolveInclude(includeInfo); + if (path == null) + continue; + location = path.toString(); + } + IPath newLocation = movedFilesByLocation.get(location); + if (newLocation != null) { + affectedIncludes.put(include, newLocation); + } + } + } + if (affectedIncludes.isEmpty()) + return rootEdit; + + NodeCommentMap commentedNodeMap = ASTCommenter.getCommentedNodeMap(ast); + IRegion includeRegion = + IncludeUtil.getSafeIncludeReplacementRegion(contents, ast, commentedNodeMap); + + IncludePreferences preferences = context.getPreferences(); + + if (rootEdit == null) + rootEdit = new MultiTextEdit(); + + context.addHeadersIncludedPreviously(existingIncludes); + + if (preferences.allowReordering) { + List modifiedIncludes = new ArrayList<>(); + // Put the changed includes into modifiedIncludes. + for (Entry entry : affectedIncludes.entrySet()) { + IASTPreprocessorIncludeStatement existingInclude = entry.getKey(); + if (IncludeUtil.isContainedInRegion(existingInclude, includeRegion)) { + IPath header = entry.getValue(); + IncludeGroupStyle style = context.getIncludeStyle(header); + IncludeInfo includeInfo = context.createIncludeInfo(header, style); + StyledInclude include = new StyledInclude(header, includeInfo, style, existingInclude); + modifiedIncludes.add(include); + } + } + + Collections.sort(modifiedIncludes, preferences); + + // Populate a list of the existing unchanged includes in the include insertion region. + List mergedIncludes = + IncludeUtil.getIncludesInRegion(existingIncludes, includeRegion, context); + Deque deletes = new ArrayDeque<>(); + // Create text deletes for old locations of the includes that will be changed. + int deleteOffset = -1; + boolean emptyLineEncountered = false; + int j = 0; + for (int i = 0; i < mergedIncludes.size(); i++) { + StyledInclude include = mergedIncludes.get(i); + IASTPreprocessorIncludeStatement existingInclude = include.getExistingInclude(); + int offset = ASTNodes.offset(existingInclude); + boolean previousLineBlank = TextUtil.isPreviousLineBlank(contents, offset); + if (affectedIncludes.containsKey(existingInclude)) { + if (deleteOffset < 0) { + deleteOffset = offset; + } else if (!emptyLineEncountered && previousLineBlank) { + // Preserve the first encountered blank line. + deletes.add(new DeleteEdit(deleteOffset, offset - deleteOffset)); + deleteOffset = -1; + } + emptyLineEncountered |= previousLineBlank; + } else { + if (deleteOffset >= 0) { + if (!emptyLineEncountered && previousLineBlank) { + offset = TextUtil.getPreviousLineStart(contents, offset); + } + deletes.add(new DeleteEdit(deleteOffset, offset - deleteOffset)); + deleteOffset = -1; + } + emptyLineEncountered = false; + if (j < i) + mergedIncludes.set(j, include); + j++; + } + } + while (j < mergedIncludes.size()) { + mergedIncludes.remove(mergedIncludes.size() - 1); + } + if (deleteOffset >= 0) + deletes.add(new DeleteEdit(deleteOffset, includeRegion.getOffset() + includeRegion.getLength() - deleteOffset)); + + // Since the order of existing include statements may not match the include order + // preferences, we find positions for the new include statements by pushing them up + // from the bottom of the include insertion region. + for (StyledInclude include : modifiedIncludes) { + if (IncludeUtil.isContainedInRegion(include.getExistingInclude(), includeRegion)) { + int i = mergedIncludes.size(); + while (--i >= 0 && preferences.compare(include, mergedIncludes.get(i)) < 0) {} + mergedIncludes.add(i + 1, include); + } + } + + int offset = includeRegion.getOffset(); + StringBuilder text = new StringBuilder(); + StyledInclude previousInclude = null; + for (StyledInclude include : mergedIncludes) { + IASTPreprocessorIncludeStatement existingInclude = include.getExistingInclude(); + if (affectedIncludes.containsKey(existingInclude)) { + if (previousInclude != null) { + IASTNode previousNode = previousInclude.getExistingInclude(); + if (!affectedIncludes.containsKey(previousNode)) { + offset = ASTNodes.skipToNextLineAfterNode(contents, previousNode); + flushEditBuffer(offset, text, deletes, rootEdit); + if (contents.charAt(offset - 1) != '\n') + text.append(context.getLineDelimiter()); + } + if (isBlankLineNeededBetween(previousInclude, include, preferences)) { + if (TextUtil.isLineBlank(contents, offset)) { + int oldOffset = offset; + offset = TextUtil.skipToNextLine(contents, offset); + if (offset == oldOffset || contents.charAt(offset - 1) != '\n') + text.append(context.getLineDelimiter()); + } else { + text.append(context.getLineDelimiter()); + } + } + } + text.append(include.getIncludeInfo().composeIncludeStatement()); + List comments = commentedNodeMap.getTrailingCommentsForNode(existingInclude); + for (IASTComment comment : comments) { + text.append(ASTNodes.getPrecedingWhitespaceInLine(contents, comment)); + text.append(comment.getRawSignature()); + } + text.append(context.getLineDelimiter()); + } else { + if (previousInclude != null && affectedIncludes.containsKey(previousInclude.getExistingInclude()) && + isBlankLineNeededBetween(previousInclude, include, preferences) && + TextUtil.findBlankLine(contents, offset, ASTNodes.offset(existingInclude)) < 0) { + text.append(context.getLineDelimiter()); + } + flushEditBuffer(offset, text, deletes, rootEdit); + } + previousInclude = include; + } + if (includeRegion.getLength() == 0 && !TextUtil.isLineBlank(contents, includeRegion.getOffset())) { + text.append(context.getLineDelimiter()); + } + offset = includeRegion.getOffset() + includeRegion.getLength(); + flushEditBuffer(offset, text, deletes, rootEdit); + } + + for (IASTPreprocessorIncludeStatement existingInclude : existingIncludes) { + IPath header = affectedIncludes.get(existingInclude); + if (header != null && + (!preferences.allowReordering || !IncludeUtil.isContainedInRegion(existingInclude, includeRegion))) { + IncludeGroupStyle style = context.getIncludeStyle(header); + IncludeInfo includeInfo = context.createIncludeInfo(header, style); + IASTName name = existingInclude.getName(); + int offset = ASTNodes.offset(name) - 1; + int length = ASTNodes.endOffset(name) + 1 - offset; + rootEdit.addChild(new ReplaceEdit(offset, length, includeInfo.toString())); + } + } + + return rootEdit; + } + + private static boolean isBlankLineNeededBetween(StyledInclude include1, StyledInclude include2, + IncludePreferences preferences) { + return include2.getStyle().isBlankLineNeededAfter(include1.getStyle(), preferences.includeStyles); + } + + private MultiTextEdit createIncludeGuardEdit(IASTTranslationUnit ast, ITranslationUnit tu, String contents) { + IResource resource = tu.getResource(); + IFile newFile = movedFiles.get(resource); + if (newFile == null) + return null; + boolean guardsAffected = areIncludeGuardsAffected((IFile) resource, newFile); + if (!guardsAffected) + return null; + List includeGuardPositions = new ArrayList<>(); + String oldGuard = IncludeUtil.findIncludeGuard(contents, ast, includeGuardPositions); + if (oldGuard == null) + return null; + if (!oldGuard.equals(StubUtility.generateIncludeGuardSymbol(resource, tu.getCProject()))) + return null; + IProject newProject = newFile.getProject(); + ICProject newCProject = CoreModel.getDefault().create(newProject); + if (newCProject == null) + return null; + String guard = StubUtility.generateIncludeGuardSymbol(newFile, newCProject); + if (guard.equals(oldGuard)) + return null; + MultiTextEdit rootEdit = new MultiTextEdit(); + for (IRegion region : includeGuardPositions) { + rootEdit.addChild(new ReplaceEdit(region.getOffset(), region.getLength(), guard)); + } + return rootEdit; + } + + private void flushEditBuffer(int offset, StringBuilder text, Deque deletes, MultiTextEdit edit) { + consumeDeletesUpTo(offset, deletes, edit); + if (text.length() != 0) { + edit.addChild(new InsertEdit(offset, text.toString())); + text.delete(0, text.length()); + } + } + + private void consumeDeletesUpTo(int offset, Deque deletes, MultiTextEdit rootEdit) { + while (!deletes.isEmpty()) { + DeleteEdit edit = deletes.peek(); + if (edit.getOffset() > offset) + break; + deletes.remove(); + rootEdit.addChild(edit); + } + } + + private void lockIndex() throws CoreException, OperationCanceledException { + if (indexLockCount == 0) { + if (index == null) { + ICProject[] projects= CoreModel.getDefault().getCModel().getCProjects(); + index = CCorePlugin.getIndexManager().getIndex(projects, + IIndexManager.ADD_EXTENSION_FRAGMENTS_EDITOR); + } + try { + index.acquireReadLock(); + } catch (InterruptedException e) { + throw new OperationCanceledException(); + } + } + indexLockCount++; + } + + private void unlockIndex() { + if (--indexLockCount <= 0) { + if (index != null) { + index.releaseReadLock(); + } + index = null; + } + } + + private static void checkCanceled(IProgressMonitor pm) throws OperationCanceledException { + if (pm != null && pm.isCanceled()) + throw new OperationCanceledException(); + } + + private static boolean areIncludeGuardsAffected(IFile oldfile, IFile newFile) { + String filename = oldfile.getLocation().lastSegment(); + if (!CoreModel.isValidHeaderUnitName(null, filename)) + return false; + IPreferencesService preferences = Platform.getPreferencesService(); + IScopeContext[] scopes = PreferenceConstants.getPreferenceScopes(oldfile.getProject()); + int schema = preferences.getInt(CUIPlugin.PLUGIN_ID, + PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME, + PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME_FILE_NAME, scopes); + switch (schema) { + case PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME_FILE_PATH: + return true; + + case PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME_FILE_NAME: + return !filename.equals(newFile.getName()); + + default: + return false; + } + } +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileRenameParticipant.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileRenameParticipant.java new file mode 100644 index 00000000000..e4ab35e40ec --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileRenameParticipant.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2014 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.ui.refactoring.rename; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceProxy; +import org.eclipse.core.resources.IResourceProxyVisitor; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Status; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; +import org.eclipse.ltk.core.refactoring.participants.RenameArguments; +import org.eclipse.ltk.core.refactoring.participants.RenameParticipant; + +/** + * Updates include statements and include guards in response to a file or a folder rename. + */ +public class HeaderFileRenameParticipant extends RenameParticipant { + private IResource renamedResource; + private Change change; + + public HeaderFileRenameParticipant() { + } + + @Override + protected boolean initialize(Object element) { + if (element instanceof IResource) { + this.renamedResource = (IResource) element; + return true; + } + return false; + } + + @Override + public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) + throws OperationCanceledException { + RenameArguments args = getArguments(); + if (!args.getUpdateReferences()) + return null; + if (renamedResource.isLinked()) + return null; + + String newName = args.getNewName(); + + try { + // Maps the affected files to new, not yet existing, files. + final Map movedFiles = new HashMap<>(); + if (renamedResource instanceof IContainer) { + final IPath oldPath = renamedResource.getFullPath(); + final IPath newPath = oldPath.removeLastSegments(1).append(newName); + final IWorkspaceRoot workspaceRoot = renamedResource.getWorkspace().getRoot(); + ((IContainer) renamedResource).accept(new IResourceProxyVisitor() { + @Override + public boolean visit(IResourceProxy proxy) throws CoreException { + if (proxy.isLinked()) + return false; + if (proxy.getType() == IResource.FILE) { + IFile file = (IFile) proxy.requestResource(); + IPath path = replacePrefix(file.getFullPath(), oldPath.segmentCount(), newPath); + movedFiles.put(file, workspaceRoot.getFile(path)); + return false; + } + return true; + } + }, IResource.NONE); + } else if (renamedResource instanceof IFile) { + IFile file = (IFile) renamedResource; + movedFiles.put(file, file.getParent().getFile(new Path(newName))); + } + + HeaderFileReferenceAdjuster includeAdjuster = new HeaderFileReferenceAdjuster(movedFiles); + change = includeAdjuster.createChange(context, pm); + } catch (CoreException e) { + return RefactoringStatus.create(e.getStatus()); + } + return RefactoringStatus.create(Status.OK_STATUS); + } + + @Override + public Change createPreChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { + pm.done(); + return change; + } + + @Override + public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { + pm.done(); + return null; + } + + @Override + public String getName() { + return RenameMessages.HeaderFileRenameParticipant_name; + } + + /** + * Replaces first few segments of the given path by the contents of another path. + * + * @param path the original path + * @param prefixLength the number of segments of {@code path} to replace + * @param newPrefix the replacement path + * @return the modified path + * @since 5.8 + */ + private static IPath replacePrefix(IPath path, int prefixLength, IPath newPrefix) { + return newPrefix.append(path.removeFirstSegments(prefixLength)); + } +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/RenameMessages.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/RenameMessages.java index 07482025cda..9b4d3a2d44e 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/RenameMessages.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/RenameMessages.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2004, 2010 Wind River Systems, Inc. + * Copyright (c) 2004, 2014 Wind River Systems, Inc. * 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 @@ -97,9 +97,11 @@ class RenameMessages extends NLS { public static String CRenameTopProcessor_virtualMethod; public static String CRenameTopProcessor_wizard_backup_title; public static String CRenameTopProcessor_wizard_title; + public static String HeaderFileMoveParticipant_name; + public static String HeaderFileRenameParticipant_name; public static String RenameCSourceFolderChange_ErrorMsg; public static String RenameCSourceFolderChange_Name0; - public static String RenameSourceFolder_0; + public static String SourceFolderRenameParticipant_name; public static String RenameInformationPopup_delayJobName; public static String RenameInformationPopup_EnterNewName; public static String RenameInformationPopup_menu; diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/RenameMessages.properties b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/RenameMessages.properties index e3c27856bd7..175d3e4a123 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/RenameMessages.properties +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/RenameMessages.properties @@ -1,5 +1,5 @@ ############################################################################### -# Copyright (c) 2005, 2010 IBM Corporation and others. +# Copyright (c) 2005, 2014 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 @@ -10,7 +10,7 @@ # Markus Schorn, Wind River Systems Inc. # Sergey Prigogin (Google) ############################################################################### -ASTManager_error_macro_name_conflict=''{0}'' conflicts with the name of an existing macro! +ASTManager_error_macro_name_conflict=''{0}'' conflicts with the name of an existing macro. ASTManager_subtask_analyzing=Analyzing {0} files ASTManager_task_analyze=Analyzing source code ASTManager_task_generateAst=Generating AST @@ -93,9 +93,11 @@ CRenameTopProcessor_type=type CRenameTopProcessor_virtualMethod=virtual method CRenameTopProcessor_wizard_backup_title=Rename CRenameTopProcessor_wizard_title=Rename ''{0}'' +HeaderFileMoveParticipant_name=Header File Move +HeaderFileRenameParticipant_name=Header File Rename RenameCSourceFolderChange_ErrorMsg=Folder {0} does not exist RenameCSourceFolderChange_Name0=Rename source folder {0} to {1} -RenameSourceFolder_0=Rename C/C++ Source Folder +SourceFolderRenameParticipant_name=Rename C/C++ Source Folder RenameInformationPopup_SnapTo=&Snap To RenameInformationPopup_snap_under_left=&Under Left RenameInformationPopup_snap_under_right=U&nder Right diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/RenameSourceFolder.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/SourceFolderRenameParticipant.java similarity index 79% rename from core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/RenameSourceFolder.java rename to core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/SourceFolderRenameParticipant.java index f7ea3a0be0b..d935a66d3ce 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/RenameSourceFolder.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/SourceFolderRenameParticipant.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009 Institute for Software, HSR Hochschule fuer Technik + * Copyright (c) 2009, 2014 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 @@ -20,37 +20,36 @@ import org.eclipse.core.runtime.Status; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; -import org.eclipse.ltk.core.refactoring.participants.RenameArguments; import org.eclipse.ltk.core.refactoring.participants.RenameParticipant; /** + * Updates source folders and associated filters of a C/C++ project in response to a folder rename. + * * @author Emanuel Graf IFS */ -public class RenameSourceFolder extends RenameParticipant { +public class SourceFolderRenameParticipant extends RenameParticipant { private IFolder oldFolder; - private String newName; - public RenameSourceFolder() { + public SourceFolderRenameParticipant() { } @Override public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) throws OperationCanceledException { - RenameArguments arg = getArguments(); - newName = arg.getNewName(); return RefactoringStatus.create(Status.OK_STATUS); } @Override public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { IPath oldFolderPath = oldFolder.getFullPath(); - IPath newFolderPath = oldFolder.getFullPath().uptoSegment(oldFolder.getFullPath().segmentCount() - 1).append(newName); + String newName = getArguments().getNewName(); + IPath newFolderPath = oldFolderPath.removeLastSegments(1).append(newName); return new RenameCSourceFolderChange(oldFolderPath, newFolderPath, oldFolder.getProject(), oldFolder); } @Override public String getName() { - return RenameMessages.RenameSourceFolder_0; + return RenameMessages.SourceFolderRenameParticipant_name; } @Override diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/PreferenceConstants.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/PreferenceConstants.java index 2d3b2075f8a..e4f12acf0aa 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/PreferenceConstants.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/PreferenceConstants.java @@ -2347,7 +2347,7 @@ public class PreferenceConstants { // Code Templates store.setDefault(PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME, - CODE_TEMPLATES_INCLUDE_GUARD_SCHEME_FILE_NAME); + CODE_TEMPLATES_INCLUDE_GUARD_SCHEME_FILE_PATH); // Name Style store.setDefault(NAME_STYLE_CONSTANT_CAPITALIZATION, NAME_STYLE_CAPITALIZATION_UPPER_CASE); diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/refactoring/CTextFileChange.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/refactoring/CTextFileChange.java index 4d245e9ddc3..c8cf7e6e968 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/refactoring/CTextFileChange.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/refactoring/CTextFileChange.java @@ -59,6 +59,8 @@ public class CTextFileChange extends TextFileChange { public CTextFileChange(String name, ITranslationUnit tu) { super(name, getFile(tu)); fTranslationUnit = tu; + if (tu instanceof IWorkingCopy) + fWorkingCopy = (IWorkingCopy) tu; setTextType(TEXT_TYPE); } @@ -66,7 +68,7 @@ public class CTextFileChange extends TextFileChange { protected IDocument acquireDocument(IProgressMonitor pm) throws CoreException { IDocument doc= super.acquireDocument(pm); if (++fAcquireCount == 1) { - if (fTranslationUnit instanceof TranslationUnit && fWorkingCopy == null) { + if (fWorkingCopy == null && fTranslationUnit instanceof TranslationUnit) { fWorkingCopy= ((TranslationUnit) fTranslationUnit).getWorkingCopy(null, DocumentAdapter.FACTORY); if (!fTranslationUnit.isOpen()) { fTranslationUnit.open(null); @@ -89,7 +91,7 @@ public class CTextFileChange extends TextFileChange { protected void releaseDocument(IDocument document, IProgressMonitor pm) throws CoreException { super.releaseDocument(document, pm); if (--fAcquireCount == 0) { - if (fWorkingCopy != null) { + if (fWorkingCopy != null && fWorkingCopy != fTranslationUnit) { fWorkingCopy.destroy(); fWorkingCopy= null; }