ComponentWithBrowseButton - optional remove listener on hide
[fedora-idea.git] / platform / lang-impl / src / com / intellij / psi / impl / search / PsiSearchHelperImpl.java
blobfe97f81cca293b77edaaefb875d66583e80d9576
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.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;
43 import java.util.*;
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];
53 static {
54 IndexPatternSearch.INDEX_PATTERN_SEARCH_INSTANCE = new IndexPatternSearchImpl();
57 @NotNull
58 public SearchScope getUseScope(@NotNull PsiElement element) {
59 return element.getUseScope();
63 public PsiSearchHelperImpl(PsiManagerEx manager) {
64 myManager = manager;
67 @NotNull
68 public PsiFile[] findFilesWithTodoItems() {
69 return myManager.getCacheManager().getFilesWithTodoItems();
72 @NotNull
73 public TodoItem[] findTodoItems(@NotNull PsiFile file) {
74 return doFindTodoItems(file, new TextRange(0, file.getTextLength()));
77 @NotNull
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) {
103 return todoPattern;
106 LOG.assertTrue(false, "Could not find matching TODO pattern for index pattern " + pattern.getPatternString());
107 return null;
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);
120 count = 0;
121 for (TodoItem item : items) {
122 if (item.getPattern().equals(pattern)) count++;
124 return count;
127 @NotNull
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);
135 return true;
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);
155 return true;
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,
164 short searchContext,
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,
174 searcher,
175 searchContext, caseSensitively);
177 else {
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;
185 return true;
189 private static boolean processElementsWithWordInScopeElement(final PsiElement scopeElement,
190 final TextOccurenceProcessor processor,
191 final String word,
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);
200 }).booleanValue();
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();
216 try {
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));
231 else {
232 for (PsiFile psiFile : psiFiles) {
233 if (fileSet.contains(psiFile)) {
234 copy.add(psiFile);
237 Set<PsiFile> tmp = copy;
238 copy = fileSet;
239 fileSet = tmp;
240 copy.clear();
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() {
259 public void run() {
260 try {
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)) {
267 canceled.set(true);
268 return;
271 if (progress != null) {
272 double fraction = (double)counter.incrementAndGet() / size;
273 progress.setFraction(fraction);
275 myManager.dropResolveCaches();
277 catch (ProcessCanceledException e) {
278 canceled.set(true);
279 pceThrown.set(true);
283 return !canceled.get();
285 }, "Process usages in files");
287 if (pceThrown.get()) {
288 throw new ProcessCanceledException();
291 return completed;
293 finally {
294 if (progress != null) {
295 progress.popState();
297 myManager.finishBatchFilesProcessingMode();
301 @NotNull
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() {
354 public void run() {
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);
362 return;
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) {
377 progress.popState();
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);