blob: db82791478f0488144d564ae1eaa1e213cbe3901 [file] [log] [blame]
/*
* 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.find.impl;
import com.intellij.BundleBase;
import com.intellij.find.*;
import com.intellij.find.findInProject.FindInProjectManager;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProcessCanceledException;
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.DumbServiceImpl;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Factory;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileProvider;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.ex.VirtualFileManagerEx;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.ui.content.Content;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewManager;
import com.intellij.usages.ConfigurableUsageTarget;
import com.intellij.usages.FindUsagesProcessPresentation;
import com.intellij.usages.UsageViewPresentation;
import com.intellij.util.Function;
import com.intellij.util.PatternUtil;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.File;
import java.util.List;
import java.util.regex.Pattern;
public class FindInProjectUtil {
private static final int USAGES_PER_READ_ACTION = 100;
private FindInProjectUtil() {}
public static void setDirectoryName(@NotNull FindModel model, @NotNull DataContext dataContext) {
PsiElement psiElement = null;
Project project = CommonDataKeys.PROJECT.getData(dataContext);
if (project != null && !DumbServiceImpl.getInstance(project).isDumb()) {
try {
psiElement = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
} catch (IndexNotReadyException ignore) {}
}
String directoryName = null;
if (psiElement instanceof PsiDirectory) {
directoryName = ((PsiDirectory)psiElement).getVirtualFile().getPresentableUrl();
}
if (directoryName == null && psiElement instanceof PsiDirectoryContainer) {
final PsiDirectory[] directories = ((PsiDirectoryContainer)psiElement).getDirectories();
directoryName = directories.length == 1 ? directories[0].getVirtualFile().getPresentableUrl():null;
}
Module module = LangDataKeys.MODULE_CONTEXT.getData(dataContext);
if (module != null) {
model.setModuleName(module.getName());
}
Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
if (model.getModuleName() == null || editor == null) {
model.setDirectoryName(directoryName);
model.setProjectScope(directoryName == null && module == null && !model.isCustomScope() || editor != null);
// for convenience set directory name to directory of current file, note that we doesn't change default projectScope
if (directoryName == null) {
VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
if (virtualFile != null && !virtualFile.isDirectory()) virtualFile = virtualFile.getParent();
if (virtualFile != null) model.setDirectoryName(virtualFile.getPresentableUrl());
}
}
}
@Nullable
public static PsiDirectory getPsiDirectory(@NotNull final FindModel findModel, @NotNull Project project) {
String directoryName = findModel.getDirectoryName();
if (findModel.isProjectScope() || directoryName == null) {
return null;
}
final PsiManager psiManager = PsiManager.getInstance(project);
String path = directoryName.replace(File.separatorChar, '/');
VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(path);
if (virtualFile == null || !virtualFile.isDirectory()) {
virtualFile = null;
for (LocalFileProvider provider : ((VirtualFileManagerEx)VirtualFileManager.getInstance()).getLocalFileProviders()) {
VirtualFile file = provider.findLocalVirtualFileByPath(path);
if (file != null && file.isDirectory()) {
if (file.getChildren().length > 0) {
virtualFile = file;
break;
}
if(virtualFile == null){
virtualFile = file;
}
}
}
}
return virtualFile == null ? null : psiManager.findDirectory(virtualFile);
}
@Nullable
public static Pattern createFileMaskRegExp(@Nullable String filter) {
if (filter == null) {
return null;
}
String pattern;
final List<String> strings = StringUtil.split(filter, ",");
if (strings.size() == 1) {
pattern = PatternUtil.convertToRegex(filter.trim());
}
else {
pattern = StringUtil.join(strings, new Function<String, String>() {
@NotNull
@Override
public String fun(@NotNull String s) {
return "(" + PatternUtil.convertToRegex(s.trim()) + ")";
}
}, "|");
}
return Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
}
public static void findUsages(@NotNull FindModel findModel,
final PsiDirectory psiDirectory,
@NotNull final Project project,
@NotNull final Processor<UsageInfo> consumer,
@NotNull FindUsagesProcessPresentation processPresentation) {
new FindInProjectTask(findModel, project, psiDirectory).findUsages(consumer, processPresentation);
}
static int processUsagesInFile(@NotNull final PsiFile psiFile,
@NotNull final FindModel findModel,
@NotNull final Processor<UsageInfo> consumer) {
if (findModel.getStringToFind().isEmpty()) {
if (!ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
@Override
public Boolean compute() {
return consumer.process(new UsageInfo(psiFile,0,0,true));
}
})) {
throw new ProcessCanceledException();
}
return 1;
}
final VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile == null) return 0;
if (virtualFile.getFileType().isBinary()) return 0; // do not decompile .class files
final Document document = ApplicationManager.getApplication().runReadAction(new Computable<Document>() {
@Override
public Document compute() {
return virtualFile.isValid() ? FileDocumentManager.getInstance().getDocument(virtualFile) : null;
}
});
if (document == null) return 0;
final int[] offset = {0};
int count = 0;
int found;
ProgressIndicator indicator = ProgressWrapper.unwrap(ProgressManager.getInstance().getProgressIndicator());
TooManyUsagesStatus tooManyUsagesStatus = TooManyUsagesStatus.getFrom(indicator);
do {
tooManyUsagesStatus.pauseProcessingIfTooManyUsages(); // wait for user out of read action
found = ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
@Override
@NotNull
public Integer compute() {
if (!psiFile.isValid()) return 0;
return addToUsages(document, consumer, findModel, psiFile, offset, USAGES_PER_READ_ACTION);
}
});
count += found;
}
while (found != 0);
return count;
}
private static int addToUsages(@NotNull Document document, @NotNull Processor<UsageInfo> consumer, @NotNull FindModel findModel,
@NotNull final PsiFile psiFile, @NotNull int[] offsetRef, int maxUsages) {
int count = 0;
CharSequence text = document.getCharsSequence();
int textLength = document.getTextLength();
int offset = offsetRef[0];
Project project = psiFile.getProject();
FindManager findManager = FindManager.getInstance(project);
while (offset < textLength) {
FindResult result = findManager.findString(text, offset, findModel, psiFile.getVirtualFile());
if (!result.isStringFound()) break;
final SearchScope customScope = findModel.getCustomScope();
if (customScope instanceof LocalSearchScope) {
final TextRange range = new TextRange(result.getStartOffset(), result.getEndOffset());
if (!((LocalSearchScope)customScope).containsRange(psiFile, range)) break;
}
UsageInfo info = new FindResultUsageInfo(findManager, psiFile, offset, findModel, result);
if (!consumer.process(info)){
throw new ProcessCanceledException();
}
count++;
final int prevOffset = offset;
offset = result.getEndOffset();
if (prevOffset == offset) {
// for regular expr the size of the match could be zero -> could be infinite loop in finding usages!
++offset;
}
if (maxUsages > 0 && count >= maxUsages) {
break;
}
}
offsetRef[0] = offset;
return count;
}
@NotNull
private static String getTitleForScope(@NotNull final FindModel findModel) {
String scopeName;
if (findModel.isProjectScope()) {
scopeName = FindBundle.message("find.scope.project.title");
}
else if (findModel.getModuleName() != null) {
scopeName = FindBundle.message("find.scope.module.title", findModel.getModuleName());
}
else if(findModel.getCustomScopeName() != null) {
scopeName = findModel.getCustomScopeName();
}
else {
scopeName = FindBundle.message("find.scope.directory.title", findModel.getDirectoryName());
}
String result = scopeName;
if (findModel.getFileFilter() != null) {
result += " "+FindBundle.message("find.scope.files.with.mask", findModel.getFileFilter());
}
return result;
}
@NotNull
public static UsageViewPresentation setupViewPresentation(final boolean toOpenInNewTab, @NotNull FindModel findModel) {
final UsageViewPresentation presentation = new UsageViewPresentation();
final String scope = getTitleForScope(findModel);
final String stringToFind = findModel.getStringToFind();
presentation.setScopeText(scope);
if (stringToFind.isEmpty()) {
presentation.setTabText("Files");
presentation.setToolwindowTitle(BundleBase.format("Files in {0}", scope));
presentation.setUsagesString("files");
}
else {
presentation.setTabText(FindBundle.message("find.usage.view.tab.text", stringToFind));
presentation.setToolwindowTitle(FindBundle.message("find.usage.view.toolwindow.title", stringToFind, scope));
presentation.setUsagesString(FindBundle.message("find.usage.view.usages.text", stringToFind));
presentation.setUsagesWord(FindBundle.message("occurrence"));
presentation.setCodeUsagesString(FindBundle.message("found.occurrences"));
}
presentation.setOpenInNewTab(toOpenInNewTab);
presentation.setCodeUsages(false);
presentation.setUsageTypeFilteringAvailable(true);
return presentation;
}
@NotNull
public static FindUsagesProcessPresentation setupProcessPresentation(@NotNull final Project project,
final boolean showPanelIfOnlyOneUsage,
@NotNull final UsageViewPresentation presentation) {
FindUsagesProcessPresentation processPresentation = new FindUsagesProcessPresentation(presentation);
processPresentation.setShowNotFoundMessage(true);
processPresentation.setShowPanelIfOnlyOneUsage(showPanelIfOnlyOneUsage);
processPresentation.setProgressIndicatorFactory(
new Factory<ProgressIndicator>() {
@NotNull
@Override
public ProgressIndicator create() {
return new FindProgressIndicator(project, presentation.getScopeText());
}
}
);
return processPresentation;
}
public static class StringUsageTarget implements ConfigurableUsageTarget, ItemPresentation {
@NotNull protected final Project myProject;
@NotNull protected final FindModel myFindModel;
public StringUsageTarget(@NotNull Project project, @NotNull FindModel findModel) {
myProject = project;
myFindModel = findModel;
}
@Override
@NotNull
public String getPresentableText() {
UsageViewPresentation presentation = setupViewPresentation(false, myFindModel);
return presentation.getToolwindowTitle();
}
@NotNull
@Override
public String getLongDescriptiveName() {
return getPresentableText();
}
@Override
public String getLocationString() {
return myFindModel + "!!";
}
@Override
public Icon getIcon(boolean open) {
return AllIcons.Actions.Menu_find;
}
@Override
public void findUsages() {
FindInProjectManager.getInstance(myProject).startFindInProject(myFindModel);
}
@Override
public void findUsagesInEditor(@NotNull FileEditor editor) {}
@Override
public void highlightUsages(@NotNull PsiFile file, @NotNull Editor editor, boolean clearHighlights) {}
@Override
public boolean isValid() {
return true;
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
@Nullable
public VirtualFile[] getFiles() {
return null;
}
@Override
public void update() {
}
@Override
public String getName() {
return myFindModel.getStringToFind().isEmpty() ? myFindModel.getFileFilter() : myFindModel.getStringToFind();
}
@Override
public ItemPresentation getPresentation() {
return this;
}
@Override
public void navigate(boolean requestFocus) {
throw new UnsupportedOperationException();
}
@Override
public boolean canNavigate() {
return false;
}
@Override
public boolean canNavigateToSource() {
return false;
}
@Override
public void showSettings() {
Content selectedContent = UsageViewManager.getInstance(myProject).getSelectedContent(true);
JComponent component = selectedContent == null ? null : selectedContent.getComponent();
FindInProjectManager findInProjectManager = FindInProjectManager.getInstance(myProject);
findInProjectManager.findInProject(DataManager.getInstance().getDataContext(component));
}
@Override
public KeyboardShortcut getShortcut() {
return ActionManager.getInstance().getKeyboardShortcut("FindInPath");
}
}
}