/* * Copyright 2000-2014 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.usages.impl; import com.intellij.find.FindManager; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.KeyboardShortcut; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.colors.CodeInsightColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.util.ProgressWrapper; import com.intellij.openapi.progress.util.TooManyUsagesStatus; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Factory; import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindowId; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.SearchScope; import com.intellij.ui.HyperlinkAdapter; import com.intellij.usageView.UsageViewBundle; import com.intellij.usages.*; import com.intellij.util.Alarm; import com.intellij.util.ArrayUtil; import com.intellij.util.CommonProcessors; import com.intellij.util.Processor; import com.intellij.util.ui.RangeBlinker; import com.intellij.xml.util.XmlStringUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import java.awt.event.ActionEvent; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; class SearchForUsagesRunnable implements Runnable { @NonNls private static final String FIND_OPTIONS_HREF_TARGET = "FindOptions"; @NonNls private static final String SEARCH_IN_PROJECT_HREF_TARGET = "SearchInProject"; @NonNls private static final String LARGE_FILES_HREF_TARGET = "LargeFiles"; private final AtomicInteger myUsageCountWithoutDefinition = new AtomicInteger(0); private final AtomicReference myFirstUsage = new AtomicReference(); @NotNull private final Project myProject; private final AtomicReference myUsageViewRef; private final UsageViewPresentation myPresentation; private final UsageTarget[] mySearchFor; private final Factory mySearcherFactory; private final FindUsagesProcessPresentation myProcessPresentation; @NotNull private final SearchScope mySearchScope; private final UsageViewManager.UsageViewStateListener myListener; private final UsageViewManagerImpl myUsageViewManager; private final AtomicInteger myOutOfScopeUsages = new AtomicInteger(); SearchForUsagesRunnable(@NotNull UsageViewManagerImpl usageViewManager, @NotNull Project project, @NotNull AtomicReference usageViewRef, @NotNull UsageViewPresentation presentation, @NotNull UsageTarget[] searchFor, @NotNull Factory searcherFactory, @NotNull FindUsagesProcessPresentation processPresentation, @NotNull SearchScope scope, @Nullable UsageViewManager.UsageViewStateListener listener) { myProject = project; myUsageViewRef = usageViewRef; myPresentation = presentation; mySearchFor = searchFor; mySearcherFactory = searcherFactory; myProcessPresentation = processPresentation; mySearchScope = scope; myListener = listener; myUsageViewManager = usageViewManager; } @NotNull private static String createOptionsHtml(@NonNls UsageTarget[] searchFor) { KeyboardShortcut shortcut = UsageViewImpl.getShowUsagesWithSettingsShortcut(searchFor); String shortcutText = ""; if (shortcut != null) { shortcutText = " (" + KeymapUtil.getShortcutText(shortcut) + ")"; } return "Find Options..." + shortcutText; } @NotNull private static String createSearchInProjectHtml() { return "Search in Project"; } private static void notifyByFindBalloon(final HyperlinkListener listener, @NotNull final MessageType info, @NotNull FindUsagesProcessPresentation processPresentation, @NotNull final Project project, @NotNull final List lines) { com.intellij.usageView.UsageViewManager.getInstance(project); // in case tool window not registered final Collection largeFiles = processPresentation.getLargeFiles(); List resultLines = new ArrayList(lines); HyperlinkListener resultListener = listener; if (!largeFiles.isEmpty()) { String shortMessage = "(" + UsageViewBundle.message("large.files.were.ignored", largeFiles.size()) + ")"; resultLines.add(shortMessage); resultListener = new HyperlinkAdapter(){ @Override protected void hyperlinkActivated(HyperlinkEvent e) { if (e.getDescription().equals(LARGE_FILES_HREF_TARGET)) { String detailedMessage = detailedLargeFilesMessage(largeFiles); List strings = new ArrayList(lines); strings.add(detailedMessage); ToolWindowManager.getInstance(project).notifyByBalloon(ToolWindowId.FIND, info, wrapInHtml(strings), AllIcons.Actions.Find, listener); } else if (listener != null) { listener.hyperlinkUpdate(e); } } }; } ToolWindowManager.getInstance(project).notifyByBalloon(ToolWindowId.FIND, info, wrapInHtml(resultLines), AllIcons.Actions.Find, resultListener); } @NotNull private static String wrapInHtml(@NotNull List strings) { return XmlStringUtil.wrapInHtml(StringUtil.join(strings, "
")); } @NotNull private static String detailedLargeFilesMessage(@NotNull Collection largeFiles) { String message = ""; if (largeFiles.size() == 1) { final VirtualFile vFile = largeFiles.iterator().next().getVirtualFile(); message += "File " + presentableFileInfo(vFile) + " is "; } else { message += "Files
"; int counter = 0; for (PsiFile file : largeFiles) { final VirtualFile vFile = file.getVirtualFile(); message += presentableFileInfo(vFile) + "
"; if (counter++ > 10) break; } message += "are "; } message += "too large and cannot be scanned"; return message; } @NotNull private static String presentableFileInfo(@NotNull VirtualFile vFile) { return getPresentablePath(vFile) + " (" + UsageViewManagerImpl.presentableSize(UsageViewManagerImpl.getFileLength(vFile)) + ")"; } @NotNull private static String getPresentablePath(@NotNull final VirtualFile virtualFile) { return "'" + ApplicationManager.getApplication().runReadAction(new Computable() { @Override public String compute() { return virtualFile.getPresentableUrl(); } }) + "'"; } @NotNull private HyperlinkListener createGotToOptionsListener(@NotNull final UsageTarget[] targets) { return new HyperlinkAdapter() { @Override protected void hyperlinkActivated(HyperlinkEvent e) { if (e.getDescription().equals(FIND_OPTIONS_HREF_TARGET)) { FindManager.getInstance(myProject).showSettingsAndFindUsages(targets); } } }; } @NotNull private HyperlinkListener createSearchInProjectListener() { return new HyperlinkAdapter() { @Override protected void hyperlinkActivated(HyperlinkEvent e) { if (e.getDescription().equals(SEARCH_IN_PROJECT_HREF_TARGET)) { PsiElement psiElement = getPsiElement(mySearchFor); if (psiElement != null) { FindManager.getInstance(myProject).findUsagesInScope(psiElement, GlobalSearchScope.projectScope(myProject)); } } } }; } private static PsiElement getPsiElement(UsageTarget[] searchFor) { if (!(searchFor[0] instanceof PsiElementUsageTarget)) return null; return ((PsiElementUsageTarget)searchFor[0]).getElement(); } private static void flashUsageScriptaculously(@NotNull final Usage usage) { if (!(usage instanceof UsageInfo2UsageAdapter)) { return; } UsageInfo2UsageAdapter usageInfo = (UsageInfo2UsageAdapter)usage; Editor editor = usageInfo.openTextEditor(true); if (editor == null) return; TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.BLINKING_HIGHLIGHTS_ATTRIBUTES); RangeBlinker rangeBlinker = new RangeBlinker(editor, attributes, 6); List segments = new ArrayList(); CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor(segments); usageInfo.processRangeMarkers(processor); rangeBlinker.resetMarkers(segments); rangeBlinker.startBlinking(); } private UsageViewImpl getUsageView(ProgressIndicator indicator) { UsageViewImpl usageView = myUsageViewRef.get(); if (usageView != null) return usageView; int usageCount = myUsageCountWithoutDefinition.get(); if (usageCount >= 2 || usageCount == 1 && myProcessPresentation.isShowPanelIfOnlyOneUsage()) { usageView = new UsageViewImpl(myProject, myPresentation, mySearchFor, mySearcherFactory); usageView.associateProgress(indicator); if (myUsageViewRef.compareAndSet(null, usageView)) { openView(usageView); final Usage firstUsage = myFirstUsage.get(); if (firstUsage != null) { final UsageViewImpl finalUsageView = usageView; ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { finalUsageView.appendUsage(firstUsage); } }); } } else { Disposer.dispose(usageView); } return myUsageViewRef.get(); } return null; } private void openView(@NotNull final UsageViewImpl usageView) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (myProject.isDisposed()) return; myUsageViewManager.addContent(usageView, myPresentation); if (myListener != null) { myListener.usageViewCreated(usageView); } myUsageViewManager.showToolWindow(false); } }); } @Override public void run() { AtomicBoolean findUsagesStartedShown = new AtomicBoolean(); searchUsages(findUsagesStartedShown); endSearchForUsages(findUsagesStartedShown); } private void searchUsages(@NotNull final AtomicBoolean findStartedBalloonShown) { ProgressIndicator indicator = ProgressWrapper.unwrap(ProgressManager.getInstance().getProgressIndicator()); TooManyUsagesStatus.createFor(indicator); Alarm findUsagesStartedBalloon = new Alarm(); findUsagesStartedBalloon.addRequest(new Runnable() { @Override public void run() { notifyByFindBalloon(null, MessageType.WARNING, myProcessPresentation, myProject, Collections.singletonList(UsageViewManagerImpl.getProgressTitle(myPresentation))); findStartedBalloonShown.set(true); } }, 300, ModalityState.NON_MODAL); UsageSearcher usageSearcher = mySearcherFactory.create(); usageSearcher.generate(new Processor() { @Override public boolean process(final Usage usage) { ProgressIndicator indicator = ProgressWrapper.unwrap(ProgressManager.getInstance().getProgressIndicator()); if (indicator != null && indicator.isCanceled()) return false; if (!UsageViewManagerImpl.isInScope(usage, mySearchScope)) { myOutOfScopeUsages.incrementAndGet(); return true; } boolean incrementCounter = !UsageViewManager.isSelfUsage(usage, mySearchFor); if (incrementCounter) { final int usageCount = myUsageCountWithoutDefinition.incrementAndGet(); if (usageCount == 1 && !myProcessPresentation.isShowPanelIfOnlyOneUsage()) { myFirstUsage.compareAndSet(null, usage); } final UsageViewImpl usageView = getUsageView(indicator); TooManyUsagesStatus tooManyUsagesStatus; if (usageCount > UsageLimitUtil.USAGES_LIMIT && (tooManyUsagesStatus = TooManyUsagesStatus.getFrom(indicator)).switchTooManyUsagesStatus()) { UsageViewManagerImpl.showTooManyUsagesWarning(myProject, tooManyUsagesStatus, indicator, myPresentation, usageCount, usageView); } if (usageView != null) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { usageView.appendUsage(usage); } }); } } return indicator == null || !indicator.isCanceled(); } }); if (getUsageView(indicator) != null) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { myUsageViewManager.showToolWindow(true); } }, myProject.getDisposed()); } Disposer.dispose(findUsagesStartedBalloon); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { if (findStartedBalloonShown.get()) { Balloon balloon = ToolWindowManager.getInstance(myProject).getToolWindowBalloon(ToolWindowId.FIND); if (balloon != null) { balloon.hide(); } } } }, myProject.getDisposed()); } private void endSearchForUsages(@NotNull final AtomicBoolean findStartedBalloonShown) { assert !ApplicationManager.getApplication().isDispatchThread() : Thread.currentThread(); int usageCount = myUsageCountWithoutDefinition.get(); if (usageCount == 0 && myProcessPresentation.isShowNotFoundMessage()) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { final List notFoundActions = myProcessPresentation.getNotFoundActions(); final String message = UsageViewBundle.message("dialog.no.usages.found.in", StringUtil.decapitalize(myPresentation.getUsagesString()), myPresentation.getScopeText()); if (notFoundActions.isEmpty()) { List lines = new ArrayList(); lines.add(StringUtil.escapeXml(message)); if (myOutOfScopeUsages.get() != 0) { lines.add(UsageViewManagerImpl.outOfScopeMessage(myOutOfScopeUsages.get(), mySearchScope)); } if (myProcessPresentation.isShowFindOptionsPrompt()) { lines.add(createOptionsHtml(mySearchFor)); } MessageType type = myOutOfScopeUsages.get() == 0 ? MessageType.INFO : MessageType.WARNING; notifyByFindBalloon(createGotToOptionsListener(mySearchFor), type, myProcessPresentation, myProject, lines); findStartedBalloonShown.set(false); } else { List titles = new ArrayList(notFoundActions.size() + 1); titles.add(UsageViewBundle.message("dialog.button.ok")); for (Action action : notFoundActions) { Object value = action.getValue(FindUsagesProcessPresentation.NAME_WITH_MNEMONIC_KEY); if (value == null) value = action.getValue(Action.NAME); titles.add((String)value); } int option = Messages.showDialog(myProject, message, UsageViewBundle.message("dialog.title.information"), ArrayUtil.toStringArray(titles), 0, Messages.getInformationIcon()); if (option > 0) { notFoundActions.get(option - 1).actionPerformed(new ActionEvent(this, 0, titles.get(option))); } } } }, ModalityState.NON_MODAL, myProject.getDisposed()); } else if (usageCount == 1 && !myProcessPresentation.isShowPanelIfOnlyOneUsage()) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { Usage usage = myFirstUsage.get(); if (usage.canNavigate()) { usage.navigate(true); flashUsageScriptaculously(usage); } List lines = new ArrayList(); lines.add("Only one usage found."); if (myOutOfScopeUsages.get() != 0) { lines.add(UsageViewManagerImpl.outOfScopeMessage(myOutOfScopeUsages.get(), mySearchScope)); } lines.add(createOptionsHtml(mySearchFor)); MessageType type = myOutOfScopeUsages.get() == 0 ? MessageType.INFO : MessageType.WARNING; notifyByFindBalloon(createGotToOptionsListener(mySearchFor), type, myProcessPresentation, myProject, lines); } }, ModalityState.NON_MODAL, myProject.getDisposed()); } else { final UsageViewImpl usageView = myUsageViewRef.get(); if (usageView != null) { usageView.drainQueuedUsageNodes(); usageView.setSearchInProgress(false); } final List lines; final HyperlinkListener hyperlinkListener; if (myOutOfScopeUsages.get() == 0 || getPsiElement(mySearchFor)==null) { lines = Collections.emptyList(); hyperlinkListener = null; } else { lines = Arrays.asList(UsageViewManagerImpl.outOfScopeMessage(myOutOfScopeUsages.get(), mySearchScope), createSearchInProjectHtml()); hyperlinkListener = createSearchInProjectListener(); } if (!myProcessPresentation.getLargeFiles().isEmpty() || myOutOfScopeUsages.get() != 0) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { MessageType type = myOutOfScopeUsages.get() == 0 ? MessageType.INFO : MessageType.WARNING; notifyByFindBalloon(hyperlinkListener, type, myProcessPresentation, myProject, lines); } }, ModalityState.NON_MODAL, myProject.getDisposed()); } } if (myListener != null) { myListener.findingUsagesFinished(myUsageViewRef.get()); } } }