FindResult (@NotNull CharSequence text, char[] textArray, int offset, @NotNull FindModel model, @NotNull final VirtualFile file) { ThreadLocal<SoftReference<CommentsLiteralsSearchData>> data; synchronized (model) { data = model.getUserData(ourCommentsLiteralsSearchDataKey); assert data != null; } FileType ftype = file.getFileType(); Language lang = LanguageUtil.getLanguageForPsi(myProject, file, ftype); SoftReference<CommentsLiteralsSearchData> currentThreadDataRef = data.get(); CommentsLiteralsSearchData currentThreadData = currentThreadDataRef == null ? null : currentThreadDataRef.get(); if (currentThreadData == null || !Comparing.equal(currentThreadData.lastFile, file) || !currentThreadData.model.equals(model)) { SyntaxHighlighter highlighter = getHighlighter(file, lang); if (highlighter == null) { // no syntax highlighter -> no search return NOT_FOUND_RESULT; } TokenSet tokensOfInterest = TokenSet.EMPTY; Set<Language> relevantLanguages; if (lang != null) { final Language finalLang = lang; relevantLanguages = ReadAction.compute(() -> { Set<Language> result = new HashSet<>(); FileViewProvider viewProvider = PsiManager.getInstance(myProject).findViewProvider(file); if (viewProvider != null) { result.addAll(viewProvider.getLanguages()); } if (result.isEmpty()) { result.add(finalLang); } return result; }); for (Language relevantLanguage : relevantLanguages) { tokensOfInterest = addTokenTypesForLanguage(model, relevantLanguage, tokensOfInterest); } } else { relevantLanguages = new HashSet<>(); if (ftype instanceof AbstractFileType) { if (model.isInCommentsOnly()) { tokensOfInterest = TokenSet.create(CustomHighlighterTokenType.LINE_COMMENT, CustomHighlighterTokenType.MULTI_LINE_COMMENT); } if (model.isInStringLiteralsOnly()) { tokensOfInterest = TokenSet.orSet(tokensOfInterest, TokenSet .create(CustomHighlighterTokenType.STRING, CustomHighlighterTokenType.SINGLE_QUOTED_STRING)); } } } Matcher matcher = model.isRegularExpressions() ? compileRegExp(model, "") : null; StringSearcher searcher = matcher != null ? null : new StringSearcher(model.getStringToFind(), model.isCaseSensitive(), true); LayeredLexer.ourDisableLayersFlag.set(Boolean.TRUE); try { SyntaxHighlighterOverEditorHighlighter highlighterAdapter = ReadAction.compute(() -> new SyntaxHighlighterOverEditorHighlighter(highlighter, file, myProject)); currentThreadData = new CommentsLiteralsSearchData(file, relevantLanguages, highlighterAdapter, tokensOfInterest, searcher, matcher, model.clone()); currentThreadData.highlighter.restart(text); } finally { LayeredLexer.ourDisableLayersFlag.set(null); } data.set(new SoftReference<>(currentThreadData)); } int initialStartOffset = model.isForward() && currentThreadData.startOffset < offset ? currentThreadData.startOffset : 0; currentThreadData.highlighter.resetPosition(initialStartOffset); final Lexer lexer = currentThreadData.highlighter.getHighlightingLexer(); IElementType tokenType; TokenSet tokens = currentThreadData.tokensOfInterest; int lastGoodOffset = 0; boolean scanningForward = model.isForward(); FindResultImpl prevFindResult = NOT_FOUND_RESULT; while ((tokenType = lexer.getTokenType()) != null) { if (lexer.getState() == 0) lastGoodOffset = lexer.getTokenStart(); final TextAttributesKey[] keys = currentThreadData.highlighter.getTokenHighlights(tokenType); if (tokens.contains(tokenType) || model.isInStringLiteralsOnly() && ChunkExtractor.isHighlightedAsString(keys) || model.isInCommentsOnly() && ChunkExtractor.isHighlightedAsComment(keys) ) { int start = lexer.getTokenStart(); int end = lexer.getTokenEnd(); if (model.isInStringLiteralsOnly()) { // skip literal quotes itself from matching char c = text.charAt(start); if (c == '"' || c == '\'') { while (start < end && c == text.charAt(start)) { ++start; if (c == text.charAt(end - 1) && start < end) --end; } } } final int tokenContentStart = start; while (true) { FindResultImpl findResult = null; if (currentThreadData.searcher != null) { int matchStart = currentThreadData.searcher.scan(text, textArray, start, end); if (matchStart != -1 && matchStart >= start) { final int matchEnd = matchStart + model.getStringToFind().length(); if (matchStart >= offset || !scanningForward) findResult = new FindResultImpl(matchStart, matchEnd); else { start = matchEnd; continue; } } } else if (start <= end) { currentThreadData.matcher.reset(StringPattern.newBombedCharSequence(text.subSequence(tokenContentStart, end))); currentThreadData.matcher.region(start - tokenContentStart, end - tokenContentStart); currentThreadData.matcher.useTransparentBounds(true); if (currentThreadData.matcher.find()) { final int matchEnd = tokenContentStart + currentThreadData.matcher.end(); int matchStart = tokenContentStart + currentThreadData.matcher.start(); if (matchStart >= offset || !scanningForward) { findResult = new FindResultImpl(matchStart, matchEnd); } else { int diff = 0; if (start == end || start == matchEnd) { diff = 1; } start = matchEnd + diff; continue; } } } if (findResult != null) { if (scanningForward) { currentThreadData.startOffset = lastGoodOffset; return findResult; } else { if (findResult.getEndOffset() >= offset) return prevFindResult; prevFindResult = findResult; start = findResult.getEndOffset(); continue; } } break; } } else { Language tokenLang = tokenType.getLanguage(); if (tokenLang != lang && tokenLang != Language.ANY && !currentThreadData.relevantLanguages.contains(tokenLang)) { tokens = addTokenTypesForLanguage(model, tokenLang, tokens); currentThreadData.tokensOfInterest = tokens; currentThreadData.relevantLanguages.add(tokenLang); } } lexer.advance(); } return prevFindResult; }