blob: 4a34e94607186559ee0114e0cd8882a3d9648223 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.navigation.ChooseByNameContributor;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileNameMatcher;
import com.intellij.openapi.fileTypes.FileNameMatcherEx;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileSystemItem;
import com.intellij.psi.PsiReference;
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.*;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
import static com.intellij.patterns.PlatformPatterns.psiElement;
/**
* @author spleaner
*/
public class FilePathCompletionContributor extends CompletionContributor {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.FilePathCompletionContributor");
public FilePathCompletionContributor() {
extend(CompletionType.BASIC, psiElement(), new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
final PsiReference psiReference = parameters.getPosition().getContainingFile().findReferenceAt(parameters.getOffset());
if (getReference(psiReference) != null && parameters.getInvocationCount() == 1) {
final String shortcut = getActionShortcut(IdeActions.ACTION_CODE_COMPLETION);
result.addLookupAdvertisement(CodeInsightBundle.message("class.completion.file.path", shortcut));
}
}
});
CompletionProvider<CompletionParameters> provider = new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull final CompletionParameters parameters,
ProcessingContext context,
@NotNull final CompletionResultSet _result) {
if (!parameters.isExtendedCompletion()) {
return;
}
@NotNull final CompletionResultSet result = _result.caseInsensitive();
final PsiElement e = parameters.getPosition();
final Project project = e.getProject();
final PsiReference psiReference = parameters.getPosition().getContainingFile().findReferenceAt(parameters.getOffset());
final Pair<FileReference, Boolean> fileReferencePair = getReference(psiReference);
if (fileReferencePair != null) {
final FileReference first = fileReferencePair.getFirst();
if (first == null) return;
final FileReferenceSet set = first.getFileReferenceSet();
String prefix = set.getPathString()
.substring(0, parameters.getOffset() - set.getElement().getTextRange().getStartOffset() - set.getStartInElement());
List<String> pathPrefixParts = null;
int lastSlashIndex;
if ((lastSlashIndex = prefix.lastIndexOf('/')) != -1) {
pathPrefixParts = StringUtil.split(prefix.substring(0, lastSlashIndex), "/");
prefix = prefix.substring(lastSlashIndex + 1);
}
final CompletionResultSet __result = result.withPrefixMatcher(prefix).caseInsensitive();
final PsiFile originalFile = parameters.getOriginalFile();
final VirtualFile contextFile = originalFile.getVirtualFile();
if (contextFile != null) {
final String[] fileNames = getAllNames(project);
final Set<String> resultNames = new TreeSet<String>();
for (String fileName : fileNames) {
if (filenameMatchesPrefixOrType(fileName, prefix, set.getSuitableFileTypes(), parameters.getInvocationCount())) {
resultNames.add(fileName);
}
}
final ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex();
final Module contextModule = index.getModuleForFile(contextFile);
if (contextModule != null) {
final List<FileReferenceHelper> helpers = FileReferenceHelperRegistrar.getHelpers(originalFile);
final GlobalSearchScope scope = ProjectScope.getProjectScope(project);
for (final String name : resultNames) {
ProgressManager.checkCanceled();
final PsiFile[] files = FilenameIndex.getFilesByName(project, name, scope);
if (files.length <= 0) {
continue;
}
for (final PsiFile file : files) {
ProgressManager.checkCanceled();
final VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile == null || !virtualFile.isValid() || Comparing.equal(virtualFile, contextFile)) {
continue;
}
List<FileReferenceHelper> helperList = new ArrayList<FileReferenceHelper>();
for (FileReferenceHelper contextHelper : helpers) {
ProgressManager.checkCanceled();
if (contextHelper.isMine(project, virtualFile)) {
if (pathPrefixParts == null ||
fileMatchesPathPrefix(contextHelper.getPsiFileSystemItem(project, virtualFile), pathPrefixParts)) {
helperList.add(contextHelper);
}
}
}
if (!helperList.isEmpty()) {
__result.addElement(new FilePathLookupItem(file, helperList));
}
}
}
}
}
if (set.getSuitableFileTypes().length > 0 && parameters.getInvocationCount() == 1) {
final String shortcut = getActionShortcut(IdeActions.ACTION_CODE_COMPLETION);
result.addLookupAdvertisement(CodeInsightBundle.message("class.completion.file.path.all.variants", shortcut));
}
if (fileReferencePair.getSecond()) result.stopHere();
}
}
};
extend(CompletionType.BASIC, psiElement(), provider);
}
private static boolean filenameMatchesPrefixOrType(final String fileName, final String prefix, final FileType[] suitableFileTypes, final int invocationCount) {
final boolean prefixMatched = prefix.length() == 0 || StringUtil.startsWithIgnoreCase(fileName, prefix);
if (prefixMatched && (suitableFileTypes.length == 0 || invocationCount > 2)) return true;
if (prefixMatched) {
final String extension = FileUtilRt.getExtension(fileName);
if (extension.length() == 0) return false;
for (final FileType fileType : suitableFileTypes) {
for (final FileNameMatcher matcher : FileTypeManager.getInstance().getAssociations(fileType)) {
if (FileNameMatcherEx.acceptsCharSequence(matcher, fileName)) return true;
}
}
}
return false;
}
private static boolean fileMatchesPathPrefix(@Nullable final PsiFileSystemItem file, @NotNull final List<String> pathPrefix) {
if (file == null) return false;
final List<String> contextParts = new ArrayList<String>();
PsiFileSystemItem parentFile = file;
PsiFileSystemItem parent;
while ((parent = parentFile.getParent()) != null) {
if (parent.getName().length() > 0) contextParts.add(0, parent.getName().toLowerCase());
parentFile = parent;
}
final String path = StringUtil.join(contextParts, "/");
int nextIndex = 0;
for (@NonNls final String s : pathPrefix) {
if ((nextIndex = path.indexOf(s.toLowerCase(), nextIndex)) == -1) return false;
}
return true;
}
private static String[] getAllNames(@NotNull final Project project) {
Set<String> names = new HashSet<String>();
final ChooseByNameContributor[] nameContributors = ChooseByNameContributor.FILE_EP_NAME.getExtensions();
for (final ChooseByNameContributor contributor : nameContributors) {
try {
names.addAll(Arrays.asList(contributor.getNames(project, false)));
}
catch (ProcessCanceledException ex) {
// index corruption detected, ignore
}
catch (Exception ex) {
LOG.error(ex);
}
}
return ArrayUtil.toStringArray(names);
}
@Nullable
private static Pair<FileReference, Boolean> getReference(final PsiReference original) {
if (original == null) {
return null;
}
if (original instanceof PsiMultiReference) {
final PsiMultiReference multiReference = (PsiMultiReference)original;
for (PsiReference reference : multiReference.getReferences()) {
if (reference instanceof FileReference) {
return Pair.create((FileReference) reference, false);
}
}
}
else if (original instanceof FileReferenceOwner) {
final PsiFileReference fileReference = ((FileReferenceOwner)original).getLastFileReference();
if (fileReference instanceof FileReference) {
return Pair.create((FileReference) fileReference, true);
}
}
return null;
}
public static class FilePathLookupItem extends LookupElement {
private final String myName;
private final String myPath;
private final String myInfo;
private final Icon myIcon;
private final PsiFile myFile;
private final List<FileReferenceHelper> myHelpers;
public FilePathLookupItem(@NotNull final PsiFile file, @NotNull final List<FileReferenceHelper> helpers) {
myName = file.getName();
myPath = file.getVirtualFile().getPath();
myHelpers = helpers;
myInfo = FileInfoManager.getFileAdditionalInfo(file);
myIcon = file.getFileType().getIcon();
myFile = file;
}
@SuppressWarnings({"HardCodedStringLiteral"})
@Override
public String toString() {
return String.format("%s%s", myName, myInfo == null ? "" : " (" + myInfo + ")");
}
@NotNull
@Override
public Object getObject() {
return myFile;
}
@Override
@NotNull
public String getLookupString() {
return myName;
}
@Override
public void handleInsert(InsertionContext context) {
context.commitDocument();
if (myFile.isValid()) {
final PsiReference psiReference = context.getFile().findReferenceAt(context.getStartOffset());
final Pair<FileReference, Boolean> fileReferencePair = getReference(psiReference);
if (fileReferencePair != null) {
FileReference ref = fileReferencePair.getFirst();
context.setTailOffset(ref.getRangeInElement().getEndOffset() + ref.getElement().getTextRange().getStartOffset());
ref.bindToElement(myFile);
}
}
}
@Override
public void renderElement(LookupElementPresentation presentation) {
final String relativePath = getRelativePath();
final StringBuilder sb = new StringBuilder();
if (myInfo != null) {
sb.append(" (").append(myInfo);
}
if (relativePath != null && !relativePath.equals(myName)) {
if (myInfo != null) {
sb.append(", ");
}
else {
sb.append(" (");
}
sb.append(relativePath);
}
if (sb.length() > 0) {
sb.append(')');
}
presentation.setItemText(myName);
if (sb.length() > 0) {
presentation.setTailText(sb.toString(), true);
}
presentation.setIcon(myIcon);
}
@Nullable
private String getRelativePath() {
final VirtualFile virtualFile = myFile.getVirtualFile();
LOG.assertTrue(virtualFile != null);
for (FileReferenceHelper helper : myHelpers) {
final PsiFileSystemItem root = helper.findRoot(myFile.getProject(), virtualFile);
String path = PsiFileSystemItemUtil.getRelativePath(root, helper.getPsiFileSystemItem(myFile.getProject(), virtualFile));
if (path != null) return path;
}
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FilePathLookupItem that = (FilePathLookupItem)o;
if (!myName.equals(that.myName)) return false;
if (!myPath.equals(that.myPath)) return false;
return true;
}
@Override
public int hashCode() {
int result = myName.hashCode();
result = 31 * result + myPath.hashCode();
return result;
}
}
}