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
.psi
.impl
.search
;
19 import com
.intellij
.concurrency
.JobUtil
;
20 import com
.intellij
.ide
.todo
.TodoConfiguration
;
21 import com
.intellij
.lang
.LanguageParserDefinitions
;
22 import com
.intellij
.lang
.ParserDefinition
;
23 import com
.intellij
.openapi
.application
.Application
;
24 import com
.intellij
.openapi
.application
.ApplicationManager
;
25 import com
.intellij
.openapi
.diagnostic
.Logger
;
26 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
27 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
28 import com
.intellij
.openapi
.progress
.ProgressManager
;
29 import com
.intellij
.openapi
.util
.Computable
;
30 import com
.intellij
.openapi
.util
.Ref
;
31 import com
.intellij
.openapi
.util
.TextRange
;
32 import com
.intellij
.openapi
.util
.text
.StringUtil
;
33 import com
.intellij
.psi
.*;
34 import com
.intellij
.psi
.impl
.PsiManagerEx
;
35 import com
.intellij
.psi
.search
.*;
36 import com
.intellij
.psi
.search
.searches
.IndexPatternSearch
;
37 import com
.intellij
.util
.Processor
;
38 import com
.intellij
.util
.text
.StringSearcher
;
39 import gnu
.trove
.THashSet
;
40 import org
.jetbrains
.annotations
.NotNull
;
41 import org
.jetbrains
.annotations
.Nullable
;
44 import java
.util
.concurrent
.atomic
.AtomicBoolean
;
45 import java
.util
.concurrent
.atomic
.AtomicInteger
;
47 public class PsiSearchHelperImpl
implements PsiSearchHelper
{
48 private static final Logger LOG
= Logger
.getInstance("#com.intellij.psi.impl.search.PsiSearchHelperImpl");
50 private final PsiManagerEx myManager
;
51 private static final TodoItem
[] EMPTY_TODO_ITEMS
= new TodoItem
[0];
54 IndexPatternSearch
.INDEX_PATTERN_SEARCH_INSTANCE
= new IndexPatternSearchImpl();
58 public SearchScope
getUseScope(@NotNull PsiElement element
) {
59 return element
.getUseScope();
63 public PsiSearchHelperImpl(PsiManagerEx manager
) {
68 public PsiFile
[] findFilesWithTodoItems() {
69 return myManager
.getCacheManager().getFilesWithTodoItems();
73 public TodoItem
[] findTodoItems(@NotNull PsiFile file
) {
74 return doFindTodoItems(file
, new TextRange(0, file
.getTextLength()));
78 public TodoItem
[] findTodoItems(@NotNull PsiFile file
, int startOffset
, int endOffset
) {
79 return doFindTodoItems(file
, new TextRange(startOffset
, endOffset
));
82 private static TodoItem
[] doFindTodoItems(final PsiFile file
, final TextRange textRange
) {
83 final Collection
<IndexPatternOccurrence
> occurrences
= IndexPatternSearch
.search(file
, TodoConfiguration
.getInstance()).findAll();
84 if (occurrences
.isEmpty()) {
85 return EMPTY_TODO_ITEMS
;
88 List
<TodoItem
> items
= new ArrayList
<TodoItem
>(occurrences
.size());
89 for(IndexPatternOccurrence occurrence
: occurrences
) {
90 TextRange occurrenceRange
= occurrence
.getTextRange();
91 if (textRange
.contains(occurrenceRange
)) {
92 items
.add(new TodoItemImpl(occurrence
.getFile(), occurrenceRange
.getStartOffset(), occurrenceRange
.getEndOffset(),
93 mapPattern(occurrence
.getPattern())));
97 return items
.toArray(new TodoItem
[items
.size()]);
100 private static TodoPattern
mapPattern(final IndexPattern pattern
) {
101 for(TodoPattern todoPattern
: TodoConfiguration
.getInstance().getTodoPatterns()) {
102 if (todoPattern
.getIndexPattern() == pattern
) {
106 LOG
.assertTrue(false, "Could not find matching TODO pattern for index pattern " + pattern
.getPatternString());
110 public int getTodoItemsCount(@NotNull PsiFile file
) {
111 int count
= myManager
.getCacheManager().getTodoCount(file
.getVirtualFile(), TodoConfiguration
.getInstance());
112 if (count
!= -1) return count
;
113 return findTodoItems(file
).length
;
116 public int getTodoItemsCount(@NotNull PsiFile file
, @NotNull TodoPattern pattern
) {
117 int count
= myManager
.getCacheManager().getTodoCount(file
.getVirtualFile(), pattern
.getIndexPattern());
118 if (count
!= -1) return count
;
119 TodoItem
[] items
= findTodoItems(file
);
121 for (TodoItem item
: items
) {
122 if (item
.getPattern().equals(pattern
)) count
++;
128 public PsiElement
[] findCommentsContainingIdentifier(@NotNull String identifier
, @NotNull SearchScope searchScope
) {
129 final ArrayList
<PsiElement
> results
= new ArrayList
<PsiElement
>();
130 processCommentsContainingIdentifier(identifier
, searchScope
, new Processor
<PsiElement
>() {
131 public boolean process(PsiElement element
) {
132 synchronized (results
) {
133 results
.add(element
);
138 synchronized (results
) {
139 return results
.toArray(new PsiElement
[results
.size()]);
143 public boolean processCommentsContainingIdentifier(@NotNull String identifier
,
144 @NotNull SearchScope searchScope
,
145 @NotNull final Processor
<PsiElement
> processor
) {
146 TextOccurenceProcessor occurenceProcessor
= new TextOccurenceProcessor() {
147 public boolean execute(PsiElement element
, int offsetInElement
) {
148 final ParserDefinition parserDefinition
= LanguageParserDefinitions
.INSTANCE
.forLanguage(element
.getLanguage());
149 if (parserDefinition
== null) return true;
151 if (element
.getNode() != null && !parserDefinition
.getCommentTokens().contains(element
.getNode().getElementType())) return true;
152 if (element
.findReferenceAt(offsetInElement
) == null) {
153 return processor
.process(element
);
158 return processElementsWithWord(occurenceProcessor
, searchScope
, identifier
, UsageSearchContext
.IN_COMMENTS
, true);
161 public boolean processElementsWithWord(@NotNull TextOccurenceProcessor processor
,
162 @NotNull SearchScope searchScope
,
163 @NotNull String text
,
165 boolean caseSensitively
) {
166 if (text
.length() == 0) {
167 throw new IllegalArgumentException("Cannot search for elements with empty text");
169 if (searchScope
instanceof GlobalSearchScope
) {
170 StringSearcher searcher
= new StringSearcher(text
, caseSensitively
, true);
172 return processElementsWithTextInGlobalScope(processor
,
173 (GlobalSearchScope
)searchScope
,
175 searchContext
, caseSensitively
);
178 LocalSearchScope scope
= (LocalSearchScope
)searchScope
;
179 PsiElement
[] scopeElements
= scope
.getScope();
180 final boolean ignoreInjectedPsi
= scope
.isIgnoreInjectedPsi();
182 for (final PsiElement scopeElement
: scopeElements
) {
183 if (!processElementsWithWordInScopeElement(scopeElement
, processor
, text
, caseSensitively
, ignoreInjectedPsi
)) return false;
189 private static boolean processElementsWithWordInScopeElement(final PsiElement scopeElement
,
190 final TextOccurenceProcessor processor
,
192 final boolean caseSensitive
,
193 final boolean ignoreInjectedPsi
) {
194 return ApplicationManager
.getApplication().runReadAction(new Computable
<Boolean
>() {
195 public Boolean
compute() {
196 StringSearcher searcher
= new StringSearcher(word
, caseSensitive
, true);
198 return LowLevelSearchUtil
.processElementsContainingWordInElement(processor
, scopeElement
, searcher
, ignoreInjectedPsi
);
203 private boolean processElementsWithTextInGlobalScope(final TextOccurenceProcessor processor
,
204 final GlobalSearchScope scope
,
205 final StringSearcher searcher
,
206 final short searchContext
,
207 final boolean caseSensitively
) {
208 LOG
.assertTrue(!Thread
.holdsLock(PsiLock
.LOCK
), "You must not run search from within updating PSI activity. Please consider invokeLatering it instead.");
209 final ProgressIndicator progress
= ProgressManager
.getInstance().getProgressIndicator();
210 if (progress
!= null) {
211 progress
.pushState();
212 progress
.setText(PsiBundle
.message("psi.scanning.files.progress"));
214 myManager
.startBatchFilesProcessingMode();
217 List
<String
> words
= StringUtil
.getWordsIn(searcher
.getPattern());
218 if (words
.isEmpty()) return true;
219 Set
<PsiFile
> fileSet
= new THashSet
<PsiFile
>();
220 Set
<PsiFile
> copy
= new THashSet
<PsiFile
>();
221 final Application application
= ApplicationManager
.getApplication();
222 for (final String word
: words
) {
223 PsiFile
[] psiFiles
= application
.runReadAction(new Computable
<PsiFile
[]>() {
224 public PsiFile
[] compute() {
225 return myManager
.getCacheManager().getFilesWithWord(word
, searchContext
, scope
, caseSensitively
);
228 if (fileSet
.isEmpty()) {
229 fileSet
.addAll(Arrays
.asList(psiFiles
));
232 for (PsiFile psiFile
: psiFiles
) {
233 if (fileSet
.contains(psiFile
)) {
237 Set
<PsiFile
> tmp
= copy
;
242 if (fileSet
.isEmpty()) break;
245 if (progress
!= null) {
246 progress
.setText(PsiBundle
.message("psi.search.for.word.progress", searcher
.getPattern()));
249 final AtomicInteger counter
= new AtomicInteger(0);
250 final AtomicBoolean canceled
= new AtomicBoolean(false);
251 final AtomicBoolean pceThrown
= new AtomicBoolean(false);
253 final int size
= fileSet
.size();
254 boolean completed
= JobUtil
.invokeConcurrentlyUnderMyProgress(new ArrayList
<PsiFile
>(fileSet
), new Processor
<PsiFile
>() {
255 public boolean process(final PsiFile file
) {
256 if (file
instanceof PsiBinaryFile
) return true;
257 file
.getViewProvider().getContents(); // load contents outside readaction
258 ApplicationManager
.getApplication().runReadAction(new Runnable() {
261 PsiElement
[] psiRoots
= file
.getPsiRoots();
262 Set
<PsiElement
> processed
= new HashSet
<PsiElement
>(psiRoots
.length
* 2, (float)0.5);
263 for (PsiElement psiRoot
: psiRoots
) {
264 ProgressManager
.checkCanceled();
265 if (!processed
.add(psiRoot
)) continue;
266 if (!LowLevelSearchUtil
.processElementsContainingWordInElement(processor
, psiRoot
, searcher
, false)) {
271 if (progress
!= null) {
272 double fraction
= (double)counter
.incrementAndGet() / size
;
273 progress
.setFraction(fraction
);
275 myManager
.dropResolveCaches();
277 catch (ProcessCanceledException e
) {
283 return !canceled
.get();
285 }, "Process usages in files");
287 if (pceThrown
.get()) {
288 throw new ProcessCanceledException();
294 if (progress
!= null) {
297 myManager
.finishBatchFilesProcessingMode();
302 public PsiFile
[] findFilesWithPlainTextWords(@NotNull String word
) {
303 return myManager
.getCacheManager().getFilesWithWord(word
,
304 UsageSearchContext
.IN_PLAIN_TEXT
,
305 GlobalSearchScope
.projectScope(myManager
.getProject()), true);
309 public void processUsagesInNonJavaFiles(@NotNull String qName
,
310 @NotNull PsiNonJavaFileReferenceProcessor processor
,
311 @NotNull GlobalSearchScope searchScope
) {
312 processUsagesInNonJavaFiles(null, qName
, processor
, searchScope
);
315 public void processUsagesInNonJavaFiles(@Nullable final PsiElement originalElement
,
316 @NotNull String qName
,
317 @NotNull final PsiNonJavaFileReferenceProcessor processor
,
318 @NotNull GlobalSearchScope searchScope
) {
319 if (qName
.length() == 0) {
320 throw new IllegalArgumentException("Cannot search for elements with empty text");
322 ProgressManager progressManager
= ProgressManager
.getInstance();
323 ProgressIndicator progress
= progressManager
.getProgressIndicator();
325 int dotIndex
= qName
.lastIndexOf('.');
326 int dollarIndex
= qName
.lastIndexOf('$');
327 int maxIndex
= Math
.max(dotIndex
, dollarIndex
);
328 final String wordToSearch
= maxIndex
>= 0 ? qName
.substring(maxIndex
+ 1) : qName
;
329 if (originalElement
!= null && myManager
.isInProject(originalElement
) && searchScope
.isSearchInLibraries()) {
330 searchScope
= searchScope
.intersectWith(GlobalSearchScope
.projectScope(myManager
.getProject()));
332 final GlobalSearchScope theSearchScope
= searchScope
;
333 PsiFile
[] files
= ApplicationManager
.getApplication().runReadAction(new Computable
<PsiFile
[]>() {
334 public PsiFile
[] compute() {
335 return myManager
.getCacheManager().getFilesWithWord(wordToSearch
, UsageSearchContext
.IN_PLAIN_TEXT
, theSearchScope
, true);
339 final StringSearcher searcher
= new StringSearcher(qName
, true, true);
341 if (progress
!= null) {
342 progress
.pushState();
343 progress
.setText(PsiBundle
.message("psi.search.in.non.java.files.progress"));
346 final Ref
<Boolean
> cancelled
= new Ref
<Boolean
>(Boolean
.FALSE
);
347 final GlobalSearchScope finalScope
= searchScope
;
348 for (int i
= 0; i
< files
.length
; i
++) {
349 progressManager
.checkCanceled();
351 final PsiFile psiFile
= files
[i
];
353 ApplicationManager
.getApplication().runReadAction(new Runnable() {
355 CharSequence text
= psiFile
.getViewProvider().getContents();
356 for (int index
= LowLevelSearchUtil
.searchWord(text
, 0, text
.length(), searcher
); index
>= 0;) {
357 PsiReference referenceAt
= psiFile
.findReferenceAt(index
);
358 if (referenceAt
== null || originalElement
== null ||
359 !PsiSearchScopeUtil
.isInScope(getUseScope(originalElement
).intersectWith(finalScope
), psiFile
)) {
360 if (!processor
.process(psiFile
, index
, index
+ searcher
.getPattern().length())) {
361 cancelled
.set(Boolean
.TRUE
);
366 index
= LowLevelSearchUtil
.searchWord(text
, index
+ searcher
.getPattern().length(), text
.length(), searcher
);
370 if (cancelled
.get()) break;
371 if (progress
!= null) {
372 progress
.setFraction((double)(i
+ 1) / files
.length
);
376 if (progress
!= null) {
381 public void processAllFilesWithWord(@NotNull String word
, @NotNull GlobalSearchScope scope
, @NotNull Processor
<PsiFile
> processor
, final boolean caseSensitively
) {
382 myManager
.getCacheManager().processFilesWithWord(processor
,word
, UsageSearchContext
.IN_CODE
, scope
, caseSensitively
);
385 public void processAllFilesWithWordInText(@NotNull final String word
, @NotNull final GlobalSearchScope scope
, @NotNull final Processor
<PsiFile
> processor
,
386 final boolean caseSensitively
) {
387 myManager
.getCacheManager().processFilesWithWord(processor
,word
, UsageSearchContext
.IN_PLAIN_TEXT
, scope
, caseSensitively
);
390 public void processAllFilesWithWordInComments(@NotNull String word
, @NotNull GlobalSearchScope scope
, @NotNull Processor
<PsiFile
> processor
) {
391 myManager
.getCacheManager().processFilesWithWord(processor
, word
, UsageSearchContext
.IN_COMMENTS
, scope
, true);
394 public void processAllFilesWithWordInLiterals(@NotNull String word
, @NotNull GlobalSearchScope scope
, @NotNull Processor
<PsiFile
> processor
) {
395 myManager
.getCacheManager().processFilesWithWord(processor
, word
, UsageSearchContext
.IN_STRINGS
, scope
, true);