2 package com
.intellij
.find
.impl
;
4 import com
.intellij
.codeInsight
.highlighting
.HighlightManager
;
5 import com
.intellij
.codeInsight
.highlighting
.HighlightManagerImpl
;
6 import com
.intellij
.codeInsight
.hint
.HintManager
;
7 import com
.intellij
.codeInsight
.hint
.HintManagerImpl
;
8 import com
.intellij
.codeInsight
.hint
.HintUtil
;
9 import com
.intellij
.find
.*;
10 import com
.intellij
.find
.findUsages
.FindUsagesManager
;
11 import com
.intellij
.lang
.Language
;
12 import com
.intellij
.lang
.LanguageParserDefinitions
;
13 import com
.intellij
.lang
.ParserDefinition
;
14 import com
.intellij
.lexer
.Lexer
;
15 import com
.intellij
.openapi
.actionSystem
.ActionManager
;
16 import com
.intellij
.openapi
.actionSystem
.AnAction
;
17 import com
.intellij
.openapi
.actionSystem
.IdeActions
;
18 import com
.intellij
.openapi
.application
.ApplicationManager
;
19 import com
.intellij
.openapi
.components
.PersistentStateComponent
;
20 import com
.intellij
.openapi
.components
.State
;
21 import com
.intellij
.openapi
.components
.Storage
;
22 import com
.intellij
.openapi
.diagnostic
.Logger
;
23 import com
.intellij
.openapi
.editor
.Document
;
24 import com
.intellij
.openapi
.editor
.Editor
;
25 import com
.intellij
.openapi
.editor
.ScrollType
;
26 import com
.intellij
.openapi
.editor
.markup
.RangeHighlighter
;
27 import com
.intellij
.openapi
.fileEditor
.FileEditor
;
28 import com
.intellij
.openapi
.fileEditor
.TextEditor
;
29 import com
.intellij
.openapi
.fileTypes
.FileType
;
30 import com
.intellij
.openapi
.fileTypes
.LanguageFileType
;
31 import com
.intellij
.openapi
.fileTypes
.SyntaxHighlighter
;
32 import com
.intellij
.openapi
.fileTypes
.SyntaxHighlighterFactory
;
33 import com
.intellij
.openapi
.keymap
.KeymapUtil
;
34 import com
.intellij
.openapi
.project
.Project
;
35 import com
.intellij
.openapi
.project
.ProjectManager
;
36 import com
.intellij
.openapi
.ui
.Messages
;
37 import com
.intellij
.openapi
.util
.*;
38 import com
.intellij
.openapi
.util
.text
.StringUtil
;
39 import com
.intellij
.openapi
.vfs
.VirtualFile
;
40 import com
.intellij
.psi
.*;
41 import com
.intellij
.psi
.tree
.IElementType
;
42 import com
.intellij
.psi
.tree
.TokenSet
;
43 import com
.intellij
.ui
.LightweightHint
;
44 import com
.intellij
.ui
.ReplacePromptDialog
;
45 import com
.intellij
.usages
.UsageViewManager
;
46 import com
.intellij
.util
.messages
.MessageBus
;
47 import com
.intellij
.util
.text
.StringSearcher
;
48 import gnu
.trove
.THashSet
;
49 import org
.jdom
.Element
;
50 import org
.jetbrains
.annotations
.NonNls
;
51 import org
.jetbrains
.annotations
.NotNull
;
52 import org
.jetbrains
.annotations
.Nullable
;
57 import java
.util
.regex
.Matcher
;
58 import java
.util
.regex
.Pattern
;
59 import java
.util
.regex
.PatternSyntaxException
;
66 file
= "$WORKSPACE_FILE$"
69 public class FindManagerImpl
extends FindManager
implements PersistentStateComponent
<Element
> {
70 private static final Logger LOG
= Logger
.getInstance("#com.intellij.find.impl.FindManagerImpl");
72 private final FindUsagesManager myFindUsagesManager
;
73 private boolean isFindWasPerformed
= false;
74 private Point myReplaceInFilePromptPos
= new Point(-1, -1);
75 private Point myReplaceInProjectPromptPos
= new Point(-1, -1);
76 private final FindModel myFindInProjectModel
= new FindModel();
77 private final FindModel myFindInFileModel
= new FindModel();
78 private FindModel myFindNextModel
= null;
79 private static final FindResultImpl NOT_FOUND_RESULT
= new FindResultImpl();
80 private final Project myProject
;
81 private final MessageBus myBus
;
82 private static final Key
<Boolean
> HIGHLIGHTER_WAS_NOT_FOUND_KEY
= Key
.create("com.intellij.find.impl.FindManagerImpl.HighlighterNotFoundKey");
83 @NonNls private static final String FIND_USAGES_MANAGER_ELEMENT
= "FindUsagesManager";
85 public FindManagerImpl(Project project
, FindSettings findSettings
, UsageViewManager anotherManager
, MessageBus bus
) {
88 findSettings
.initModelBySetings(myFindInFileModel
);
89 findSettings
.initModelBySetings(myFindInProjectModel
);
91 myFindInFileModel
.setCaseSensitive(findSettings
.isLocalCaseSensitive());
92 myFindInFileModel
.setWholeWordsOnly(findSettings
.isLocalWholeWordsOnly());
94 myFindUsagesManager
= new FindUsagesManager(myProject
, anotherManager
);
95 myFindInProjectModel
.setMultipleFiles(true);
98 public Element
getState() {
99 Element element
= new Element("FindManager");
100 final Element findUsages
= new Element(FIND_USAGES_MANAGER_ELEMENT
);
101 element
.addContent(findUsages
);
103 myFindUsagesManager
.writeExternal(findUsages
);
105 catch (WriteExternalException e
) {
111 public void loadState(final Element state
) {
112 final Element findUsages
= state
.getChild(FIND_USAGES_MANAGER_ELEMENT
);
113 if (findUsages
!= null) {
115 myFindUsagesManager
.readExternal(findUsages
);
117 catch (InvalidDataException e
) {
123 public int showPromptDialog(final FindModel model
, String title
) {
124 ReplacePromptDialog replacePromptDialog
= new ReplacePromptDialog(model
.isMultipleFiles(), title
, myProject
) {
126 public Point
getInitialLocation() {
127 if (model
.isMultipleFiles() && myReplaceInProjectPromptPos
.x
>= 0 && myReplaceInProjectPromptPos
.y
>= 0){
128 return myReplaceInProjectPromptPos
;
130 if (!model
.isMultipleFiles() && myReplaceInFilePromptPos
.x
>= 0 && myReplaceInFilePromptPos
.y
>= 0){
131 return myReplaceInFilePromptPos
;
137 replacePromptDialog
.show();
139 if (model
.isMultipleFiles()){
140 myReplaceInProjectPromptPos
= replacePromptDialog
.getLocation();
143 myReplaceInFilePromptPos
= replacePromptDialog
.getLocation();
145 return replacePromptDialog
.getExitCode();
148 public void showFindDialog(@NotNull final FindModel model
, @NotNull final Runnable okHandler
) {
149 FindDialog findDialog
= new FindDialog(myProject
, model
, new Runnable(){
151 String stringToFind
= model
.getStringToFind();
152 if (stringToFind
== null || stringToFind
.length() == 0){
155 FindSettings
.getInstance().addStringToFind(stringToFind
);
156 if (!model
.isMultipleFiles()){
157 setFindWasPerformed();
159 if (model
.isReplaceState()){
160 FindSettings
.getInstance().addStringToReplace(model
.getStringToReplace());
162 if (model
.isMultipleFiles() && !model
.isProjectScope()){
163 FindSettings
.getInstance().addDirectory(model
.getDirectoryName());
165 if (model
.getDirectoryName()!=null) {
166 myFindInProjectModel
.setWithSubdirectories(model
.isWithSubdirectories());
172 findDialog
.setModal(false);
177 public FindModel
getFindInFileModel() {
178 return myFindInFileModel
;
182 public FindModel
getFindInProjectModel() {
183 myFindInProjectModel
.setFromCursor(false);
184 myFindInProjectModel
.setForward(true);
185 myFindInProjectModel
.setGlobal(true);
186 return myFindInProjectModel
;
189 public boolean findWasPerformed() {
190 return isFindWasPerformed
;
193 public void setFindWasPerformed() {
194 isFindWasPerformed
= true;
195 myFindUsagesManager
.clearFindingNextUsageInFile();
198 public FindModel
getFindNextModel() {
199 return myFindNextModel
;
202 public FindModel
getFindNextModel(@NotNull final Editor editor
) {
203 if (myFindNextModel
== null) return null;
205 final JComponent header
= editor
.getHeaderComponent();
206 if (header
instanceof EditorSearchComponent
) {
207 final EditorSearchComponent searchComponent
= (EditorSearchComponent
)header
;
208 final String textInField
= searchComponent
.getTextInField();
209 if (!Comparing
.equal(textInField
, myFindInFileModel
.getStringToFind())) {
210 FindModel patched
= new FindModel();
211 patched
.copyFrom(myFindNextModel
);
212 patched
.setStringToFind(textInField
);
217 return myFindNextModel
;
220 public void setFindNextModel(FindModel findNextModel
) {
221 myFindNextModel
= findNextModel
;
222 myBus
.syncPublisher(FIND_MODEL_TOPIC
).findNextModelChanged();
226 public FindResult
findString(@NotNull CharSequence text
, int offset
, @NotNull FindModel model
){
227 return findString(text
, offset
, model
, null);
232 public FindResult
findString(@NotNull CharSequence text
, int offset
, @NotNull FindModel model
, @Nullable VirtualFile file
) {
233 if (LOG
.isDebugEnabled()) {
234 LOG
.debug("offset="+offset
);
235 LOG
.debug("textlength="+text
.length());
236 LOG
.debug(model
.toString());
240 FindResult result
= doFindString(text
, offset
, model
, file
);
242 if (!model
.isWholeWordsOnly()) {
245 if (!result
.isStringFound()){
248 if (isWholeWord(text
, result
.getStartOffset(), result
.getEndOffset())){
252 offset
= model
.isForward() ? result
.getStartOffset() + 1 : result
.getEndOffset() - 1;
256 private static boolean isWholeWord(CharSequence text
, int startOffset
, int endOffset
) {
257 boolean isWordStart
= startOffset
== 0 ||
258 !Character
.isJavaIdentifierPart(text
.charAt(startOffset
- 1)) ||
259 startOffset
> 1 && text
.charAt(startOffset
- 2) == '\\';
261 boolean isWordEnd
= endOffset
== text
.length() ||
262 !Character
.isJavaIdentifierPart(text
.charAt(endOffset
)) ||
263 endOffset
> 0 && !Character
.isJavaIdentifierPart(text
.charAt(endOffset
- 1));
265 return isWordStart
&& isWordEnd
;
268 private static FindResult
doFindString(CharSequence text
, int offset
, final FindModel model
, @Nullable VirtualFile file
) {
269 String toFind
= model
.getStringToFind();
270 if (toFind
== null || toFind
.length() == 0){
271 return NOT_FOUND_RESULT
;
274 if (model
.isInCommentsOnly() || model
.isInStringLiteralsOnly()) {
275 return findInCommentsAndLiterals(text
, offset
, model
, file
);
278 if (model
.isRegularExpressions()){
279 return findStringByRegularExpression(text
, offset
, model
);
282 final StringSearcher searcher
= createStringSearcher(model
);
285 if (model
.isForward()){
286 final int res
= searcher
.scan(text
.subSequence(offset
, text
.length()));
287 index
= res
< 0 ?
-1 : res
+ offset
;
290 index
= searcher
.scan(text
.subSequence(0, offset
));
293 return NOT_FOUND_RESULT
;
295 return new FindResultImpl(index
, index
+ toFind
.length());
298 private static StringSearcher
createStringSearcher(FindModel model
) {
299 return new StringSearcher(model
.getStringToFind(), model
.isCaseSensitive(), model
.isForward());
302 static class CommentsLiteralsSearchData
{
303 final VirtualFile lastFile
;
307 TokenSet tokensOfInterest
;
308 final StringSearcher searcher
;
309 final Matcher matcher
;
310 final Set
<Language
> relevantLanguages
;
312 public CommentsLiteralsSearchData(VirtualFile lastFile
, Set
<Language
> relevantLanguages
, Lexer lexer
, TokenSet tokensOfInterest
,
313 StringSearcher searcher
, Matcher matcher
) {
314 this.lastFile
= lastFile
;
316 this.tokensOfInterest
= tokensOfInterest
;
317 this.searcher
= searcher
;
318 this.matcher
= matcher
;
319 this.relevantLanguages
= relevantLanguages
;
323 private static final Key
<CommentsLiteralsSearchData
> ourCommentsLiteralsSearchDataKey
= Key
.create("comments.literals.search.data");
325 private static FindResult
findInCommentsAndLiterals(CharSequence text
, int offset
, FindModel model
, final VirtualFile file
) {
326 if (file
== null) return NOT_FOUND_RESULT
;
328 FileType ftype
= file
.getFileType();
329 Language lang
= null;
330 if (ftype
instanceof LanguageFileType
) {
331 lang
= ((LanguageFileType
)ftype
).getLanguage();
334 if(lang
== null) return NOT_FOUND_RESULT
;
336 CommentsLiteralsSearchData data
= model
.getUserData(ourCommentsLiteralsSearchDataKey
);
337 if (data
== null || data
.lastFile
!= file
) {
338 Lexer lexer
= getLexer(file
, lang
);
340 TokenSet tokensOfInterest
= TokenSet
.EMPTY
;
341 final Language finalLang
= lang
;
342 Set
<Language
> relevantLanguages
= ApplicationManager
.getApplication().runReadAction(new Computable
<Set
<Language
>>() {
343 public Set
<Language
> compute() {
344 THashSet
<Language
> result
= new THashSet
<Language
>();
346 for(Project project
:ProjectManager
.getInstance().getOpenProjects()) {
347 FileViewProvider viewProvider
= PsiManager
.getInstance(project
).findViewProvider(file
);
348 if (viewProvider
!= null) {
349 result
.addAll(viewProvider
.getLanguages());
354 if (result
.size() == 0) {
355 result
.add(finalLang
);
361 for (Language relevantLanguage
:relevantLanguages
) {
362 tokensOfInterest
= addTokenTypesForLanguage(model
, relevantLanguage
, tokensOfInterest
);
365 if(model
.isInStringLiteralsOnly()) {
366 // TODO: xml does not have string literals defined so we add XmlAttributeValue element type as convenience
367 final Lexer xmlLexer
= getLexer(null, Language
.findLanguageByID("XML"));
368 final String marker
= "xxx";
369 xmlLexer
.start("<a href=\"" + marker
+ "\" />");
371 while (!marker
.equals(xmlLexer
.getTokenText())) {
373 if (xmlLexer
.getTokenType() == null) break;
376 IElementType convenienceXmlAttrType
= xmlLexer
.getTokenType();
377 if (convenienceXmlAttrType
!= null) {
378 tokensOfInterest
= TokenSet
.orSet(tokensOfInterest
, TokenSet
.create(convenienceXmlAttrType
));
382 Matcher matcher
= model
.isRegularExpressions() ?
compileRegExp(model
, ""):null;
383 StringSearcher searcher
= matcher
!= null ?
null: createStringSearcher(model
);
384 data
= new CommentsLiteralsSearchData(file
, relevantLanguages
, lexer
, tokensOfInterest
, searcher
, matcher
);
385 model
.putUserData(ourCommentsLiteralsSearchDataKey
, data
);
388 data
.lexer
.start(text
, data
.startOffset
, text
.length(), 0);
390 IElementType tokenType
;
391 final Lexer lexer
= data
.lexer
;
392 TokenSet tokens
= data
.tokensOfInterest
;
394 int lastGoodOffset
= 0;
395 boolean scanningForward
= model
.isForward();
396 FindResultImpl prevFindResult
= NOT_FOUND_RESULT
;
398 while((tokenType
= lexer
.getTokenType()) != null) {
399 if (lexer
.getState() == 0) lastGoodOffset
= lexer
.getTokenStart();
401 if (tokens
.contains(tokenType
)) {
402 int start
= lexer
.getTokenStart();
404 if (start
>= offset
|| !scanningForward
) {
405 FindResultImpl findResult
= null;
407 if (data
.searcher
!= null) {
408 int i
= data
.searcher
.scan(text
, start
, lexer
.getTokenEnd());
409 if (i
!= -1) findResult
= new FindResultImpl(i
, i
+ model
.getStringToFind().length());
411 data
.matcher
.reset(text
.subSequence(start
, lexer
.getTokenEnd()));
412 if (data
.matcher
.find()) {
413 int matchStart
= data
.matcher
.start();
414 findResult
= new FindResultImpl(start
+ matchStart
, start
+ data
.matcher
.end());
418 if (findResult
!= null) {
419 if (scanningForward
) {
420 data
.startOffset
= lastGoodOffset
;
423 if (start
>= offset
) return prevFindResult
;
424 prevFindResult
= findResult
;
429 Language tokenLang
= tokenType
.getLanguage();
430 if (tokenLang
!= lang
&& tokenLang
!= Language
.ANY
&& !data
.relevantLanguages
.contains(tokenLang
)) {
431 tokens
= addTokenTypesForLanguage(model
, tokenLang
, tokens
);
432 data
.tokensOfInterest
= tokens
;
433 data
.relevantLanguages
.add(tokenLang
);
440 return prevFindResult
;
443 private static TokenSet
addTokenTypesForLanguage(FindModel model
, Language lang
, TokenSet tokensOfInterest
) {
444 ParserDefinition definition
= LanguageParserDefinitions
.INSTANCE
.forLanguage(lang
);
445 if (definition
!= null) {
446 tokensOfInterest
= TokenSet
.orSet(tokensOfInterest
, model
.isInCommentsOnly() ? definition
.getCommentTokens(): TokenSet
.EMPTY
);
447 tokensOfInterest
= TokenSet
.orSet(tokensOfInterest
, model
.isInStringLiteralsOnly() ? definition
.getStringLiteralElements() : TokenSet
.EMPTY
);
449 return tokensOfInterest
;
452 private static Lexer
getLexer(VirtualFile file
, Language lang
) {
453 SyntaxHighlighter syntaxHighlighter
= SyntaxHighlighterFactory
.getSyntaxHighlighter(lang
, null, file
);
454 assert syntaxHighlighter
!= null:"Syntax highlighter is null:"+file
;
455 return syntaxHighlighter
.getHighlightingLexer();
458 private static FindResult
findStringByRegularExpression(CharSequence text
, int startOffset
, FindModel model
) {
459 Matcher matcher
= compileRegExp(model
, text
);
461 if (model
.isForward()){
462 if (matcher
.find(startOffset
)) {
463 if (matcher
.end() <= text
.length()) {
464 return new FindResultImpl(matcher
.start(), matcher
.end());
467 return NOT_FOUND_RESULT
;
472 while(matcher
.find() && matcher
.end() < startOffset
){
473 start
= matcher
.start();
477 return NOT_FOUND_RESULT
;
479 return new FindResultImpl(start
, end
);
483 private static Matcher
compileRegExp(FindModel model
, CharSequence text
) {
484 String toFind
= model
.getStringToFind();
488 pattern
= Pattern
.compile(toFind
, model
.isCaseSensitive() ? Pattern
.MULTILINE
: Pattern
.MULTILINE
| Pattern
.CASE_INSENSITIVE
);
490 catch(PatternSyntaxException e
){
495 return pattern
.matcher(text
);
498 public String
getStringToReplace(@NotNull String foundString
, FindModel model
) {
502 String toReplace
= model
.getStringToReplace();
503 if (!model
.isRegularExpressions()) {
504 if (model
.isPreserveCase()) {
505 return replaceWithCaseRespect (toReplace
, foundString
);
510 String toFind
= model
.getStringToFind();
514 int flags
= Pattern
.MULTILINE
;
515 if (!model
.isCaseSensitive()) {
516 flags
|= Pattern
.CASE_INSENSITIVE
;
518 pattern
= Pattern
.compile(toFind
, flags
);
520 catch(PatternSyntaxException e
){
524 Matcher matcher
= pattern
.matcher(foundString
);
525 if (matcher
.matches()) {
527 return matcher
.replaceAll(StringUtil
.unescapeStringCharacters(toReplace
));
529 catch (Exception e
) {
530 ApplicationManager
.getApplication().invokeLater(new Runnable() {
532 Messages
.showErrorDialog(myProject
, FindBundle
.message("find.replace.invalid.replacement.string"),
533 FindBundle
.message("find.replace.invalid.replacement.string.title"));
539 // There are valid situations (for example, IDEADEV-2543 or positive lookbehind assertions)
540 // where an expression which matches a string in context will not match the same string
546 private static String
replaceWithCaseRespect(String toReplace
, String foundString
) {
547 if (foundString
.length() == 0 || toReplace
.length() == 0) return toReplace
;
548 StringBuilder buffer
= new StringBuilder();
550 if (Character
.isUpperCase(foundString
.charAt(0))) {
551 buffer
.append(Character
.toUpperCase(toReplace
.charAt(0)));
553 buffer
.append(Character
.toLowerCase(toReplace
.charAt(0)));
555 if (toReplace
.length() == 1) return buffer
.toString();
557 if (foundString
.length() == 1) {
558 buffer
.append(toReplace
.substring(1));
559 return buffer
.toString();
562 boolean isTailUpper
= true;
563 boolean isTailLower
= true;
564 for (int i
= 1; i
< foundString
.length(); i
++) {
565 isTailUpper
&= Character
.isUpperCase(foundString
.charAt(i
));
566 isTailLower
&= Character
.isLowerCase(foundString
.charAt(i
));
567 if (!isTailUpper
&& !isTailLower
) break;
571 buffer
.append(toReplace
.substring(1).toUpperCase());
572 } else if (isTailLower
) {
573 buffer
.append(toReplace
.substring(1).toLowerCase());
575 buffer
.append(toReplace
.substring(1));
577 return buffer
.toString();
580 public boolean canFindUsages(@NotNull PsiElement element
) {
581 return myFindUsagesManager
.canFindUsages(element
);
584 public void findUsages(@NotNull PsiElement element
) {
585 myFindUsagesManager
.findUsages(element
, null, null);
588 public void findUsagesInEditor(@NotNull PsiElement element
, @NotNull FileEditor fileEditor
) {
589 if (fileEditor
instanceof TextEditor
) {
590 TextEditor textEditor
= (TextEditor
)fileEditor
;
591 Editor editor
= textEditor
.getEditor();
592 Document document
= editor
.getDocument();
593 PsiFile psiFile
= PsiDocumentManager
.getInstance(myProject
).getPsiFile(document
);
595 myFindUsagesManager
.findUsages(element
, psiFile
, fileEditor
);
599 public boolean findNextUsageInEditor(@NotNull FileEditor fileEditor
) {
600 if (fileEditor
instanceof TextEditor
) {
601 TextEditor textEditor
= (TextEditor
)fileEditor
;
602 Editor editor
= textEditor
.getEditor();
604 FindModel model
= getFindNextModel(editor
);
605 if (model
!= null && model
.searchHighlighters()) {
606 RangeHighlighter
[] highlighters
= ((HighlightManagerImpl
)HighlightManager
.getInstance(myProject
)).getHighlighters(editor
);
607 if (highlighters
.length
> 0) {
608 return highlightNextHighlighter(highlighters
, editor
, editor
.getCaretModel().getOffset(), true, false);
613 return myFindUsagesManager
.findNextUsageInFile(fileEditor
);
616 private static boolean highlightNextHighlighter(RangeHighlighter
[] highlighters
, Editor editor
, int offset
, boolean isForward
, boolean secondPass
) {
617 RangeHighlighter highlighterToSelect
= null;
618 Object wasNotFound
= editor
.getUserData(HIGHLIGHTER_WAS_NOT_FOUND_KEY
);
619 for (RangeHighlighter highlighter
: highlighters
) {
620 int start
= highlighter
.getStartOffset();
621 int end
= highlighter
.getEndOffset();
622 if (highlighter
.isValid() && start
< end
) {
623 if (isForward
&& (start
> offset
|| start
== offset
&& secondPass
)) {
624 if (highlighterToSelect
== null || highlighterToSelect
.getStartOffset() > start
) highlighterToSelect
= highlighter
;
626 if (!isForward
&& (end
< offset
|| end
== offset
&& secondPass
)) {
627 if (highlighterToSelect
== null || highlighterToSelect
.getEndOffset() < end
) highlighterToSelect
= highlighter
;
631 if (highlighterToSelect
!= null) {
632 editor
.getSelectionModel().setSelection(highlighterToSelect
.getStartOffset(), highlighterToSelect
.getEndOffset());
633 editor
.getCaretModel().moveToOffset(highlighterToSelect
.getStartOffset());
634 ScrollType scrollType
;
636 scrollType
= isForward ? ScrollType
.CENTER_DOWN
: ScrollType
.CENTER_UP
;
639 scrollType
= isForward ? ScrollType
.CENTER_UP
: ScrollType
.CENTER_DOWN
;
641 editor
.getScrollingModel().scrollToCaret(scrollType
);
642 editor
.putUserData(HIGHLIGHTER_WAS_NOT_FOUND_KEY
, null);
646 if (wasNotFound
== null) {
647 editor
.putUserData(HIGHLIGHTER_WAS_NOT_FOUND_KEY
, Boolean
.TRUE
);
648 String message
= FindBundle
.message("find.highlight.no.more.highlights.found");
650 AnAction action
=ActionManager
.getInstance().getAction(IdeActions
.ACTION_FIND_NEXT
);
651 String shortcutsText
=KeymapUtil
.getFirstKeyboardShortcutText(action
);
652 if (shortcutsText
.length() > 0) {
653 message
= FindBundle
.message("find.search.again.from.top.hotkey.message", message
, shortcutsText
);
656 message
= FindBundle
.message("find.search.again.from.top.action.message", message
);
660 AnAction action
=ActionManager
.getInstance().getAction(IdeActions
.ACTION_FIND_PREVIOUS
);
661 String shortcutsText
=KeymapUtil
.getFirstKeyboardShortcutText(action
);
662 if (shortcutsText
.length() > 0) {
663 message
= FindBundle
.message("find.search.again.from.bottom.hotkey.message", message
, shortcutsText
);
666 message
= FindBundle
.message("find.search.again.from.bottom.action.message", message
);
669 JComponent component
= HintUtil
.createInformationLabel(message
);
670 final LightweightHint hint
= new LightweightHint(component
);
671 HintManagerImpl
.getInstanceImpl().showEditorHint(hint
, editor
, HintManager
.UNDER
, HintManager
.HIDE_BY_ANY_KEY
|
672 HintManager
.HIDE_BY_TEXT_CHANGE
| HintManager
.HIDE_BY_SCROLLING
, 0, false);
674 } else if (!secondPass
) {
675 offset
= isForward ?
0 : editor
.getDocument().getTextLength();
676 return highlightNextHighlighter(highlighters
, editor
, offset
, isForward
, true);
682 public boolean findPreviousUsageInEditor(@NotNull FileEditor fileEditor
) {
683 if (fileEditor
instanceof TextEditor
) {
684 TextEditor textEditor
= (TextEditor
)fileEditor
;
685 Editor editor
= textEditor
.getEditor();
687 FindModel model
= getFindNextModel(editor
);
688 if (model
!= null && model
.searchHighlighters()) {
689 RangeHighlighter
[] highlighters
= ((HighlightManagerImpl
)HighlightManager
.getInstance(myProject
)).getHighlighters(editor
);
690 if (highlighters
.length
> 0) {
691 return highlightNextHighlighter(highlighters
, editor
, editor
.getCaretModel().getOffset(), false, false);
696 return myFindUsagesManager
.findPreviousUsageInFile(fileEditor
);
699 public FindUsagesManager
getFindUsagesManager() {
700 return myFindUsagesManager
;