diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java index ad6002473a98..976802fd04cd 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java @@ -85,6 +85,7 @@ import java.util.regex.Pattern; public class FindInProjectUtil { private static final int USAGES_LIMIT = 1000; + private static final int USAGES_PER_READ_ACTION = 100; private static final int FILES_SIZE_LIMIT = 70 * 1024 * 1024; // megabytes. private static final int SINGLE_FILE_SIZE_LIMIT = 5 * 1024 * 1024; // megabytes. @@ -218,7 +219,7 @@ public class FindInProjectUtil { int i = 0; long totalFilesSize = 0; int count = 0; - boolean warningShown = false; + final boolean[] warningShown = new boolean[] {false}; final UsageViewManager usageViewManager = UsageViewManager.getInstance(project); for (final PsiFile psiFile : psiFiles) { @@ -252,22 +253,16 @@ public class FindInProjectUtil { progress.setText2(FindBundle.message("find.searching.for.string.in.file.occurrences.progress", count)); } - int countInFile = processUsagesInFile(psiFile, findModel, consumer); + int countInFile = processUsagesInFile(psiFile, findModel, consumer, count, warningShown); if (countInFile > 0) { totalFilesSize += fileLength; - if (totalFilesSize > FILES_SIZE_LIMIT && !warningShown) { + if (totalFilesSize > FILES_SIZE_LIMIT && !warningShown[0]) { showTooManyUsagesWaring(project, FindBundle.message("find.excessive.total.size.prompt", presentableSize(totalFilesSize), ApplicationNamesInfo.getInstance().getProductName())); - warningShown = true; + warningShown[0] = true; } } - - count += countInFile; - if (count > USAGES_LIMIT && !warningShown) { - showTooManyUsagesWaring(project, FindBundle.message("find.excessive.usage.count.prompt", count)); - warningShown = true; - } } if (!largeFiles.isEmpty()) { @@ -317,22 +312,37 @@ public class FindInProjectUtil { } } - private static int processUsagesInFile(final PsiFile psiFile, final FindModel findModel, final Processor consumer) { - return ApplicationManager.getApplication().runReadAction(new Computable() { - public Integer compute() { - if (psiFile.isValid()) { - VirtualFile virtualFile = psiFile.getVirtualFile(); - // Check once more if valid and text since we're in new read action and things might have been changed. - if (FileTypeManager.getInstance().getFileTypeByFile(virtualFile).isBinary()) return 0; // do not decompile .class files - final Document document = FileDocumentManager.getInstance().getDocument(virtualFile); - if (document != null) { - return addToUsages(document, consumer, findModel, psiFile); - } + private static int processUsagesInFile(final PsiFile psiFile, + final FindModel findModel, + final Processor consumer, + int alreadyCounted, + boolean[] warningShown) { + final VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile == null) return 0; + if (FileTypeManager.getInstance().getFileTypeByFile(virtualFile).isBinary()) return 0; // do not decompile .class files + final Document document = FileDocumentManager.getInstance().getDocument(virtualFile); + if (document == null) return 0; + final int[] offset = new int[] {0}; + int count = 0; + int found; + do { + found = ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + @NotNull + public Integer compute() { + if (!psiFile.isValid()) return 0; + return addToUsages(document, consumer, findModel, psiFile, offset, USAGES_PER_READ_ACTION); } - - return 0; + }); + count += found; + if (found > 0 && count + alreadyCounted > USAGES_LIMIT && !warningShown[0]) { + showTooManyUsagesWaring(psiFile.getProject(), FindBundle.message("find.excessive.usage.count.prompt", count)); + warningShown[0] = true; } - }).intValue(); + + } + while (found != 0); + return count; } private static String getPresentablePath(final VirtualFile virtualFile) { @@ -605,12 +615,11 @@ public class FindInProjectUtil { } private static int addToUsages(@NotNull Document document, @NotNull Processor consumer, @NotNull FindModel findModel, - @NotNull final PsiFile psiFile) { - + @NotNull final PsiFile psiFile, int[] offsetRef, int maxUsages) { int count = 0; CharSequence text = document.getCharsSequence(); int textLength = document.getTextLength(); - int offset = 0; + int offset = offsetRef[0]; Project project = psiFile.getProject(); @@ -632,7 +641,11 @@ public class FindInProjectUtil { // 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; } diff --git a/platform/usageView/src/com/intellij/usages/ChunkExtractor.java b/platform/usageView/src/com/intellij/usages/ChunkExtractor.java index 636ad3ff8b54..e7df19e8c17b 100644 --- a/platform/usageView/src/com/intellij/usages/ChunkExtractor.java +++ b/platform/usageView/src/com/intellij/usages/ChunkExtractor.java @@ -29,14 +29,19 @@ import com.intellij.openapi.fileTypes.SyntaxHighlighter; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.psi.tree.IElementType; import com.intellij.usageView.UsageTreeColors; import com.intellij.usageView.UsageTreeColorsScheme; +import com.intellij.util.containers.FactoryMap; +import org.jetbrains.annotations.NotNull; import java.awt.*; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; /** * @author peter @@ -44,29 +49,65 @@ import java.util.List; public class ChunkExtractor { private static final Logger LOG = Logger.getInstance("#com.intellij.usages.ChunkExtractor"); - private final PsiElement myElement; - private final Document myDocument; - private final int myLineNumber; - private final int myColumnNumber; - - private final List myRangeMarkers; private final EditorColorsScheme myColorsScheme; - public ChunkExtractor(final PsiElement element, final List rangeMarkers) { - myElement = element; - myRangeMarkers = new ArrayList(rangeMarkers.size()); - for (RangeMarker rangeMarker : rangeMarkers) { - if (rangeMarker.isValid()) myRangeMarkers.add(rangeMarker); + private final Document myDocument; + private final SyntaxHighlighter myHighlighter; + + private final Lexer myLexer; + + private static abstract class WeakFactory { + + private WeakReference myRef; + + @NotNull + protected abstract T create(); + + @NotNull + public T getValue() { + final T cur = myRef == null ? null : myRef.get(); + if (cur != null) return cur; + final T result = create(); + myRef = new WeakReference(result); + return result; } - Collections.sort(myRangeMarkers, RangeMarker.BY_START_OFFSET); + + } + + private static final ThreadLocal>> ourExtractors = new ThreadLocal>>() { + @Override + protected WeakFactory> initialValue() { + return new WeakFactory>() { + @NotNull + @Override + protected Map create() { + return new FactoryMap() { + @Override + protected ChunkExtractor create(PsiFile key) { + return new ChunkExtractor(key); + } + }; + } + }; + } + }; + + public static TextChunk[] extractChunks(PsiElement element, List rangeMarkers) { + return ourExtractors.get().getValue().get(element.getContainingFile()).extractChunks(rangeMarkers); + } + + + private ChunkExtractor(PsiFile file) { myColorsScheme = UsageTreeColorsScheme.getInstance().getScheme(); - int absoluteStartOffset = getStartOffset(myRangeMarkers); - assert absoluteStartOffset != -1; - - myDocument = PsiDocumentManager.getInstance(myElement.getProject()).getDocument(myElement.getContainingFile()); - myLineNumber = myDocument.getLineNumber(absoluteStartOffset); - myColumnNumber = absoluteStartOffset - myDocument.getLineStartOffset(myLineNumber); + myDocument = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); + LOG.assertTrue(myDocument != null); + final FileType fileType = file.getFileType(); + final SyntaxHighlighter highlighter = + SyntaxHighlighter.PROVIDER.create(fileType, file.getProject(), file.getVirtualFile()); + myHighlighter = highlighter == null ? new PlainSyntaxHighlighter() : highlighter; + myLexer = myHighlighter.getHighlightingLexer(); + myLexer.start(myDocument.getCharsSequence()); } public static int getStartOffset(final List rangeMarkers) { @@ -80,30 +121,40 @@ public class ChunkExtractor { return minStart == Integer.MAX_VALUE ? -1 : minStart; } - public TextChunk[] extractChunks() { - final int lineStartOffset = myDocument.getLineStartOffset(myLineNumber); - final int lineEndOffset = lineStartOffset < myDocument.getTextLength() ? myDocument.getLineEndOffset(myLineNumber):0; - if (lineStartOffset > lineEndOffset) return new TextChunk[0]; - final FileType fileType = myElement.getContainingFile().getFileType(); - SyntaxHighlighter highlighter = - SyntaxHighlighter.PROVIDER.create(fileType, myElement.getProject(), myElement.getContainingFile().getVirtualFile()); - if (highlighter == null) { - highlighter = new PlainSyntaxHighlighter(); + public TextChunk[] extractChunks(List rangeMarkers) { + final ArrayList markers = new ArrayList(rangeMarkers.size()); + for (RangeMarker rangeMarker : rangeMarkers) { + if (rangeMarker.isValid()) markers.add(rangeMarker); } - return createTextChunks(myDocument.getCharsSequence(), highlighter, lineStartOffset, lineEndOffset); + int absoluteStartOffset = getStartOffset(markers); + assert absoluteStartOffset != -1; + + final int lineNumber = myDocument.getLineNumber(absoluteStartOffset); + final int columnNumber = absoluteStartOffset - myDocument.getLineStartOffset(lineNumber); + + Collections.sort(markers, RangeMarker.BY_START_OFFSET); + final int lineStartOffset = myDocument.getLineStartOffset(lineNumber); + final int lineEndOffset = lineStartOffset < myDocument.getTextLength() ? myDocument.getLineEndOffset(lineNumber) : 0; + if (lineStartOffset > lineEndOffset) return TextChunk.EMPTY_ARRAY; + + final CharSequence chars = myDocument.getCharsSequence(); + if (myLexer.getTokenStart() > absoluteStartOffset) { + myLexer.start(chars); + } + final List result = new ArrayList(); + appendPrefix(result, lineNumber, columnNumber); + return createTextChunks(markers, chars, lineStartOffset, lineEndOffset, result); } - private TextChunk[] createTextChunks(final CharSequence chars, - SyntaxHighlighter highlighter, + private TextChunk[] createTextChunks(final List markers, + final CharSequence chars, int start, - int end) { + int end, + final List result) { + final Lexer lexer = myLexer; + final SyntaxHighlighter highlighter = myHighlighter; + LOG.assertTrue(start <= end); - List result = new ArrayList(); - - appendPrefix(result); - - Lexer lexer = highlighter.getHighlightingLexer(); - lexer.start(chars); int i = StringUtil.indexOf(chars, '\n', start, end); if (i != -1) end = i; @@ -127,7 +178,7 @@ public class ChunkExtractor { IElementType tokenType = lexer.getTokenType(); TextAttributesKey[] tokenHighlights = highlighter.getTokenHighlights(tokenType); - processIntersectingRange(chars, hiStart, hiEnd, tokenHighlights, result); + processIntersectingRange(markers, chars, hiStart, hiEnd, tokenHighlights, result); } finally { lexer.advance(); @@ -137,14 +188,15 @@ public class ChunkExtractor { return result.toArray(new TextChunk[result.size()]); } - private void processIntersectingRange(CharSequence chars, + private void processIntersectingRange(List markers, + CharSequence chars, int hiStart, int hiEnd, TextAttributesKey[] tokenHighlights, List result) { TextAttributes originalAttrs = convertAttributes(tokenHighlights); int lastOffset = hiStart; - for(RangeMarker rangeMarker: myRangeMarkers) { + for(RangeMarker rangeMarker: markers) { int usageStart = rangeMarker.getStartOffset(); int usageEnd = rangeMarker.getEndOffset(); if (rangeMarker.isValid() && rangeIntersect(lastOffset, hiEnd, usageStart, usageEnd)) { @@ -191,11 +243,11 @@ public class ChunkExtractor { return attrs; } - private void appendPrefix(List result) { + private void appendPrefix(List result, int lineNumber, int columnNumber) { StringBuilder buffer = new StringBuilder("("); - buffer.append(myLineNumber + 1); + buffer.append(lineNumber + 1); buffer.append(", "); - buffer.append(myColumnNumber + 1); + buffer.append(columnNumber + 1); buffer.append(") "); TextChunk prefixChunk = new TextChunk(myColorsScheme.getAttributes(UsageTreeColors.USAGE_LOCATION), buffer.toString()); result.add(prefixChunk); diff --git a/platform/usageView/src/com/intellij/usages/TextChunk.java b/platform/usageView/src/com/intellij/usages/TextChunk.java index bc2cabcef047..2ab65cc469dd 100644 --- a/platform/usageView/src/com/intellij/usages/TextChunk.java +++ b/platform/usageView/src/com/intellij/usages/TextChunk.java @@ -20,6 +20,9 @@ import com.intellij.openapi.editor.markup.TextAttributes; import org.jetbrains.annotations.NotNull; public class TextChunk { + + public static final TextChunk[] EMPTY_ARRAY = new TextChunk[0]; + private final AttributesFlyweight myAttributes; private final String myText; diff --git a/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java b/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java index 40b2bc9fa2f8..97873b7fde99 100644 --- a/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java +++ b/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java @@ -125,7 +125,7 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us } private void initChunks() { - myTextChunks = new ChunkExtractor(getElement(), myRangeMarkers).extractChunks(); + myTextChunks = ChunkExtractor.extractChunks(getElement(), myRangeMarkers); } @NotNull