calculate prefix for path completion contributor properly
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / completion / FilePathCompletionContributor.java
blobb1eacc72e4348ccc3c7533637cd4039d32d845ce
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.codeInsight.completion;
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.codeInsight.lookup.LookupElement;
21 import com.intellij.codeInsight.lookup.LookupElementPresentation;
22 import com.intellij.navigation.ChooseByNameContributor;
23 import com.intellij.openapi.actionSystem.IdeActions;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.fileTypes.FileNameMatcher;
27 import com.intellij.openapi.fileTypes.FileType;
28 import com.intellij.openapi.fileTypes.FileTypeManager;
29 import com.intellij.openapi.module.Module;
30 import com.intellij.openapi.progress.ProcessCanceledException;
31 import com.intellij.openapi.progress.ProgressManager;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.roots.ProjectFileIndex;
34 import com.intellij.openapi.roots.ProjectRootManager;
35 import com.intellij.openapi.util.Computable;
36 import com.intellij.openapi.util.Pair;
37 import com.intellij.openapi.util.io.FileUtil;
38 import com.intellij.openapi.util.text.StringUtil;
39 import com.intellij.openapi.vfs.VirtualFile;
40 import com.intellij.psi.PsiElement;
41 import com.intellij.psi.PsiFile;
42 import com.intellij.psi.PsiFileSystemItem;
43 import com.intellij.psi.PsiReference;
44 import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
45 import com.intellij.psi.impl.source.resolve.reference.impl.providers.*;
46 import com.intellij.psi.search.FilenameIndex;
47 import com.intellij.psi.search.GlobalSearchScope;
48 import com.intellij.psi.search.ProjectScope;
49 import com.intellij.util.ArrayUtil;
50 import com.intellij.util.ProcessingContext;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.Nullable;
54 import javax.swing.*;
55 import java.util.*;
57 import static com.intellij.patterns.PlatformPatterns.psiElement;
59 /**
60 * @author spleaner
62 public class FilePathCompletionContributor extends CompletionContributor {
63 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.FilePathCompletionContributor");
65 public FilePathCompletionContributor() {
66 extend(CompletionType.BASIC, psiElement(), new CompletionProvider<CompletionParameters>() {
67 @Override
68 protected void addCompletions(@NotNull CompletionParameters parameters,
69 ProcessingContext context,
70 @NotNull CompletionResultSet result) {
71 final PsiReference psiReference = parameters.getPosition().getContainingFile().findReferenceAt(parameters.getOffset());
72 if (getReference(psiReference) != null) {
73 final String shortcut = getActionShortcut(IdeActions.ACTION_CLASS_NAME_COMPLETION);
74 final CompletionService service = CompletionService.getCompletionService();
75 if (/*StringUtil.isEmpty(service.getAdvertisementText()) &&*/ shortcut != null) {
76 service.setAdvertisementText(CodeInsightBundle.message("class.completion.file.path", shortcut));
80 });
82 extend(CompletionType.CLASS_NAME, psiElement(), new CompletionProvider<CompletionParameters>(false) {
83 @Override
84 protected void addCompletions(@NotNull final CompletionParameters parameters,
85 ProcessingContext context,
86 @NotNull final CompletionResultSet _result) {
87 @NotNull final CompletionResultSet result = _result.caseInsensitive();
88 final PsiElement e = parameters.getPosition();
89 final Project project = e.getProject();
91 final PsiReference psiReference = ApplicationManager.getApplication().runReadAction(new Computable<PsiReference>() {
92 public PsiReference compute() {
93 //noinspection ConstantConditions
94 return parameters.getPosition().getContainingFile().findReferenceAt(parameters.getOffset());
96 });
98 final Pair<FileReference, Boolean> fileReferencePair = getReference(psiReference);
99 if (fileReferencePair != null) {
100 final FileReference first = fileReferencePair.getFirst();
101 if (first == null) return;
103 final FileReferenceSet set = first.getFileReferenceSet();
104 String prefix = set.getPathString().substring(0, parameters.getOffset() - set.getElement().getTextRange().getStartOffset() - set.getStartInElement());
106 final List<String>[] pathPrefixParts = new List[] {null};
107 int lastSlashIndex;
108 if ((lastSlashIndex = prefix.lastIndexOf('/')) != -1) {
109 pathPrefixParts[0] = StringUtil.split(prefix.substring(0, lastSlashIndex), "/");
110 prefix = prefix.substring(lastSlashIndex + 1);
113 final CompletionResultSet __result = result.withPrefixMatcher(prefix).caseInsensitive();
115 final PsiFile originalFile = parameters.getOriginalFile();
116 final VirtualFile contextFile = originalFile.getVirtualFile();
117 if (contextFile != null) {
118 final String[] fileNames = getAllNames(project);
119 final Set<String> resultNames = new TreeSet<String>();
120 for (String fileName : fileNames) {
121 if (filenameMatchesPrefixOrType(fileName, prefix, set.getSuitableFileTypes(), parameters.getInvocationCount())) {
122 resultNames.add(fileName);
126 final ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex();
128 final Module contextModule = index.getModuleForFile(contextFile);
129 if (contextModule != null) {
130 final FileReferenceHelper contextHelper = FileReferenceHelperRegistrar.getNotNullHelper(originalFile);
132 final GlobalSearchScope scope = ProjectScope.getProjectScope(project);
133 for (final String name : resultNames) {
134 ProgressManager.checkCanceled();
136 final PsiFile[] files = ApplicationManager.getApplication().runReadAction(new Computable<PsiFile[]>() {
137 public PsiFile[] compute() {
138 return FilenameIndex.getFilesByName(project, name, scope);
142 if (files.length > 0) {
143 for (final PsiFile file : files) {
144 ProgressManager.checkCanceled();
146 ApplicationManager.getApplication().runReadAction(new Runnable() {
147 public void run() {
148 final VirtualFile virtualFile = file.getVirtualFile();
149 if (virtualFile != null && virtualFile.isValid() && virtualFile != contextFile) {
150 if (contextHelper.isMine(project, virtualFile)) {
151 if (pathPrefixParts[0] == null || fileMatchesPathPrefix(contextHelper.getPsiFileSystemItem(project, virtualFile), pathPrefixParts[0])) {
152 __result.addElement(new FilePathLookupItem(file, contextHelper));
164 if (set.getSuitableFileTypes().length > 0 && parameters.getInvocationCount() == 1) {
165 final String shortcut = getActionShortcut(IdeActions.ACTION_CLASS_NAME_COMPLETION);
166 final CompletionService service = CompletionService.getCompletionService();
167 if (shortcut != null) {
168 service.setAdvertisementText(CodeInsightBundle.message("class.completion.file.path.all.variants", shortcut));
172 if (fileReferencePair.getSecond()) result.stopHere();
178 private static boolean filenameMatchesPrefixOrType(final String fileName, final String prefix, final FileType[] suitableFileTypes, final int invocationCount) {
179 final boolean prefixMatched = prefix.length() == 0 || StringUtil.startsWithIgnoreCase(fileName, prefix);
180 if (prefixMatched && (suitableFileTypes.length == 0 || invocationCount > 1)) return true;
182 if (prefixMatched) {
183 final String extension = FileUtil.getExtension(fileName);
184 if (extension.length() == 0) return false;
186 for (final FileType fileType : suitableFileTypes) {
187 final List<FileNameMatcher> matchers = FileTypeManager.getInstance().getAssociations(fileType);
188 for (final FileNameMatcher matcher : matchers) {
189 if (matcher.accept(fileName)) return true;
194 return false;
197 private static boolean fileMatchesPathPrefix(@Nullable final PsiFileSystemItem file, @NotNull final List<String> pathPrefix) {
198 if (file == null) return false;
200 final List<String> contextParts = new ArrayList<String>();
201 PsiFileSystemItem parentFile = file;
202 PsiFileSystemItem parent;
203 while ((parent = parentFile.getParent()) != null) {
204 if (parent.getName().length() > 0) contextParts.add(0, parent.getName().toLowerCase());
205 parentFile = parent;
208 final String path = StringUtil.join(contextParts, "/");
210 int nextIndex = 0;
211 for (final String s : pathPrefix) {
212 if ((nextIndex = path.indexOf(s.toLowerCase(), nextIndex)) == -1) return false;
215 return true;
218 private static String[] getAllNames(@NotNull final Project project) {
219 Set<String> names = new HashSet<String>();
220 final ChooseByNameContributor[] nameContributors = ChooseByNameContributor.FILE_EP_NAME.getExtensions();
221 for (final ChooseByNameContributor contributor : nameContributors) {
222 try {
223 names.addAll(ApplicationManager.getApplication().runReadAction(new Computable<Collection<? extends String>>() {
224 public Collection<? extends String> compute() {
225 return Arrays.asList(contributor.getNames(project, false));
227 }));
229 catch (ProcessCanceledException ex) {
230 // index corruption detected, ignore
232 catch (Exception ex) {
233 LOG.error(ex);
237 return ArrayUtil.toStringArray(names);
240 @Nullable
241 private static Pair<FileReference, Boolean> getReference(final PsiReference original) {
242 if (original == null) {
243 return null;
246 if (original instanceof PsiMultiReference) {
247 final PsiMultiReference multiReference = (PsiMultiReference)original;
248 for (PsiReference reference : multiReference.getReferences()) {
249 if (reference instanceof FileReference) {
250 return Pair.create((FileReference) reference, false);
254 else if (original instanceof FileReferenceOwner) {
255 final FileReference fileReference = ((FileReferenceOwner)original).getLastFileReference();
256 if (fileReference != null) {
257 return Pair.create(fileReference, true);
261 return null;
264 public class FilePathLookupItem extends LookupElement {
265 private final String myName;
266 private final String myPath;
267 private final String myInfo;
268 private final Icon myIcon;
269 private final PsiFile myFile;
270 private FileReferenceHelper myReferenceHelper;
272 public FilePathLookupItem(@NotNull final PsiFile file, @NotNull final FileReferenceHelper referenceHelper) {
273 myName = file.getName();
274 myPath = file.getVirtualFile().getPath();
276 myReferenceHelper = referenceHelper;
278 myInfo = FileInfoManager.getFileAdditionalInfo(file);
279 myIcon = file.getFileType().getIcon();
281 myFile = file;
284 @SuppressWarnings({"HardCodedStringLiteral"})
285 @Override
286 public String toString() {
287 return String.format("%s%s", myName, myInfo == null ? "" : " (" + myInfo + ")");
290 @NotNull
291 @Override
292 public Object getObject() {
293 return myFile;
296 @NotNull
297 public String getLookupString() {
298 return myName;
301 @Override
302 public void handleInsert(InsertionContext context) {
303 if (myFile.isValid()) {
304 final PsiReference psiReference = context.getFile().findReferenceAt(context.getStartOffset());
305 final Pair<FileReference, Boolean> fileReferencePair = getReference(psiReference);
306 LOG.assertTrue(fileReferencePair != null);
308 fileReferencePair.getFirst().bindToElement(myFile, true);
312 @Override
313 public void renderElement(LookupElementPresentation presentation) {
314 final VirtualFile virtualFile = myFile.getVirtualFile();
315 LOG.assertTrue(virtualFile != null);
316 final PsiFileSystemItem root = myReferenceHelper.findRoot(myFile.getProject(), virtualFile);
317 final String relativePath = PsiFileSystemItemUtil.getRelativePath(root, myReferenceHelper.getPsiFileSystemItem(myFile.getProject(), virtualFile));
319 final StringBuilder sb = new StringBuilder();
320 if (myInfo != null) {
321 sb.append(" (").append(myInfo);
324 if (relativePath != null && !relativePath.equals(myName)) {
325 if (myInfo != null) {
326 sb.append(", ");
328 else {
329 sb.append(" (");
332 sb.append(relativePath);
335 if (sb.length() > 0) {
336 sb.append(')');
339 presentation.setItemText(myName);
341 if (sb.length() > 0) {
342 presentation.setTailText(sb.toString(), true);
345 presentation.setIcon(myIcon);
348 @Override
349 public boolean equals(Object o) {
350 if (this == o) return true;
351 if (o == null || getClass() != o.getClass()) return false;
353 FilePathLookupItem that = (FilePathLookupItem)o;
355 if (!myName.equals(that.myName)) return false;
356 if (!myPath.equals(that.myPath)) return false;
358 return true;
361 @Override
362 public int hashCode() {
363 int result = myName.hashCode();
364 result = 31 * result + myPath.hashCode();
365 return result;