From deeb5db9aee08df4dbd2607429dcc98d85843be9 Mon Sep 17 00:00:00 2001 From: Sergey Prigogin Date: Mon, 4 May 2009 21:28:23 +0000 Subject: [PATCH] Extensive rewrite of Add Include. Bugs 236530 and 255952. --- .../core/dom/parser/ProblemBinding.java | 3 +- .../dom/parser/cpp/semantics/CPPVisitor.java | 13 +- .../utils/org/eclipse/cdt/utils/PathUtil.java | 118 ++- .../AddIncludesOperation.java | 144 ++-- .../editor/AddIncludeOnSelectionAction.java | 760 +++++++++++------- .../internal/ui/editor/CEditorMessages.java | 26 +- .../ui/editor/CEditorMessages.properties | 10 +- .../ConstructedCEditorMessages.properties | 5 +- 8 files changed, 691 insertions(+), 388 deletions(-) diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ProblemBinding.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ProblemBinding.java index d5975b3125e..539a53ca79b 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ProblemBinding.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ProblemBinding.java @@ -29,6 +29,7 @@ import org.eclipse.cdt.core.index.IIndexFileSet; import org.eclipse.cdt.core.parser.util.CharArrayUtils; import org.eclipse.cdt.internal.core.dom.Linkage; import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPSemantics; +import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor; import org.eclipse.cdt.internal.core.parser.ParserMessages; import org.eclipse.core.runtime.PlatformObject; @@ -230,7 +231,7 @@ public class ProblemBinding extends PlatformObject implements IProblemBinding, I } public IBinding getOwner() throws DOMException { - return null; + return node instanceof IASTName ? CPPVisitor.findNameOwner((IASTName) node, true) : null; } public void setASTNode(IASTName name) { diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java index cbe9944b9f6..4c4c0515e0a 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java @@ -2010,10 +2010,17 @@ public class CPPVisitor extends ASTQueries { while (node instanceof IASTName) { if (node instanceof ICPPASTQualifiedName) { IASTName[] qn= ((ICPPASTQualifiedName) node).getNames(); - if (qn.length < 2) - return null; - return qn[qn.length - 2].resolveBinding(); + int i = qn.length; + while (--i >= 0) { + if (qn[i] == name) { + break; + } + } + if (--i < 0) + break; + return qn[i].resolveBinding(); } + name = (IASTName) node; node= node.getParent(); } return findDeclarationOwner(node, allowFunction); diff --git a/core/org.eclipse.cdt.core/utils/org/eclipse/cdt/utils/PathUtil.java b/core/org.eclipse.cdt.core/utils/org/eclipse/cdt/utils/PathUtil.java index 91b10214c11..feee6716ac9 100644 --- a/core/org.eclipse.cdt.core/utils/org/eclipse/cdt/utils/PathUtil.java +++ b/core/org.eclipse.cdt.core/utils/org/eclipse/cdt/utils/PathUtil.java @@ -9,6 +9,7 @@ * QNX Software Systems - initial API and implementation * Markus Schorn (Wind River Systems) * Ed Swartz (Nokia) + * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.utils; @@ -23,6 +24,7 @@ import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; @@ -65,8 +67,8 @@ public class PathUtil { IWorkspaceRoot workspaceRoot = getWorkspaceRoot(); if (workspaceRoot != null) { IPath workspaceLocation = workspaceRoot.getLocation(); - if (workspaceLocation != null && workspaceLocation.isPrefixOf(fullPath)) { - int segments = fullPath.matchingFirstSegments(workspaceLocation); + if (workspaceLocation != null && isPrefix(workspaceLocation, fullPath)) { + int segments = matchingFirstSegments(fullPath, workspaceLocation); IPath relPath = fullPath.setDevice(null).removeFirstSegments(segments); return new Path("").addTrailingSeparator().append(relPath); //$NON-NLS-1$ } @@ -76,11 +78,11 @@ public class PathUtil { public static IPath getProjectRelativePath(IPath fullPath, IProject project) { IPath projectPath = project.getFullPath(); - if (projectPath.isPrefixOf(fullPath)) { + if (isPrefix(projectPath, fullPath)) { return fullPath.removeFirstSegments(projectPath.segmentCount()); } projectPath = project.getLocation(); - if (projectPath.isPrefixOf(fullPath)) { + if (isPrefix(projectPath, fullPath)) { return fullPath.removeFirstSegments(projectPath.segmentCount()); } return getWorkspaceRelativePath(fullPath); @@ -94,7 +96,7 @@ public class PathUtil { IWorkspaceRoot workspaceRoot = getWorkspaceRoot(); if (workspaceRoot != null && wsRelativePath != null) { IPath workspaceLocation = workspaceRoot.getLocation(); - if (workspaceLocation != null && !workspaceLocation.isPrefixOf(wsRelativePath)) { + if (workspaceLocation != null && !isPrefix(workspaceLocation, wsRelativePath)) { return workspaceLocation.append(wsRelativePath); } } @@ -102,7 +104,7 @@ public class PathUtil { } public static IPath makeRelativePath(IPath path, IPath relativeTo) { - int segments = relativeTo.matchingFirstSegments(path); + int segments = matchingFirstSegments(relativeTo, path); if (segments > 0) { IPath prefix = relativeTo.removeFirstSegments(segments); IPath suffix = path.removeFirstSegments(segments); @@ -131,8 +133,8 @@ public class PathUtil { int mostSegments = 0; for (int i = 0; i < includePaths.length; ++i) { IPath includePath = new Path(includePaths[i]); - if (includePath.isPrefixOf(fullPath)) { - int segments = includePath.matchingFirstSegments(fullPath); + if (isPrefix(includePath, fullPath)) { + int segments = includePath.segmentCount(); if (segments > mostSegments) { relativePath = fullPath.removeFirstSegments(segments).setDevice(null); mostSegments = segments; @@ -171,4 +173,104 @@ public class PathUtil { } return null; } + + /** + * Checks whether path1 is the same as path2. + * @return true if path1 is the same as path2, and false otherwise + * + * Similar to IPath.equals(Object obj), but takes case sensitivity of the file system + * into account. + * @since 5.1 + */ + public boolean equal(IPath path1, IPath path2) { + // Check leading separators + if (path1.isAbsolute() != path2.isAbsolute() || path1.isUNC() != path2.isUNC()) { + return false; + } + int i = path1.segmentCount(); + // Check segment count + if (i != path2.segmentCount()) + return false; + // Check segments in reverse order - later segments more likely to differ + while (--i >= 0) { + if (!path1.segment(i).equals(path2.segment(i))) + return false; + } + // Check device last (least likely to differ) + if (path1.getDevice() == null) { + return path2.getDevice() == null; + } else { + return path1.getDevice().equalsIgnoreCase(path2.getDevice()); + } + } + + /** + * Checks whether path1 is a prefix of path2. To be a prefix, path1's segments + * must appear in path1 in the same order, and their device ids must match. + *

+ * An empty path is a prefix of all paths with the same device; a root path is a prefix of + * all absolute paths with the same device. + *

+ * @return true if path1 is a prefix of path2, and false otherwise + * + * Similar to IPath.isPrefixOf(IPath anotherPath), but takes case sensitivity of the file system + * into account. + * @since 5.1 + */ + public static boolean isPrefix(IPath path1, IPath path2) { + if (path1.getDevice() == null) { + if (path2.getDevice() != null) { + return false; + } + } else { + if (!path1.getDevice().equalsIgnoreCase(path2.getDevice())) { + return false; + } + } + if (path1.isEmpty() || (path1.isRoot() && path2.isAbsolute())) { + return true; + } + int len1 = path1.segmentCount(); + if (len1 > path2.segmentCount()) { + return false; + } + boolean caseSensitive = !isWindowsFileSystem(); + for (int i = 0; i < len1; i++) { + if (!(caseSensitive ? + path1.segment(i).equals(path2.segment(i)) : + path1.segment(i).equalsIgnoreCase(path2.segment(i)))) { + return false; + } + } + return true; + } + + /** + * Returns the number of segments which match in path1 and path2 + * (device ids are ignored), comparing in increasing segment number order. + * + * @return the number of matching segments + + * Similar to IPath.matchingFirstSegments(IPath anotherPath), but takes case sensitivity + * of the file system into account. + * @since 5.1 + */ + public static int matchingFirstSegments(IPath path1, IPath path2) { + Assert.isNotNull(path1); + Assert.isNotNull(path2); + int len1 = path1.segmentCount(); + int len2 = path2.segmentCount(); + int max = Math.min(len1, len2); + int count = 0; + boolean caseSensitive = !isWindowsFileSystem(); + for (int i = 0; i < max; i++) { + if (!(caseSensitive ? + path1.segment(i).equals(path2.segment(i)) : + path1.segment(i).equalsIgnoreCase(path2.segment(i)))) { + return count; + } + count++; + } + return count; + } } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/AddIncludesOperation.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/AddIncludesOperation.java index 6e5b3426872..d4600020e3f 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/AddIncludesOperation.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/codemanipulation/AddIncludesOperation.java @@ -8,6 +8,7 @@ * Contributors: * IBM Corporation - initial API and implementation * QNX Software Systems + * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.internal.corext.codemanipulation; @@ -24,6 +25,8 @@ import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.IBuffer; @@ -43,26 +46,27 @@ import org.eclipse.cdt.internal.ui.editor.CEditorMessages; * If the translation unit is open in an editor, be sure to pass over its working copy. */ public class AddIncludesOperation implements IWorkspaceRunnable { - private ITranslationUnit fTranslationUnit; private IRequiredInclude[] fIncludes; private String[] fUsings; private final String fNewLine; + private IRegion insertedIncludes; /** * Generate include statements for the passed java elements */ public AddIncludesOperation(ITranslationUnit tu, IRequiredInclude[] includes, boolean save) { - this (tu, includes, null, save); + this(tu, includes, null, save); } /** * Generate include statements for the passed c elements */ - public AddIncludesOperation(ITranslationUnit tu, IRequiredInclude[] includes, String[] using, boolean save) { + public AddIncludesOperation(ITranslationUnit tu, IRequiredInclude[] includes, String[] usings, + boolean save) { super(); fIncludes= includes; - fUsings = using; + fUsings = usings; fTranslationUnit = tu; fNewLine= getNewLine(tu); } @@ -82,10 +86,10 @@ public class AddIncludesOperation implements IWorkspaceRunnable { } catch (CModelException e) { } catch (BadLocationException e) { } - return System.getProperty("line.separator", "\n"); //$NON-NLS-1$//$NON-NLS-2$ } + return System.getProperty("line.separator", "\n"); //$NON-NLS-1$//$NON-NLS-2$ } - public void executeIncludes(IProgressMonitor monitor) throws CoreException { + private void insertIncludes(IProgressMonitor monitor) throws CoreException { // Sanity if (fIncludes == null || fIncludes.length == 0) { return; @@ -96,122 +100,118 @@ public class AddIncludesOperation implements IWorkspaceRunnable { monitor.beginTask(CEditorMessages.AddIncludesOperation_description, 2); - List elements = fTranslationUnit.getChildrenOfType(ICElement.C_INCLUDE); - for (int i = 0; i < fIncludes.length; ++i) { - String name = fIncludes[i].getIncludeName(); + List elements = fTranslationUnit.getChildrenOfType(ICElement.C_INCLUDE); + for (IRequiredInclude include : fIncludes) { + String name = include.getIncludeName(); boolean found = false; - for (int j = 0; j < elements.size(); ++j) { - IInclude include = (IInclude)elements.get(j); - if (name.equals(include.getElementName())) { + for (ICElement element : elements) { + if (name.equals(element.getElementName())) { found = true; break; } } if (!found) { - toAdd.add(fIncludes[i]); + toAdd.add(include); } } - if (toAdd.size() > 0) { + if (!toAdd.isEmpty()) { // So we have our list. Now insert. - StringBuffer insert = new StringBuffer(""); //$NON-NLS-1$ - for(int j = 0; j < toAdd.size(); j++) { - IRequiredInclude req = toAdd.get(j); - if (req.isStandard()) { - insert.append("#include <" + req.getIncludeName() + ">").append(fNewLine); //$NON-NLS-1$ //$NON-NLS-2$ + StringBuilder buf = new StringBuilder(); + for (IRequiredInclude include : toAdd) { + if (include.isStandard()) { + buf.append("#include <" + include.getIncludeName() + ">").append(fNewLine); //$NON-NLS-1$ //$NON-NLS-2$ } else { - insert.append("#include \"" + req.getIncludeName() + "\"").append(fNewLine); //$NON-NLS-1$ //$NON-NLS-2$ + buf.append("#include \"" + include.getIncludeName() + "\"").append(fNewLine); //$NON-NLS-1$ //$NON-NLS-2$ } } - int pos; - if (elements.size() > 0) { - IInclude lastInclude = (IInclude)elements.get(elements.size() - 1); + int pos = 0; + if (!elements.isEmpty()) { + IInclude lastInclude = (IInclude) elements.get(elements.size() - 1); ISourceRange range = lastInclude.getSourceRange(); pos = range.getStartPos() + range.getLength(); - } else { - pos = 0; } monitor.worked(1); - replace(pos, insert.toString()); + replace(pos, buf.toString()); + insertedIncludes = new Region(pos, buf.length()); monitor.worked(1); } } } - public void executeUsings(IProgressMonitor monitor) throws CoreException { + private void insertUsings(IProgressMonitor monitor) throws CoreException { // Sanity if (fUsings == null || fUsings.length == 0) { return; } if (fTranslationUnit != null) { - ArrayList toAdd = new ArrayList(); - + ArrayList toAdd = new ArrayList(fUsings.length); + monitor.beginTask(CEditorMessages.AddIncludesOperation_description, 2); - - List elements = fTranslationUnit.getChildrenOfType(ICElement.C_USING); - for (int i = 0; i < fUsings.length; ++i) { - String name = fUsings[i]; + + List elements = fTranslationUnit.getChildrenOfType(ICElement.C_USING); + for (String name : fUsings) { boolean found = false; - for (int j = 0; j < elements.size(); ++j) { - IUsing using = (IUsing)elements.get(j); - if (name.equals(using.getElementName())) { + for (ICElement element : elements) { + if (name.equals(element.getElementName())) { found = true; break; } } if (!found) { - toAdd.add(fUsings[i]); + toAdd.add(name); } } - - if (toAdd.size() > 0) { + + if (!toAdd.isEmpty()) { // So we have our list. Now insert. - StringBuffer insert = new StringBuffer(""); //$NON-NLS-1$ - for(int j = 0; j < toAdd.size(); j++) { - String using = toAdd.get(j); - insert.append("using namespace " + using + ";").append(fNewLine); //$NON-NLS-1$ //$NON-NLS-2$ + StringBuilder buf = new StringBuilder(); + for (String using : toAdd) { + buf.append("using ").append(using).append(';').append(fNewLine); //$NON-NLS-1$ } - - int pos; - List includes = fTranslationUnit.getChildrenOfType(ICElement.C_INCLUDE); - if (includes.size() > 0) { - IInclude lastInclude = (IInclude)includes.get(includes.size() - 1); - ISourceRange range = lastInclude.getSourceRange(); - pos = range.getStartPos() + range.getLength(); - } else if (elements.size() > 0) { - IUsing lastUsing = (IUsing)includes.get(includes.size() - 1); + + int pos = 0; + if (!elements.isEmpty()) { + IUsing lastUsing = (IUsing) elements.get(elements.size() - 1); ISourceRange range = lastUsing.getSourceRange(); pos = range.getStartPos() + range.getLength(); } else { - pos = 0; + List includes = fTranslationUnit.getChildrenOfType(ICElement.C_INCLUDE); + if (!includes.isEmpty()) { + IInclude lastInclude = (IInclude) includes.get(includes.size() - 1); + ISourceRange range = lastInclude.getSourceRange(); + pos = range.getStartPos() + range.getLength(); + } + if (!includes.isEmpty() || insertedIncludes != null) { + buf.insert(0, fNewLine); + } } - + if (insertedIncludes != null && pos >= insertedIncludes.getOffset()) { + pos += insertedIncludes.getLength(); + } + monitor.worked(1); - replace(pos, insert.toString()); + replace(pos, buf.toString()); monitor.worked(1); } } } - void replace(int pos, String s) { - try { - IBuffer buffer = fTranslationUnit.getBuffer(); - // Now find the next newline and insert after that - if (pos > 0) { - while (buffer.getChar(pos) != '\n') { - pos++; - } - if (buffer.getChar(pos) == '\r') { - pos++; - } + private void replace(int pos, String s) throws CModelException { + IBuffer buffer = fTranslationUnit.getBuffer(); + // Now find the next newline and insert after that + if (pos > 0) { + while (buffer.getChar(pos) != '\n') { pos++; } - buffer.replace(pos, 0, s); - } catch (Exception e) { - // ignore; should we log ? + if (buffer.getChar(pos) == '\r') { + pos++; + } + pos++; } + buffer.replace(pos, 0, s); } public void run(IProgressMonitor monitor) throws CoreException { @@ -219,8 +219,8 @@ public class AddIncludesOperation implements IWorkspaceRunnable { monitor= new NullProgressMonitor(); } try { - executeUsings(monitor); - executeIncludes(monitor); + insertIncludes(monitor); + insertUsings(monitor); } finally { monitor.done(); } @@ -229,7 +229,7 @@ public class AddIncludesOperation implements IWorkspaceRunnable { /** * @return Returns the scheduling rule for this operation */ - public ISchedulingRule getScheduleRule() { + public ISchedulingRule getSchedulingRule() { return ResourcesPlugin.getWorkspace().getRoot(); } } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/AddIncludeOnSelectionAction.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/AddIncludeOnSelectionAction.java index 8f8aed567cd..9dcc7c41bda 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/AddIncludeOnSelectionAction.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/AddIncludeOnSelectionAction.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. + * Copyright (c) 2000, 2009 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 @@ -9,341 +9,406 @@ * IBM Corporation - initial API and implementation * QNX Software Systems * Markus Schorn (Wind River Systems) + * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.internal.ui.editor; - +import java.io.File; import java.lang.reflect.InvocationTargetException; +import java.net.URI; import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +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.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.window.Window; +import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.IEditorInput; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.ElementListSelectionDialog; import org.eclipse.ui.texteditor.ITextEditor; -import org.eclipse.ui.texteditor.IUpdate; +import org.eclipse.ui.texteditor.TextEditorAction; import org.eclipse.cdt.core.CCorePlugin; -import org.eclipse.cdt.core.browser.IQualifiedTypeName; -import org.eclipse.cdt.core.browser.QualifiedTypeName; +import org.eclipse.cdt.core.dom.ast.DOMException; +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.core.dom.ast.IASTNodeSelector; +import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; +import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.ICompositeType; import org.eclipse.cdt.core.dom.ast.IEnumeration; import org.eclipse.cdt.core.dom.ast.IFunction; +import org.eclipse.cdt.core.dom.ast.IProblemBinding; +import org.eclipse.cdt.core.dom.ast.IType; import org.eclipse.cdt.core.dom.ast.ITypedef; import org.eclipse.cdt.core.dom.ast.cpp.ICPPBinding; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespace; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPVariable; import org.eclipse.cdt.core.index.IIndex; import org.eclipse.cdt.core.index.IIndexBinding; -import org.eclipse.cdt.core.index.IIndexManager; +import org.eclipse.cdt.core.index.IIndexFile; +import org.eclipse.cdt.core.index.IIndexInclude; +import org.eclipse.cdt.core.index.IIndexMacro; import org.eclipse.cdt.core.index.IIndexName; import org.eclipse.cdt.core.index.IndexFilter; +import org.eclipse.cdt.core.model.ILanguage; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.core.parser.IScannerInfo; import org.eclipse.cdt.core.parser.IScannerInfoProvider; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.IFunctionSummary; import org.eclipse.cdt.ui.IRequiredInclude; -import org.eclipse.cdt.ui.browser.typeinfo.TypeInfoLabelProvider; import org.eclipse.cdt.ui.text.ICHelpInvocationContext; +import org.eclipse.cdt.ui.text.SharedASTJob; import org.eclipse.cdt.utils.PathUtil; +import org.eclipse.cdt.internal.core.dom.parser.ProblemBinding; +import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor; +import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil; +import org.eclipse.cdt.internal.core.resources.ResourceLookup; import org.eclipse.cdt.internal.corext.codemanipulation.AddIncludesOperation; import org.eclipse.cdt.internal.ui.CHelpProviderManager; import org.eclipse.cdt.internal.ui.ICHelpContextIds; import org.eclipse.cdt.internal.ui.actions.WorkbenchRunnableAdapter; -import org.eclipse.cdt.internal.ui.text.CWordFinder; import org.eclipse.cdt.internal.ui.util.ExceptionHandler; -// TODO this is a big TODO. -public class AddIncludeOnSelectionAction extends Action implements IUpdate { - - private ITextEditor fEditor; +/** + * Adds an include statement and, optionally, a 'using' declaration for the currently + * selected name. + */ +public class AddIncludeOnSelectionAction extends TextEditorAction { + private ITranslationUnit fTu; + private IProject fProject; + private String[] fIncludePath; private IRequiredInclude[] fRequiredIncludes; - private String[] fUsings; - - class RequiredIncludes implements IRequiredInclude { - String name; - boolean isStandard; - - RequiredIncludes(String n) { - name = n; - isStandard = true; - } - - RequiredIncludes(String n, boolean isStandard) { - name = n; - this.isStandard = isStandard; - } - - /* (non-Javadoc) - * @see org.eclipse.cdt.ui.IRequiredInclude#getIncludeName() - */ - public String getIncludeName() { - return name; - } - - /* (non-Javadoc) - * @see org.eclipse.cdt.ui.IRequiredInclude#isStandard() - */ - public boolean isStandard() { - return isStandard; - } - } + private String[] fUsingDeclarations; public AddIncludeOnSelectionAction(ITextEditor editor) { - super(CEditorMessages.AddIncludeOnSelection_label); - setToolTipText(CEditorMessages.AddIncludeOnSelection_tooltip); - setDescription(CEditorMessages.AddIncludeOnSelection_description); + super(CEditorMessages.getBundleForConstructedKeys(), "AddIncludeOnSelection.", editor); //$NON-NLS-1$ - fEditor= editor; - CUIPlugin.getDefault().getWorkbench().getHelpSystem().setHelp(this, ICHelpContextIds.ADD_INCLUDE_ON_SELECTION_ACTION); + CUIPlugin.getDefault().getWorkbench().getHelpSystem().setHelp(this, + ICHelpContextIds.ADD_INCLUDE_ON_SELECTION_ACTION); } - private void addInclude(ITranslationUnit tu) { - AddIncludesOperation op= new AddIncludesOperation(tu, fRequiredIncludes, fUsings, false); + private void insertInclude(IRequiredInclude[] includes, String[] usings) { + AddIncludesOperation op= new AddIncludesOperation(fTu, includes, usings, false); try { PlatformUI.getWorkbench().getProgressService().runInUI( PlatformUI.getWorkbench().getProgressService(), - new WorkbenchRunnableAdapter(op), op.getScheduleRule()); + new WorkbenchRunnableAdapter(op), op.getSchedulingRule()); } catch (InvocationTargetException e) { - ExceptionHandler.handle(e, getShell(), CEditorMessages.AddIncludeOnSelection_error_message1, null); + ExceptionHandler.handle(e, getShell(), CEditorMessages.AddIncludeOnSelection_error_title, + CEditorMessages.AddIncludeOnSelection_insertion_failed); } catch (InterruptedException e) { // Do nothing. Operation has been canceled. } } - protected ITranslationUnit getTranslationUnit () { - ITranslationUnit unit = null; - if (fEditor != null) { - IEditorInput editorInput= fEditor.getEditorInput(); - unit = CUIPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editorInput); + private static ITranslationUnit getTranslationUnit(ITextEditor editor) { + if (editor == null) { + return null; } - return unit; + return CUIPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editor.getEditorInput()); } private Shell getShell() { - return fEditor.getSite().getShell(); + return getTextEditor().getSite().getShell(); } @Override public void run() { - ITranslationUnit tu= getTranslationUnit(); - if (tu == null) { + fTu = getTranslationUnit(getTextEditor()); + if (fTu == null) { return; } - IIndex index; + fProject = fTu.getCProject().getProject(); + IScannerInfoProvider provider = CCorePlugin.getDefault().getScannerInfoProvider(fProject); + fIncludePath = null; + if (provider != null) { + IScannerInfo info = provider.getScannerInformation(fTu.getResource()); + if (info != null) { + fIncludePath = info.getIncludePaths(); + } + } + if (fIncludePath == null) { + fIncludePath = new String[0]; + } + try { - index = CCorePlugin.getIndexManager().getIndex(tu.getCProject(), IIndexManager.ADD_DEPENDENCIES); - index.acquireReadLock(); - try { - extractIncludes(fEditor, index); - addInclude(tu); - } finally { - index.releaseReadLock(); + final ISelection selection= getTextEditor().getSelectionProvider().getSelection(); + if (selection.isEmpty() || !(selection instanceof ITextSelection)) { + return; + } + if (!validateEditorInputState()) { + return; + } + + final String[] lookupName = new String[1]; + + SharedASTJob job = new SharedASTJob(CEditorMessages.AddIncludeOnSelection_label, fTu) { + @Override + public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) throws CoreException { + deduceInclude((ITextSelection) selection, ast, lookupName); + return Status.OK_STATUS; + } + }; + job.schedule(); + job.join(); + + if (fRequiredIncludes == null || fRequiredIncludes.length == 0 && lookupName[0].length() > 0) { + // Try contribution from plugins. + IFunctionSummary fs = findContribution(lookupName[0]); + if (fs != null) { + fRequiredIncludes = fs.getIncludes(); + String ns = fs.getNamespace(); + if (ns != null && ns.length() > 0) { + fUsingDeclarations = new String[] { fs.getNamespace() }; + } + } + + } + if (fRequiredIncludes != null && fRequiredIncludes.length >= 0) { + insertInclude(fRequiredIncludes, fUsingDeclarations); } - } catch (CoreException e) { - CUIPlugin.log(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } - fUsings = null; - fRequiredIncludes = null; } - /** - * To be used by ElementListSelectionDialog for user to choose which declarations/ - * definitions for "add include" when there are more than one to choose from. - */ - private static class DisplayName extends Object { - private IIndexName name; - private IIndexBinding binding; - - public DisplayName(IIndexName name, IIndexBinding binding) { - this.name = name; - this.binding= binding; - } - - @Override - public String toString() { - try { - if (binding != null) { - return getBindingQualifiedName(binding) + " - " + name.getFileLocation().getFileName(); //$NON-NLS-1$ - } - return null; - } catch (CoreException e) { - CUIPlugin.log(e); - return null; - } - } - - public IIndexName getName() { - return name; - } - - public IIndexBinding getBinding() { - return binding; - } - } - /** * Extract the includes for the given selection. This can be both used to perform - * the work as well as being invoked when there is a change. The actual results - * can and should be cached as the lookup process could be potentially costly. + * the work as well as being invoked when there is a change. * @param index */ - private void extractIncludes(ITextEditor editor, IIndex index) { - if (editor == null) { + private void deduceInclude(ITextSelection selection, IASTTranslationUnit ast, String[] lookupName) + throws CoreException { + IASTNodeSelector selector = ast.getNodeSelector(fTu.getLocation().toOSString()); + IASTName name = selector.findEnclosingName(selection.getOffset(), selection.getLength()); + if (name == null) { return; } + char[] nameChars = name.toCharArray(); + IBinding binding = name.resolveBinding(); + try { + if (binding instanceof ICPPVariable) { + IType type = ((ICPPVariable) binding).getType(); + type = SemanticUtil.getNestedType(type, + SemanticUtil.CVQ | SemanticUtil.PTR | SemanticUtil.ARRAY | SemanticUtil.REF); + if (type instanceof IBinding) { + binding = (IBinding) type; + nameChars = binding.getNameCharArray(); + } + } + } catch (DOMException e) { + CUIPlugin.log(e); + } + if (nameChars.length == 0) { + return; + } + + IIndex index = ast.getIndex(); + final IndexFilter filter = IndexFilter.getDeclaredBindingFilter(ast.getLinkage().getLinkageID(), false); + IIndexBinding[] bindings= index.findBindings(nameChars, false, filter, new NullProgressMonitor()); + final Map candidatesMap= new HashMap(); + for (IIndexBinding indexBinding : bindings) { + IIndexName[] definitions= null; + // class, struct, union, enum + if (indexBinding instanceof ICompositeType || indexBinding instanceof IEnumeration) { + definitions= index.findDefinitions(indexBinding); + } else if (indexBinding instanceof ITypedef || + (indexBinding instanceof IFunction && !(indexBinding instanceof ICPPMethod))) { + definitions= index.findDeclarations(indexBinding); + } + if (definitions != null) { + for (IIndexName definition : definitions) { + considerForInclusion(definition, indexBinding, index, candidatesMap); + } + } + } + IIndexMacro[] macros = index.findMacros(nameChars, filter, new NullProgressMonitor()); + for (IIndexMacro macro : macros) { + IIndexName definition = macro.getDefinition(); + considerForInclusion(definition, macro, index, candidatesMap); + } - ISelection s= editor.getSelectionProvider().getSelection(); - IDocument doc= editor.getDocumentProvider().getDocument(editor.getEditorInput()); - - if (s.isEmpty() || !(s instanceof ITextSelection) || doc == null) { - return; - } - - ITextSelection selection= (ITextSelection) s; - try { - IRegion region = CWordFinder.findWord(doc, selection.getOffset()); - if (region == null || region.getLength() == 0) { - return; - } - String name = doc.get(region.getOffset(), region.getLength()); - if (name.length() == 0) { - return; - } - - // Try contribution from plugins. - IFunctionSummary fs = findContribution(name); - if (fs != null) { - fRequiredIncludes = fs.getIncludes(); - String ns = fs.getNamespace(); - if (ns != null && ns.length() > 0) { - fUsings = new String[] { fs.getNamespace() }; - } - } - - try { - IndexFilter filter= IndexFilter.ALL_DECLARED_OR_IMPLICIT; - IIndexBinding[] bindings= index.findBindings(name.toCharArray(), false, filter, - new NullProgressMonitor()); - ArrayList pdomNames= new ArrayList(); - for (int i = 0; i < bindings.length; ++i) { - IIndexBinding binding= bindings[i]; - IIndexName[] defs= null; - // class, struct union, enumeration - if (binding instanceof ICompositeType || binding instanceof IEnumeration) { - defs= index.findDefinitions(binding); - } else if (binding instanceof ITypedef || binding instanceof IFunction) { - defs= index.findDeclarations(binding); - } - if (defs != null) { - for (IIndexName def : defs) { - pdomNames.add(new DisplayName(def, binding)); - } - } - } - - if (pdomNames.size() > 1) { - ElementListSelectionDialog dialog= new ElementListSelectionDialog(getShell(), new TypeInfoLabelProvider(TypeInfoLabelProvider.SHOW_NAME_ONLY)); - dialog.setElements(pdomNames.toArray()); + final ArrayList candidates = new ArrayList(candidatesMap.values()); + if (candidates.size() > 1) { + runInUIThread(new Runnable() { + public void run() { + ElementListSelectionDialog dialog= + new ElementListSelectionDialog(getShell(), new LabelProvider()); + dialog.setElements(candidates.toArray()); dialog.setTitle(CEditorMessages.AddIncludeOnSelection_label); dialog.setMessage(CEditorMessages.AddIncludeOnSelection_description); if (dialog.open() == Window.OK) { - //get selection - Object[] selects = dialog.getResult(); - - fRequiredIncludes = new IRequiredInclude[selects.length]; - List usings = new ArrayList(selects.length); - for (int i = 0; i < fRequiredIncludes.length; i++) { - IRequiredInclude include = getRequiredInclude( - ((DisplayName)selects[i]).getName().getFileLocation().getFileName(), getTranslationUnit()); - if (include != null) { - fRequiredIncludes[i] = include; - IIndexBinding binding = ((DisplayName)selects[i]).getBinding(); - if (binding instanceof ICPPBinding) - { - //find the enclosing namespace, if there's one - IQualifiedTypeName qualifiedName = new QualifiedTypeName(getBindingQualifiedName(binding)); - String qualifiedEnclosingName = (new QualifiedTypeName(qualifiedName.getEnclosingNames())).getFullyQualifiedName(); - if (!qualifiedEnclosingName.equals("")) //$NON-NLS-1$ - usings.add(qualifiedEnclosingName); - } - } - } - if (usings.size() > 0) - { - fUsings = new String[usings.size()]; - for (int i = 0; i < usings.size(); i++) - { - fUsings[i] = usings.get(i); - } - } - } - } else if (pdomNames.size() == 1) { - // we should use the IIndexName.getLocation here rather than getFileLocation - String fileName = (pdomNames.get(0)).getName().getFileLocation().getFileName(); - fRequiredIncludes = new IRequiredInclude[] {getRequiredInclude(fileName, getTranslationUnit())}; - IIndexBinding binding = (pdomNames.get(0)).getBinding(); - - if (binding instanceof ICPPBinding) { - //find the enclosing namespace, if there's one - IQualifiedTypeName qualifiedName = new QualifiedTypeName(getBindingQualifiedName(binding)); - String qualifiedEnclosingName = new QualifiedTypeName(qualifiedName.getEnclosingNames()).getFullyQualifiedName(); - if (!qualifiedEnclosingName.equals("")) //$NON-NLS-1$ - fUsings = new String[] {qualifiedEnclosingName}; + candidates.clear(); + candidates.add((IncludeCandidate) dialog.getFirstResult()); } } - } catch (CoreException e) { - e.printStackTrace(); - } - - // Try the type caching. - if (fRequiredIncludes == null && fUsings == null) { - } + }); + } - // Do a full search - if (fRequiredIncludes == null && fUsings == null) { + fRequiredIncludes = null; + fUsingDeclarations = null; + if (candidates.size() == 1) { + IncludeCandidate candidate = candidates.get(0); + fRequiredIncludes = new IRequiredInclude[] { candidate.getInclude() }; + IIndexBinding indexBinding = candidate.getBinding(); + + if (indexBinding instanceof ICPPBinding && !(indexBinding instanceof IIndexMacro)) { + // Decide what 'using' declaration should be added along with the include. + if (binding == null) { + binding = new ProblemBinding(name, IProblemBinding.SEMANTIC_NAME_NOT_FOUND); + } + String usingDeclaration = deduceUsingDeclaration(binding, indexBinding); + if (usingDeclaration != null) + fUsingDeclarations = new String[] { usingDeclaration }; } - } catch (BadLocationException e) { - MessageDialog.openError(getShell(), CEditorMessages.AddIncludeOnSelection_error_message3, - CEditorMessages.AddIncludeOnSelection_error_message4 + e.getMessage()); } } - private IFunctionSummary findContribution (final String name) { + /** + * Adds an include candidate to the candidates map if the file containing + * the definition is suitable for inclusion. + */ + private void considerForInclusion(IIndexName definition, IIndexBinding binding, + IIndex index, Map candidates) throws CoreException { + IIndexFile file = definition.getFile(); + // Consider the file for inclusion only if it is not a source file, + // or a source file that was already included by some other file. + if (!isSource(getPath(file)) || index.findIncludedBy(file, 0).length > 0) { + IIndexFile representativeFile = getRepresentativeFile(file, index); + IRequiredInclude include = getRequiredInclude(representativeFile, index); + IncludeCandidate candidate = new IncludeCandidate(binding, include); + if (!candidates.containsKey(candidate.toString())) { + candidates.put(candidate.toString(), candidate); + } + } + } + + private String deduceUsingDeclaration(IBinding source, IBinding target) { + if (source.equals(target)) { + return null; // No using declaration is needed. + } + ArrayList targetChain = getUsingChain(target); + if (targetChain.size() <= 1) { + return null; // Target is not in a namespace + } + ArrayList sourceChain = getUsingChain(source); + if (sourceChain.size() >= targetChain.size()) { + int j = targetChain.size(); + for (int i = sourceChain.size(); --j >= 1 && --i >= 1;) { + if (!sourceChain.get(i).equals(targetChain.get(j))) { + break; + } + } + if (j <= 0) { + return null; // Source is in the target's namespace + } + } + StringBuilder buf = new StringBuilder(); + for (int i = targetChain.size(); --i >= 0;) { + if (buf.length() > 0) { + buf.append("::"); //$NON-NLS-1$ + } + buf.append(targetChain.get(i)); + } + return buf.toString(); + } + + private ArrayList getUsingChain(IBinding binding) { + ArrayList chain = new ArrayList(4); + try { + for (; binding != null; binding = binding.getOwner()) { + String name = binding.getName(); + if (binding instanceof ICPPNamespace) { + if (name.length() == 0) { + continue; + } + } else { + chain.clear(); + } + chain.add(name); + } + } catch (DOMException e) { + CUIPlugin.log(e); + } + return chain; + } + + /** + * Given a header file, decides if this header file should be included directly or + * through another header file. For example, bits/stl_map.h is not supposed + * to be included directly, but should be represented by map. + * @return the header file to include. + */ + private IIndexFile getRepresentativeFile(IIndexFile headerFile, IIndex index) { + try { + if (ResourceLookup.findFilesForLocationURI(headerFile.getLocation().getURI()).length > 0) { + return headerFile; + } + // TODO(sprigogin): Change to ArrayDeque when Java 5 support is no longer needed. + LinkedList front = new LinkedList(); + front.add(headerFile); + HashSet processed = new HashSet(); + processed.add(headerFile); + while (!front.isEmpty()) { + IIndexFile file = front.remove(); + // A header without an extension is a good candidate for inclusion into a C++ source file. + if (fTu.isCXXLanguage() && !hasExtension(getPath(file))) { + return file; + } + IIndexInclude[] includes = index.findIncludedBy(file, 0); + for (IIndexInclude include : includes) { + IIndexFile includer = include.getIncludedBy(); + if (!processed.contains(includer)) { + URI uri = includer.getLocation().getURI(); + if (isSource(uri.getPath()) || ResourceLookup.findFilesForLocationURI(uri).length > 0) { + return file; + } + front.add(includer); + processed.add(includer); + } + } + } + } catch (CoreException e) { + CUIPlugin.log(e); + } + return headerFile; + } + + private boolean hasExtension(String path) { + return path.indexOf('.', path.lastIndexOf('/') + 1) >= 0; + } + + private IFunctionSummary findContribution(final String name) { final IFunctionSummary[] fs = new IFunctionSummary[1]; IRunnableWithProgress op = new IRunnableWithProgress() { public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { ICHelpInvocationContext context = new ICHelpInvocationContext() { - public IProject getProject() { - ITranslationUnit u = getTranslationUnit(); - if (u != null) { - return u.getCProject().getProject(); - } - return null; + return fProject; } public ITranslationUnit getTranslationUnit() { - return AddIncludeOnSelectionAction.this.getTranslationUnit(); + return fTu; } }; @@ -353,91 +418,204 @@ public class AddIncludeOnSelectionAction extends Action implements IUpdate { try { PlatformUI.getWorkbench().getProgressService().busyCursorWhile(op); } catch (InvocationTargetException e) { - ExceptionHandler.handle(e, getShell(), CEditorMessages.AddIncludeOnSelection_error_message1, null); + ExceptionHandler.handle(e, getShell(), CEditorMessages.AddIncludeOnSelection_error_title, + CEditorMessages.AddIncludeOnSelection_help_provider_error); } catch (InterruptedException e) { // Do nothing. Operation has been canceled. } return fs[0]; } - public void setContentEditor(ITextEditor editor) { - fEditor= editor; + private void runInUIThread(Runnable runnable) { + if (Display.getCurrent() != null) { + runnable.run(); + } else { + Display.getDefault().syncExec(runnable); + } } - + + @Override public void update() { - setEnabled(getTranslationUnit() != null); + ITextEditor editor = getTextEditor(); + setEnabled(editor != null && getTranslationUnit(editor) != null); + } + + /** + * Checks if a file is a source file (.c, .cpp, .cc, etc). Header files are not considered source files. + * @return Returns true if the the file is a source file. + */ + private boolean isSource(String filename) { + IContentType ct= CCorePlugin.getContentType(fProject, filename); + if (ct != null) { + String id = ct.getId(); + if (CCorePlugin.CONTENT_TYPE_CSOURCE.equals(id) || CCorePlugin.CONTENT_TYPE_CXXSOURCE.equals(id)) { + return true; + } + } + return false; + } + + private static String getPath(IIndexFile file) throws CoreException { + return file.getLocation().getURI().getPath(); } /** * Returns the RequiredInclude object to be added to the include list * @param path - the full path of the file to include - * @param tu - the translation unit which requires the include * @return the required include + * @throws CoreException */ - private IRequiredInclude getRequiredInclude(String path, ITranslationUnit tu) { - if (path != null) { - IPath typeLocation = new Path(path); - IProject project = tu.getCProject().getProject(); - IPath projectLocation = project.getLocation(); - IPath workspaceLocation = project.getWorkspace().getRoot().getLocation(); - IPath headerLocation = tu.getResource().getLocation(); - boolean isSystemIncludePath = false; + private IRequiredInclude getRequiredInclude(IIndexFile file, IIndex index) throws CoreException { + IIndexInclude[] includes; + includes = index.findIncludedBy(file); + if (includes.length > 0) { + // Let the existing includes vote. To be eligible to vote, an include + // has to be resolvable in the context of the current translation unit. + int systemIncludeVotes = 0; + String[] ballotBox = new String[includes.length]; + for (int i = 0; i < includes.length; i++) { + IIndexInclude include = includes[i]; + if (isResolvable(include)) { + ballotBox[i] = include.getFullName(); + if (include.isSystemInclude()) { + systemIncludeVotes++; + } + } + } + Arrays.sort(ballotBox); + String contender = ballotBox[0]; + int votes = 1; + String winner = contender; + int winnerVotes = votes; + for (int i = 1; i < ballotBox.length; i++) { + if (!ballotBox[i].equals(contender)) { + contender = ballotBox[i]; + votes = 1; + } + votes++; + if (votes > winnerVotes) { + winner = contender; + winnerVotes = votes; + } + } + return new RequiredInclude(winner, systemIncludeVotes * 2 >= includes.length); + } - IPath includePath = makeRelativePathToProjectIncludes(typeLocation, tu); - if (includePath != null && !projectLocation.isPrefixOf(typeLocation)) { - isSystemIncludePath = true; - } - //create a relative path - the include file is in the same project as the file we're currently at - else if (projectLocation.isPrefixOf(typeLocation) - && projectLocation.isPrefixOf(headerLocation)) { - includePath = PathUtil.makeRelativePath(typeLocation, headerLocation.removeLastSegments(1)); - } - //create a relative path - the include file is in the same workspace as the file we're currently at - else if (workspaceLocation.isPrefixOf(typeLocation)) - { - includePath = PathUtil.makeRelativePath(typeLocation, projectLocation); - } - if (includePath == null) - includePath = typeLocation; //the full path - return new RequiredIncludes(includePath.toString(), isSystemIncludePath); + // The file has never been included before. + IPath targetLocation = PathUtil.getCanonicalPath(new Path(file.getLocation().getURI().getPath())); + IPath sourceLocation = PathUtil.getCanonicalPath(fTu.getResource().getLocation()); + boolean isSystemIncludePath = false; + + IPath path = PathUtil.makeRelativePathToIncludes(targetLocation, fIncludePath); + if (path != null && + ResourceLookup.findFilesForLocationURI(URI.create(targetLocation.toString())).length == 0) { + // A header file in the include path but outside the workspace is included with angle brackets. + isSystemIncludePath = true; } - return null; + if (path == null) { + IPath sourceDirectory = sourceLocation.removeLastSegments(1); + if (PathUtil.isPrefix(sourceDirectory, targetLocation)) { + path = targetLocation.removeFirstSegments(sourceDirectory.segmentCount()); + } else { + path = targetLocation; + } + } + return new RequiredInclude(path.toString(), isSystemIncludePath); } - + /** - * Create a relative path to the project includes. - * @param fullPath the full path to the project - * @param tu a translation unit in the project - * @return IPath corresponding to a relative path to the project includes + * Returns true if the given include can be resolved in the context of + * the current translation unit. */ - private static IPath makeRelativePathToProjectIncludes(IPath fullPath, ITranslationUnit tu) { - IScannerInfoProvider provider = CCorePlugin.getDefault().getScannerInfoProvider(tu.getCProject().getProject()); - if (provider != null) { - IScannerInfo info = provider.getScannerInformation(tu.getResource()); - if (info != null) { - return PathUtil.makeRelativePathToIncludes(fullPath, info.getIncludePaths()); - } - } - return null; - } - + private boolean isResolvable(IIndexInclude include) { + try { + File target = new File(include.getIncludesLocation().getURI().getPath()); + String includeName = include.getFullName(); + for (String dir : fIncludePath) { + if (target.equals(new File(dir, includeName))) { + return true; + } + } + if (include.isSystemInclude()) { + return false; + } + return target.equals(new File(new File(fTu.getLocationURI().getPath()).getParent(), includeName)); + } catch (CoreException e) { + CUIPlugin.log(e); + return false; + } + } + /** - * Get the fully qualified name for a given PDOMBinding - * @param pdomBinding + * Get the fully qualified name for a given index binding. + * @param binding * @return binding's fully qualified name * @throws CoreException */ private static String getBindingQualifiedName(IIndexBinding binding) throws CoreException { - StringBuffer buf = new StringBuffer(); String[] qname= binding.getQualifiedName(); - for (int i = 0; i < qname.length; i++) { - if (i > 0) { - buf.append("::"); //$NON-NLS-1$ - } - buf.append(qname[i]); + return CPPVisitor.renderQualifiedName(qname); + } + + /** + * To be used by ElementListSelectionDialog for user to choose which declarations/ + * definitions for "add include" when there are more than one to choose from. + */ + private static class IncludeCandidate { + private final IIndexBinding binding; + private final IRequiredInclude include; + private final String label; + + public IncludeCandidate(IIndexBinding binding, IRequiredInclude include) throws CoreException { + this.binding = binding; + this.include = include; + this.label = getBindingQualifiedName(binding) + " - " + include.toString(); //$NON-NLS-1$ + } + + public IIndexBinding getBinding() { + return binding; + } + + public IRequiredInclude getInclude() { + return include; + } + + @Override + public String toString() { + return label; + } + } + + private static class RequiredInclude implements IRequiredInclude { + final String includeName; + final boolean isSystem; + + RequiredInclude(String includeName, boolean isSystem) { + this.includeName = includeName; + this.isSystem = isSystem; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.ui.IRequiredInclude#getIncludeName() + */ + public String getIncludeName() { + return includeName; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.ui.IRequiredInclude#isStandard() + */ + public boolean isStandard() { + return isSystem; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(includeName.length() + 2); + buf.append(isSystem ? '<' : '"'); + buf.append(includeName); + buf.append(isSystem ? '>' : '"'); + return buf.toString(); } - return buf.toString(); } } - - diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CEditorMessages.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CEditorMessages.java index 70895cedb8b..99bc805413a 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CEditorMessages.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CEditorMessages.java @@ -10,22 +10,36 @@ *******************************************************************************/ package org.eclipse.cdt.internal.ui.editor; +import java.util.ResourceBundle; + import org.eclipse.osgi.util.NLS; public final class CEditorMessages extends NLS { + private static final String BUNDLE_FOR_CONSTRUCTED_KEYS= + "org.eclipse.cdt.internal.ui.editor.ConstructedCEditorMessages"; //$NON-NLS-1$ + private static ResourceBundle fgBundleForConstructedKeys= ResourceBundle.getBundle(BUNDLE_FOR_CONSTRUCTED_KEYS); - private static final String BUNDLE_NAME = "org.eclipse.cdt.internal.ui.editor.CEditorMessages";//$NON-NLS-1$ + /** + * Returns the message bundle which contains constructed keys. + * + * @return the message bundle + * @since 5.1 + */ + public static ResourceBundle getBundleForConstructedKeys() { + return fgBundleForConstructedKeys; + } + + private static final String BUNDLE_NAME = CEditorMessages.class.getName(); private CEditorMessages() { // Do not instantiate } - public static String AddIncludeOnSelection_description; - public static String AddIncludeOnSelection_error_message1; - public static String AddIncludeOnSelection_error_message3; - public static String AddIncludeOnSelection_error_message4; public static String AddIncludeOnSelection_label; - public static String AddIncludeOnSelection_tooltip; + public static String AddIncludeOnSelection_description; + public static String AddIncludeOnSelection_error_title; + public static String AddIncludeOnSelection_insertion_failed; + public static String AddIncludeOnSelection_help_provider_error; public static String AddIncludesOperation_description; public static String ShowInCView_description; public static String ShowInCView_label; diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CEditorMessages.properties b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CEditorMessages.properties index 4a53257d545..64fe222180b 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CEditorMessages.properties +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CEditorMessages.properties @@ -10,14 +10,14 @@ # QNX Software System # Anton Leherbauer (Wind River Systems) # Markus Schorn (Wind River Systems) +# Sergey Prigogin (Google) ######################################### -AddIncludeOnSelection_description=Add include statement on selection -AddIncludeOnSelection_error_message1=Adding include statements failed -AddIncludeOnSelection_error_message3=Error -AddIncludeOnSelection_error_message4=BadLocationException: AddIncludeOnSelection_label=Add Include -AddIncludeOnSelection_tooltip=Add Include Statement on Selection +AddIncludeOnSelection_description=Add include statement for selected name +AddIncludeOnSelection_error_title=Error Adding Include +AddIncludeOnSelection_insertion_failed=Adding include statements failed +AddIncludeOnSelection_help_provider_error=Help provider error AddIncludesOperation_description=Adding include statement ShowInCView_description=Show the current resource in the C/C++ Projects view diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/ConstructedCEditorMessages.properties b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/ConstructedCEditorMessages.properties index f41715b402b..c56e97640d3 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/ConstructedCEditorMessages.properties +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/ConstructedCEditorMessages.properties @@ -10,11 +10,12 @@ # QNX Software System # Anton Leherbauer (Wind River Systems) # Markus Schorn (Wind River Systems) +# Sergey Prigogin (Google) ######################################### -AddIncludeOnSelection.description=Add include statement on selection +AddIncludeOnSelection.description=Add include statement for selected name AddIncludeOnSelection.label=Add Include -AddIncludeOnSelection.tooltip=Add Include Statement on Selection +AddIncludeOnSelection.tooltip=Add Include Statement for Selected Name OpenOutline.label= Quick Out&line OpenOutline.tooltip= Shows the Quick Outline of Editor Input