/**
* Copyright (C) 2005 - 2014 Eric Van Dewoestine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.eclim.plugin.cdt.command.search;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import org.apache.tools.ant.taskdefs.condition.Os;
import org.eclim.annotation.Command;
import org.eclim.command.CommandLine;
import org.eclim.command.Options;
import org.eclim.plugin.cdt.PluginResources;
import org.eclim.plugin.cdt.util.CUtils;
import org.eclim.plugin.core.command.AbstractCommand;
import org.eclim.plugin.core.util.ProjectUtils;
import org.eclim.util.CollectionUtils;
import org.eclim.util.file.Position;
import org.eclipse.cdt.core.CCProjectNature;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.CProjectNature;
import org.eclipse.cdt.core.dom.IName;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNodeSelector;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexFileLocation;
import org.eclipse.cdt.core.index.IIndexMacro;
import org.eclipse.cdt.core.index.IIndexManager;
import org.eclipse.cdt.core.index.IndexFilter;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.internal.ui.search.CSearchElement;
import org.eclipse.cdt.internal.ui.search.CSearchMatch;
import org.eclipse.cdt.internal.ui.search.CSearchMessages;
import org.eclipse.cdt.internal.ui.search.CSearchPatternQuery;
import org.eclipse.cdt.internal.ui.search.CSearchQuery;
import org.eclipse.cdt.internal.ui.search.CSearchResult;
import org.eclipse.cdt.internal.ui.viewsupport.IndexUI;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.search.ui.text.Match;
/**
* Command to handle search requests.
*
* @author Eric Van Dewoestine
*/
@Command(
name = "c_search",
options =
"OPTIONAL n project ARG," +
"OPTIONAL f file ARG," +
"OPTIONAL o offset ARG," +
"OPTIONAL l length ARG," +
"OPTIONAL e encoding ARG," +
"OPTIONAL p pattern ARG," +
"OPTIONAL t type ARG," +
"OPTIONAL x context ARG," +
"OPTIONAL s scope ARG," +
"OPTIONAL i case_insensitive NOARG"
)
public class SearchCommand
extends AbstractCommand
{
public static final String CONTEXT_ALL = "all";
public static final String CONTEXT_CONTEXT = "context";
public static final String CONTEXT_DECLARATIONS = "declarations";
public static final String CONTEXT_DEFINITIONS = "definitions";
public static final String CONTEXT_REFERENCES = "references";
private static final int FIND_CONTEXT = -1;
public static final String SCOPE_ALL = "all";
public static final String SCOPE_PROJECT = "project";
public static final String TYPE_ALL = "all";
public static final String TYPE_CLASS_STRUCT = "class_struct";
public static final String TYPE_FUNCTION = "function";
public static final String TYPE_VARIABLE = "variable";
public static final String TYPE_UNION = "union";
public static final String TYPE_METHOD = "method";
public static final String TYPE_FIELD = "field";
public static final String TYPE_ENUM = "enum";
public static final String TYPE_ENUMERATOR = "enumerator";
public static final String TYPE_NAMESPACE = "namespace";
public static final String TYPE_TYPEDEF = "typedef";
public static final String TYPE_MACRO = "macro";
@Override
public Object execute(CommandLine commandLine)
throws Exception
{
String projectName = commandLine.getValue(Options.NAME_OPTION);
String file = commandLine.getValue(Options.FILE_OPTION);
String offset = commandLine.getValue(Options.OFFSET_OPTION);
String length = commandLine.getValue(Options.LENGTH_OPTION);
IProject project = projectName != null ?
ProjectUtils.getProject(projectName) : null;
ICProject cproject = null;
if (project != null){
cproject = CUtils.getCProject(project);
}
// element search
if(file != null && offset != null && length != null){
return executeElementSearch(commandLine, cproject);
}
// pattern search
return executePatternSearch(commandLine, cproject);
}
private Object executeElementSearch(
CommandLine commandLine, ICProject cproject)
throws Exception
{
ArrayList<Position> results = new ArrayList<Position>();
String file = commandLine.getValue(Options.FILE_OPTION);
ITranslationUnit src = CUtils.getTranslationUnit(cproject, file);
if(src != null){
int context = getContext(
commandLine.getValue(Options.CONTEXT_OPTION), FIND_CONTEXT);
ICProject[] scope = new ICProject[]{cproject};
if (SCOPE_ALL.equals(commandLine.getValue(Options.SCOPE_OPTION))){
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
ArrayList<ICProject> cprojects = new ArrayList<ICProject>();
for (IProject project : projects){
if (project.isOpen() && (
project.hasNature(PluginResources.NATURE_C) ||
project.hasNature(PluginResources.NATURE_CPP)))
{
cprojects.add(CUtils.getCProject(project));
}
}
scope = cprojects.toArray(new ICProject[cprojects.size()]);
}
IIndex index = CCorePlugin.getIndexManager().getIndex(
scope, IIndexManager.ADD_DEPENDENCIES | IIndexManager.ADD_DEPENDENT);
index.acquireReadLock();
try{
int offset = getOffset(commandLine);
int length = commandLine.getIntValue(Options.LENGTH_OPTION);
IName[] names = findElement(src, scope, context, offset, length);
for (IName iname : names){
IASTFileLocation loc = iname.getFileLocation();
String filename = loc.getFileName().replace('\\', '/');
results.add(
Position.fromOffset(filename, null, loc.getNodeOffset(), 0));
}
}finally{
index.releaseReadLock();
}
}
return results;
}
private Object executePatternSearch(
CommandLine commandLine, ICProject cproject)
throws Exception
{
String scopeName = commandLine.getValue(Options.SCOPE_OPTION);
ICProject[] scope = getScope(scopeName, cproject);
String scopeDesc = null;
if (cproject == null || SCOPE_ALL.equals(scopeName)){
scopeDesc = CSearchMessages.WorkspaceScope;
}else if (SCOPE_PROJECT.equals(scopeName)){
scopeDesc = CSearchMessages.ProjectScope;
}
int context = getContext(commandLine.getValue(Options.CONTEXT_OPTION));
int type = getType(commandLine.getValue(Options.TYPE_OPTION));
String pattern = commandLine.getValue(Options.PATTERN_OPTION);
boolean caseSensitive =
!commandLine.hasOption(Options.CASE_INSENSITIVE_OPTION);
CSearchQuery query = new CSearchPatternQuery(
scope, scopeDesc, pattern, caseSensitive, type | context);
ArrayList<Position> results = new ArrayList<Position>();
if (query != null){
query.run(new NullProgressMonitor());
CSearchResult result = (CSearchResult)query.getSearchResult();
for (Object e : result.getElements()){
Method method = CSearchElement.class.getDeclaredMethod("getLocation");
method.setAccessible(true);
IIndexFileLocation location = (IIndexFileLocation)method.invoke(e);
String filename = location.getURI().getPath();
if (Os.isFamily("windows") && filename.startsWith("/")){
filename = filename.substring(1);
}
for (Match m : result.getMatches(e)){
CSearchMatch match = (CSearchMatch)m;
results.add(
Position.fromOffset(filename, null, match.getOffset(), 0));
}
}
}
return results;
}
protected IName[] findElement(
ITranslationUnit src,
ICProject[] scope, int context,
int offset, int length)
throws Exception
{
LinkedHashSet<IName> names = new LinkedHashSet<IName>();
IIndex index = CCorePlugin.getIndexManager().getIndex(
scope,
IIndexManager.ADD_DEPENDENCIES |
IIndexManager.ADD_DEPENDENT |
IIndexManager.ADD_EXTENSION_FRAGMENTS_NAVIGATION);
index.acquireReadLock();
try{
IASTTranslationUnit ast = src.getAST(index,
ITranslationUnit.AST_CONFIGURE_USING_SOURCE_CONTEXT |
ITranslationUnit.AST_SKIP_INDEXED_HEADERS);
IASTNodeSelector selector = ast.getNodeSelector(null);
IASTName name = selector.findEnclosingName(offset, length);
if (name != null){
IBinding binding = name.resolveBinding();
int flags = IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES;
if (context == FIND_CONTEXT){
if (!name.isDeclaration() && !name.isDefinition()){
flags |= IIndex.FIND_DEFINITIONS;
} else {
// if on the declaration, search for the definition and vice verca
flags |= (name.isDefinition() ?
IIndex.FIND_DECLARATIONS : IIndex.FIND_DEFINITIONS);
}
} else if (context == CSearchQuery.FIND_ALL_OCCURRENCES){
flags |= IIndex.FIND_ALL_OCCURRENCES;
} else if (context == CSearchQuery.FIND_REFERENCES){
flags |= IIndex.FIND_REFERENCES;
} else if (context == CSearchQuery.FIND_DECLARATIONS_DEFINITIONS) {
flags |= IIndex.FIND_DECLARATIONS_DEFINITIONS;
} else if (context == CSearchQuery.FIND_DECLARATIONS) {
flags |= IIndex.FIND_DECLARATIONS;
} else if (context == CSearchQuery.FIND_DEFINITIONS) {
flags |= IIndex.FIND_DEFINITIONS;
}
CollectionUtils.addAll(names, index.findNames(binding, flags));
// kind of hacky. if we issued a context search and found no
// definitions, we'll try a declarations search (useful for system
// library references).
if (names.size() == 0 &&
context == FIND_CONTEXT &&
(flags & IIndex.FIND_DEFINITIONS) != 0)
{
CollectionUtils.addAll(names, index.findNames(
binding,
IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES |
IIndex.FIND_DECLARATIONS));
}
if (names.size() == 0){
// alternate search that finds some things that index.findNames may
// not.
if ((flags & IIndex.FIND_DECLARATIONS) != 0){
CollectionUtils.addAll(names, ast.getDeclarations(binding));
}
if ((flags & IIndex.FIND_DEFINITIONS) != 0){
CollectionUtils.addAll(names, ast.getDefinitions(binding));
}
if ((flags & IIndex.FIND_REFERENCES) != 0){
CollectionUtils.addAll(names, ast.getReferences(binding));
}
if ((flags & IIndex.FIND_DECLARATIONS) != 0 ||
(flags & IIndex.FIND_DEFINITIONS) != 0)
{
// also try to find macros (now required for stdlib EXIT_SUCCESS)
// gleaned from navigationFallBack in
// org.eclipse.cdt.internal.ui.search.actions.OpenDeclarationJob
IndexFilter filter = IndexFilter.getDeclaredBindingFilter(
ast.getLinkage().getLinkageID(), false);
IIndexMacro[] macros = index.findMacros(
binding.getName().toCharArray(),
filter,
new NullProgressMonitor());
for (IIndexMacro macro : macros) {
ICElement element = IndexUI.getCElementForMacro(
src.getCProject(), index, macro);
if (element != null) {
ISourceReference ref = (ISourceReference)element;
FileLocation location = new FileLocation(
ref.getTranslationUnit().getPath().toPortableString(),
ref.getSourceRange().getStartPos(),
ref.getSourceRange().getLength(),
ref.getSourceRange().getStartLine());
names.add(new Name(location));
}
}
}
}
}
}finally{
index.releaseReadLock();
}
return names.toArray(new IName[names.size()]);
}
/**
* Gets the search scope to use.
*
* @param scope The string name of the scope.
* @param project The current project.
*
* @return The ICProject array representing the scope.
*/
protected ICProject[] getScope(String scope, ICProject project)
throws Exception
{
if (project == null || SCOPE_ALL.equals(scope)){
ArrayList<ICProject> elements = new ArrayList<ICProject>();
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
for (IProject p : projects){
if(p.isOpen() && (
p.hasNature(CProjectNature.C_NATURE_ID) ||
p.hasNature(CCProjectNature.CC_NATURE_ID)))
{
elements.add(CoreModel.getDefault().create(p));
}
}
return elements.toArray(new ICProject[elements.size()]);
}
ArrayList<ICProject> elements = new ArrayList<ICProject>();
elements.add(project);
IProject[] depends = project.getProject().getReferencedProjects();
for (IProject p : depends){
if(!p.isOpen()){
p.open(null);
}
elements.add(CoreModel.getDefault().create(p));
}
return elements.toArray(new ICProject[elements.size()]);
}
/**
* Translates the string context to the int equivalent.
*
* @param context The String context.
* @return The int context
*/
protected int getContext(String context)
{
return getContext(context, CSearchQuery.FIND_DECLARATIONS_DEFINITIONS);
}
/**
* Translates the string context to the int equivalent.
*
* @param context The String context.
* @param dflt The default String context.
* @return The int context
*/
protected int getContext(String context, int dflt)
{
if (context == null){
return dflt;
}
if(CONTEXT_ALL.equals(context)){
return CSearchQuery.FIND_ALL_OCCURRENCES;
}else if(CONTEXT_CONTEXT.equals(context)){
return FIND_CONTEXT;
}else if(CONTEXT_REFERENCES.equals(context)){
return CSearchQuery.FIND_REFERENCES;
}else if(CONTEXT_DECLARATIONS.equals(context)){
return CSearchQuery.FIND_DECLARATIONS;
}else if(CONTEXT_DEFINITIONS.equals(context)){
return CSearchQuery.FIND_DEFINITIONS;
}
return CSearchQuery.FIND_DECLARATIONS_DEFINITIONS;
}
/**
* Translates the string type to the int equivalent.
*
* @param type The String type.
* @return The int type.
*/
protected int getType(String type)
{
if(TYPE_CLASS_STRUCT.equals(type)){
return CSearchPatternQuery.FIND_CLASS_STRUCT;
}else if(TYPE_FUNCTION.equals(type)){
return CSearchPatternQuery.FIND_FUNCTION;
}else if(TYPE_VARIABLE.equals(type)){
return CSearchPatternQuery.FIND_VARIABLE;
}else if(TYPE_UNION.equals(type)){
return CSearchPatternQuery.FIND_UNION;
}else if(TYPE_METHOD.equals(type)){
return CSearchPatternQuery.FIND_METHOD;
}else if(TYPE_FIELD.equals(type)){
return CSearchPatternQuery.FIND_FIELD;
}else if(TYPE_ENUM.equals(type)){
return CSearchPatternQuery.FIND_ENUM;
}else if(TYPE_ENUMERATOR.equals(type)){
return CSearchPatternQuery.FIND_ENUMERATOR;
}else if(TYPE_NAMESPACE.equals(type)){
return CSearchPatternQuery.FIND_NAMESPACE;
}else if(TYPE_TYPEDEF.equals(type)){
return CSearchPatternQuery.FIND_TYPEDEF;
}else if(TYPE_MACRO.equals(type)){
return CSearchPatternQuery.FIND_MACRO;
}
return CSearchPatternQuery.FIND_ALL_TYPES;
}
private class Name
implements IName
{
private IASTFileLocation fileLocation;
public Name(IASTFileLocation fileLocation)
{
this.fileLocation = fileLocation;
}
@Override
public char[] getSimpleID()
{
return null;
}
@Override
public char[] toCharArray()
{
return null;
}
@Override
public boolean isDeclaration()
{
return false;
}
@Override
public boolean isReference()
{
return false;
}
@Override
public boolean isDefinition()
{
return false;
}
@Override
public IASTFileLocation getFileLocation()
{
return fileLocation;
}
}
private class FileLocation
implements IASTFileLocation
{
private String fileName;
private int offset;
private int length;
private int line;
public FileLocation(String fileName, int offset, int length, int line)
{
this.fileName = fileName;
this.offset = offset;
this.length = length;
this.line = line;
}
@Override
public IASTFileLocation asFileLocation()
{
return this;
}
@Override
public String getFileName()
{
return fileName;
}
@Override
public int getNodeOffset()
{
return offset;
}
@Override
public int getNodeLength()
{
return length;
}
@Override
public int getStartingLineNumber()
{
return line;
}
@Override
public int getEndingLineNumber()
{
return 0;
}
@Override
public IASTPreprocessorIncludeStatement getContextInclusionStatement()
{
return null;
}
}
}