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
;
57 import static com
.intellij
.patterns
.PlatformPatterns
.psiElement
;
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
>() {
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
));
82 extend(CompletionType
.CLASS_NAME
, psiElement(), new CompletionProvider
<CompletionParameters
>(false) {
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());
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};
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() {
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;
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;
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());
208 final String path
= StringUtil
.join(contextParts
, "/");
211 for (final String s
: pathPrefix
) {
212 if ((nextIndex
= path
.indexOf(s
.toLowerCase(), nextIndex
)) == -1) return false;
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
) {
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));
229 catch (ProcessCanceledException ex
) {
230 // index corruption detected, ignore
232 catch (Exception ex
) {
237 return ArrayUtil
.toStringArray(names
);
241 private static Pair
<FileReference
, Boolean
> getReference(final PsiReference original
) {
242 if (original
== 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);
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();
284 @SuppressWarnings({"HardCodedStringLiteral"})
286 public String
toString() {
287 return String
.format("%s%s", myName
, myInfo
== null ?
"" : " (" + myInfo
+ ")");
292 public Object
getObject() {
297 public String
getLookupString() {
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);
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) {
332 sb
.append(relativePath
);
335 if (sb
.length() > 0) {
339 presentation
.setItemText(myName
);
341 if (sb
.length() > 0) {
342 presentation
.setTailText(sb
.toString(), true);
345 presentation
.setIcon(myIcon
);
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;
362 public int hashCode() {
363 int result
= myName
.hashCode();
364 result
= 31 * result
+ myPath
.hashCode();