blob: adc24b1a1c958839ffcb2a9c272bae4c049120af [file] [log] [blame]
/*
* Copyright 2000-2009 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.psi.impl.search;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageParserDefinitions;
import com.intellij.lang.ParserDefinition;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
import com.intellij.openapi.fileTypes.impl.CustomSyntaxTableFileType;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.cache.CacheUtil;
import com.intellij.psi.impl.cache.TodoCacheManager;
import com.intellij.psi.search.IndexPattern;
import com.intellij.psi.search.IndexPatternOccurrence;
import com.intellij.psi.search.IndexPatternProvider;
import com.intellij.psi.search.searches.IndexPatternSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.Processor;
import com.intellij.util.QueryExecutor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.CharSequenceSubSequence;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author yole
*/
public class IndexPatternSearcher implements QueryExecutor<IndexPatternOccurrence, IndexPatternSearch.SearchParameters> {
@Override
public boolean execute(@NotNull final IndexPatternSearch.SearchParameters queryParameters,
@NotNull final Processor<IndexPatternOccurrence> consumer) {
final PsiFile file = queryParameters.getFile();
VirtualFile virtualFile = file.getVirtualFile();
if (file instanceof PsiBinaryFile || file instanceof PsiCompiledElement || virtualFile == null) {
return true;
}
final TodoCacheManager cacheManager = TodoCacheManager.SERVICE.getInstance(file.getProject());
final IndexPatternProvider patternProvider = queryParameters.getPatternProvider();
int count = patternProvider != null
? cacheManager.getTodoCount(virtualFile, patternProvider)
: cacheManager.getTodoCount(virtualFile, queryParameters.getPattern());
return count == 0 || executeImpl(queryParameters, consumer);
}
protected static boolean executeImpl(IndexPatternSearch.SearchParameters queryParameters,
Processor<IndexPatternOccurrence> consumer) {
final IndexPatternProvider patternProvider = queryParameters.getPatternProvider();
final PsiFile file = queryParameters.getFile();
TIntArrayList commentStarts = new TIntArrayList();
TIntArrayList commentEnds = new TIntArrayList();
final CharSequence chars = file.getViewProvider().getContents();
findCommentTokenRanges(file, chars, queryParameters.getRange(), commentStarts, commentEnds);
TIntArrayList occurrences = new TIntArrayList(1);
IndexPattern[] patterns = patternProvider != null ? patternProvider.getIndexPatterns() : null;
for (int i = 0; i < commentStarts.size(); i++) {
int commentStart = commentStarts.get(i);
int commentEnd = commentEnds.get(i);
occurrences.resetQuick();
if (patternProvider != null) {
for (int j = patterns.length - 1; j >=0; --j) {
if (!collectPatternMatches(patterns[j], chars, commentStart, commentEnd, file, queryParameters.getRange(), consumer, occurrences)) {
return false;
}
}
}
else {
if (!collectPatternMatches(queryParameters.getPattern(), chars, commentStart, commentEnd, file, queryParameters.getRange(),
consumer, occurrences)) {
return false;
}
}
}
return true;
}
private static final TokenSet COMMENT_TOKENS =
TokenSet.create(CustomHighlighterTokenType.LINE_COMMENT, CustomHighlighterTokenType.MULTI_LINE_COMMENT);
private static void findCommentTokenRanges(final PsiFile file,
final CharSequence chars,
final TextRange range,
final TIntArrayList commentStarts,
final TIntArrayList commentEnds) {
if (file instanceof PsiPlainTextFile) {
FileType fType = file.getFileType();
if (fType instanceof CustomSyntaxTableFileType) {
Lexer lexer = SyntaxHighlighterFactory.getSyntaxHighlighter(fType, file.getProject(), file.getVirtualFile()).getHighlightingLexer();
findComments(lexer, chars, range, COMMENT_TOKENS, commentStarts, commentEnds, null);
}
else {
commentStarts.add(0);
commentEnds.add(file.getTextLength());
}
}
else {
final FileViewProvider viewProvider = file.getViewProvider();
final Set<Language> relevantLanguages = viewProvider.getLanguages();
for (Language lang : relevantLanguages) {
final TIntArrayList commentStartsList = new TIntArrayList();
final TIntArrayList commentEndsList = new TIntArrayList();
final SyntaxHighlighter syntaxHighlighter =
SyntaxHighlighterFactory.getSyntaxHighlighter(lang, file.getProject(), file.getVirtualFile());
Lexer lexer = syntaxHighlighter.getHighlightingLexer();
TokenSet commentTokens = null;
IndexPatternBuilder builderForFile = null;
for (IndexPatternBuilder builder : Extensions.getExtensions(IndexPatternBuilder.EP_NAME)) {
Lexer lexerFromBuilder = builder.getIndexingLexer(file);
if (lexerFromBuilder != null) {
lexer = lexerFromBuilder;
commentTokens = builder.getCommentTokenSet(file);
builderForFile = builder;
}
}
if (builderForFile == null) {
final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang);
if (parserDefinition != null) {
commentTokens = parserDefinition.getCommentTokens();
}
}
if (commentTokens != null) {
findComments(lexer, chars, range, commentTokens, commentStartsList, commentEndsList, builderForFile);
mergeCommentLists(commentStarts, commentEnds, commentStartsList, commentEndsList);
}
}
}
}
private static void mergeCommentLists(TIntArrayList commentStarts,
TIntArrayList commentEnds,
TIntArrayList commentStartsList,
TIntArrayList commentEndsList) {
if (commentStarts.isEmpty() && commentEnds.isEmpty()) {
commentStarts.add(commentStartsList.toNativeArray());
commentEnds.add(commentEndsList.toNativeArray());
return;
}
ContainerUtil.mergeSortedArrays(commentStarts, commentEnds, commentStartsList, commentEndsList);
}
private static void findComments(final Lexer lexer,
final CharSequence chars,
final TextRange range,
final TokenSet commentTokens,
final TIntArrayList commentStarts,
final TIntArrayList commentEnds,
final IndexPatternBuilder builderForFile) {
for (lexer.start(chars); ; lexer.advance()) {
IElementType tokenType = lexer.getTokenType();
if (tokenType == null) break;
if (range != null) {
if (lexer.getTokenEnd() <= range.getStartOffset()) continue;
if (lexer.getTokenStart() >= range.getEndOffset()) break;
}
boolean isComment = commentTokens.contains(tokenType) || CacheUtil.isInComments(tokenType);
if (isComment) {
final int startDelta = builderForFile != null ? builderForFile.getCommentStartDelta(lexer.getTokenType()) : 0;
final int endDelta = builderForFile != null ? builderForFile.getCommentEndDelta(lexer.getTokenType()) : 0;
int start = lexer.getTokenStart() + startDelta;
int end = lexer.getTokenEnd() - endDelta;
assert start <= end : "Invalid comment range: " +
new TextRange(start, end) +
"; lexer token range=" +
new TextRange(lexer.getTokenStart(), lexer.getTokenEnd()) +
"; delta=" +
new TextRange(startDelta, endDelta) +
"; lexer=" +
lexer +
"; builder=" +
builderForFile +
"; chars length:" +
chars.length();
assert end <= chars.length() : "Invalid comment end: " +
new TextRange(start, end) +
"; lexer token range=" +
new TextRange(lexer.getTokenStart(), lexer.getTokenEnd()) +
"; delta=" +
new TextRange(startDelta, endDelta) +
"; lexer=" +
lexer +
"; builder=" +
builderForFile +
"; chars length:" +
chars.length();
commentStarts.add(start);
commentEnds.add(end);
}
}
}
private static boolean collectPatternMatches(IndexPattern indexPattern,
CharSequence chars,
int commentStart,
int commentEnd,
PsiFile file,
TextRange range,
Processor<IndexPatternOccurrence> consumer,
TIntArrayList matches
) {
Pattern pattern = indexPattern.getPattern();
if (pattern != null) {
ProgressManager.checkCanceled();
CharSequence input = new CharSequenceSubSequence(chars, commentStart, commentEnd);
Matcher matcher = pattern.matcher(input);
while (true) {
//long time1 = System.currentTimeMillis();
boolean found = matcher.find();
//long time2 = System.currentTimeMillis();
//System.out.println("scanned text of length " + (lexer.getTokenEnd() - lexer.getTokenStart() + " in " + (time2 - time1) + " ms"));
if (!found) break;
int start = matcher.start() + commentStart;
int end = matcher.end() + commentStart;
if (start != end) {
if ((range == null || range.getStartOffset() <= start && end <= range.getEndOffset()) && matches.indexOf(start) == -1) {
matches.add(start);
if (!consumer.process(new IndexPatternOccurrenceImpl(file, start, end, indexPattern))) {
return false;
}
}
}
ProgressManager.checkCanceled();
}
}
return true;
}
}