/*
* Copyright 2000-2015 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.SearchInBackgroundOption;
import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.actionSystem.DataSink;
import com.intellij.openapi.actionSystem.TypeSafeDataProvider;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.util.TooManyUsagesStatus;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Factory;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.*;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.content.Content;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usages.*;
import com.intellij.usages.rules.PsiElementUsage;
import com.intellij.usages.rules.UsageInFile;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author max
*/
public class UsageViewManagerImpl extends UsageViewManager {
private static final Logger LOG = Logger.getInstance(UsageViewManagerImpl.class);
private final Project myProject;
private static final Key<UsageView> USAGE_VIEW_KEY = Key.create("USAGE_VIEW");
public UsageViewManagerImpl(@NotNull Project project) {
myProject = project;
}
@Override
@NotNull
public UsageView createUsageView(@NotNull UsageTarget[] targets,
@NotNull Usage[] usages,
@NotNull UsageViewPresentation presentation,
Factory<UsageSearcher> usageSearcherFactory) {
UsageViewImpl usageView = new UsageViewImpl(myProject, presentation, targets, usageSearcherFactory);
appendUsages(usages, usageView);
usageView.setSearchInProgress(false);
return usageView;
}
@Override
@NotNull
public UsageView showUsages(@NotNull UsageTarget[] searchedFor,
@NotNull Usage[] foundUsages,
@NotNull UsageViewPresentation presentation,
Factory<UsageSearcher> factory) {
UsageView usageView = createUsageView(searchedFor, foundUsages, presentation, factory);
addContent((UsageViewImpl)usageView, presentation);
showToolWindow(true);
UIUtil.invokeLaterIfNeeded(() -> {
if (!((UsageViewImpl)usageView).isDisposed()) {
((UsageViewImpl)usageView).expandRoot();
}
});
return usageView;
}
@Override
@NotNull
public UsageView showUsages(@NotNull UsageTarget[] searchedFor, @NotNull Usage[] foundUsages, @NotNull UsageViewPresentation presentation) {
return showUsages(searchedFor, foundUsages, presentation, null);
}
void addContent(@NotNull UsageViewImpl usageView, @NotNull UsageViewPresentation presentation) {
Content content = com.intellij.usageView.UsageViewManager.getInstance(myProject)
.addContent(presentation.getTabText(), presentation.getTabName(), presentation.getToolwindowTitle(), true, usageView.getComponent(),
presentation.isOpenInNewTab(), true);
usageView.setContent(content);
content.putUserData(USAGE_VIEW_KEY, usageView);
}
@Override
public UsageView searchAndShowUsages(@NotNull final UsageTarget[] searchFor,
@NotNull final Factory<UsageSearcher> searcherFactory,
final boolean showPanelIfOnlyOneUsage,
final boolean showNotFoundMessage,
@NotNull final UsageViewPresentation presentation,
@Nullable final UsageViewStateListener listener) {
final FindUsagesProcessPresentation processPresentation = new FindUsagesProcessPresentation(presentation);
processPresentation.setShowNotFoundMessage(showNotFoundMessage);
processPresentation.setShowPanelIfOnlyOneUsage(showPanelIfOnlyOneUsage);
return doSearchAndShow(searchFor, searcherFactory, presentation, processPresentation, listener);
}
private UsageView doSearchAndShow(@NotNull final UsageTarget[] searchFor,
@NotNull final Factory<UsageSearcher> searcherFactory,
@NotNull final UsageViewPresentation presentation,
@NotNull final FindUsagesProcessPresentation processPresentation,
@Nullable final UsageViewStateListener listener) {
final SearchScope searchScopeToWarnOfFallingOutOf = getMaxSearchScopeToWarnOfFallingOutOf(searchFor);
final AtomicReference<UsageViewImpl> usageViewRef = new AtomicReference<>();
long start = System.currentTimeMillis();
Task.Backgroundable task = new Task.Backgroundable(myProject, getProgressTitle(presentation), true, new SearchInBackgroundOption()) {
@Override
public void run(@NotNull final ProgressIndicator indicator) {
new SearchForUsagesRunnable(UsageViewManagerImpl.this, UsageViewManagerImpl.this.myProject, usageViewRef, presentation, searchFor, searcherFactory,
processPresentation, searchScopeToWarnOfFallingOutOf, listener).run();
}
@NotNull
@Override
public NotificationInfo getNotificationInfo() {
UsageViewImpl usageView = usageViewRef.get();
int count = usageView == null ? 0 : usageView.getUsagesCount();
String notification = StringUtil.capitalizeWords(UsageViewBundle.message("usages.n", count), true);
LOG.debug(notification + " in " + (System.currentTimeMillis() - start) + "ms.");
return new NotificationInfo("Find Usages", "Find Usages Finished", notification);
}
};
ProgressManager.getInstance().run(task);
return usageViewRef.get();
}
@NotNull
SearchScope getMaxSearchScopeToWarnOfFallingOutOf(@NotNull UsageTarget[] searchFor) {
UsageTarget target = searchFor.length > 0 ? searchFor[0] : null;
if (target instanceof TypeSafeDataProvider) {
final SearchScope[] scope = new SearchScope[1];
((TypeSafeDataProvider)target).calcData(UsageView.USAGE_SCOPE, new DataSink() {
@Override
public <T> void put(DataKey<T> key, T data) {
scope[0] = (SearchScope)data;
}
});
return scope[0];
}
return GlobalSearchScope.allScope(myProject); // by default do not warn of falling out of scope
}
@Override
public void searchAndShowUsages(@NotNull UsageTarget[] searchFor,
@NotNull Factory<UsageSearcher> searcherFactory,
@NotNull FindUsagesProcessPresentation processPresentation,
@NotNull UsageViewPresentation presentation,
@Nullable UsageViewStateListener listener) {
doSearchAndShow(searchFor, searcherFactory, presentation, processPresentation, listener);
}
@Override
public UsageView getSelectedUsageView() {
final Content content = com.intellij.usageView.UsageViewManager.getInstance(myProject).getSelectedContent();
if (content != null) {
return content.getUserData(USAGE_VIEW_KEY);
}
return null;
}
@NotNull
public static String getProgressTitle(@NotNull UsageViewPresentation presentation) {
final String scopeText = presentation.getScopeText();
String usagesString = StringUtil.capitalize(presentation.getUsagesString());
return UsageViewBundle.message("progress.searching.for.in", usagesString, scopeText, presentation.getContextText());
}
void showToolWindow(boolean activateWindow) {
ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.FIND);
toolWindow.show(null);
if (activateWindow && !toolWindow.isActive()) {
toolWindow.activate(null);
}
}
protected static void appendUsages(@NotNull final Usage[] foundUsages, @NotNull final UsageViewImpl usageView) {
ApplicationManager.getApplication().runReadAction(() -> {
for (Usage foundUsage : foundUsages) {
usageView.appendUsage(foundUsage);
}
});
}
public static void showTooManyUsagesWarning(@NotNull final Project project,
@NotNull final TooManyUsagesStatus tooManyUsagesStatus,
@NotNull final ProgressIndicator indicator,
@NotNull final UsageViewPresentation presentation,
final int usageCount,
@Nullable final UsageViewImpl usageView) {
UIUtil.invokeLaterIfNeeded(() -> {
if (usageView != null && usageView.searchHasBeenCancelled() || indicator.isCanceled()) return;
int shownUsageCount = usageView == null ? usageCount : usageView.getRoot().getRecursiveUsageCount();
String message = UsageViewBundle.message("find.excessive.usage.count.prompt", shownUsageCount, StringUtil.pluralize(presentation.getUsagesWord()));
UsageLimitUtil.Result ret = UsageLimitUtil.showTooManyUsagesWarning(project, message, presentation);
if (ret == UsageLimitUtil.Result.ABORT) {
if (usageView != null) {
usageView.cancelCurrentSearch();
}
indicator.cancel();
}
tooManyUsagesStatus.userResponded();
});
}
public static long getFileLength(@NotNull final VirtualFile virtualFile) {
final long[] length = {-1L};
ApplicationManager.getApplication().runReadAction(() -> {
if (!virtualFile.isValid()) return;
length[0] = virtualFile.getLength();
});
return length[0];
}
@NotNull
public static String presentableSize(long bytes) {
long megabytes = bytes / (1024 * 1024);
return UsageViewBundle.message("find.file.size.megabytes", Long.toString(megabytes));
}
public static boolean isInScope(@NotNull Usage usage, @NotNull SearchScope searchScope) {
PsiElement element = null;
VirtualFile file = usage instanceof UsageInFile
? ((UsageInFile)usage).getFile()
: usage instanceof PsiElementUsage ? PsiUtilCore.getVirtualFile(element = ((PsiElementUsage)usage).getElement()) : null;
if (file != null) {
return isFileInScope(file, searchScope);
}
return element != null &&
(searchScope instanceof EverythingGlobalScope || searchScope instanceof ProjectScopeImpl || searchScope instanceof ProjectAndLibrariesScope);
}
private static boolean isFileInScope(@NotNull VirtualFile file, @NotNull SearchScope searchScope) {
if (file instanceof VirtualFileWindow) {
file = ((VirtualFileWindow)file).getDelegate();
}
return searchScope.contains(file);
}
@NotNull
public static String outOfScopeMessage(int nUsages, @NotNull SearchScope searchScope) {
return (nUsages == 1 ? "One usage is" : nUsages + " usages are") + " out of scope '" + searchScope.getDisplayName() + "'";
}
}