WI-14, IDEABKL-3028 Non-modal find/replace dialogs.
[fedora-idea.git] / lang-impl / src / com / intellij / find / impl / FindManagerImpl.java
blob284ecbbc9f79591d20b5a79d14c374f282a772b2
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;
54 import javax.swing.*;
55 import java.awt.*;
56 import java.util.Set;
57 import java.util.regex.Matcher;
58 import java.util.regex.Pattern;
59 import java.util.regex.PatternSyntaxException;
61 @State(
62 name = "FindManager",
63 storages = {
64 @Storage(
65 id ="other",
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) {
86 myProject = project;
87 myBus = 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);
102 try {
103 myFindUsagesManager.writeExternal(findUsages);
105 catch (WriteExternalException e) {
106 LOG.error(e);
108 return element;
111 public void loadState(final Element state) {
112 final Element findUsages = state.getChild(FIND_USAGES_MANAGER_ELEMENT);
113 if (findUsages != null) {
114 try {
115 myFindUsagesManager.readExternal(findUsages);
117 catch (InvalidDataException e) {
118 LOG.error(e);
123 public int showPromptDialog(final FindModel model, String title) {
124 ReplacePromptDialog replacePromptDialog = new ReplacePromptDialog(model.isMultipleFiles(), title, myProject) {
125 @Nullable
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;
133 return null;
137 replacePromptDialog.show();
139 if (model.isMultipleFiles()){
140 myReplaceInProjectPromptPos = replacePromptDialog.getLocation();
142 else{
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(){
150 public void run() {
151 String stringToFind = model.getStringToFind();
152 if (stringToFind == null || stringToFind.length() == 0){
153 return;
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());
169 okHandler.run();
172 findDialog.setModal(false);
173 findDialog.show();
176 @NotNull
177 public FindModel getFindInFileModel() {
178 return myFindInFileModel;
181 @NotNull
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);
213 return patched;
217 return myFindNextModel;
220 public void setFindNextModel(FindModel findNextModel) {
221 myFindNextModel = findNextModel;
222 myBus.syncPublisher(FIND_MODEL_TOPIC).findNextModelChanged();
225 @NotNull
226 public FindResult findString(@NotNull CharSequence text, int offset, @NotNull FindModel model){
227 return findString(text, offset, model, null);
230 @NotNull
231 @Override
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());
239 while(true){
240 FindResult result = doFindString(text, offset, model, file);
242 if (!model.isWholeWordsOnly()) {
243 return result;
245 if (!result.isStringFound()){
246 return result;
248 if (isWholeWord(text, result.getStartOffset(), result.getEndOffset())){
249 return result;
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);
284 int index;
285 if (model.isForward()){
286 final int res = searcher.scan(text.subSequence(offset, text.length()));
287 index = res < 0 ? -1 : res + offset;
289 else{
290 index = searcher.scan(text.subSequence(0, offset));
292 if (index < 0){
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;
304 int startOffset = 0;
305 final Lexer lexer;
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;
315 this.lexer = lexer;
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());
350 break;
354 if (result.size() == 0) {
355 result.add(finalLang);
357 return result;
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())) {
372 xmlLexer.advance();
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());
410 } else {
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;
421 return findResult;
422 } else {
423 if (start >= offset) return prevFindResult;
424 prevFindResult = findResult;
428 } else {
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);
437 lexer.advance();
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;
469 else{
470 int start = -1;
471 int end = -1;
472 while(matcher.find() && matcher.end() < startOffset){
473 start = matcher.start();
474 end = matcher.end();
476 if (start < 0){
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();
486 Pattern pattern;
487 try {
488 pattern = Pattern.compile(toFind, model.isCaseSensitive() ? Pattern.MULTILINE : Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
490 catch(PatternSyntaxException e){
491 LOG.error(e);
492 return null;
495 return pattern.matcher(text);
498 public String getStringToReplace(@NotNull String foundString, FindModel model) {
499 if (model == null) {
500 return null;
502 String toReplace = model.getStringToReplace();
503 if (!model.isRegularExpressions()) {
504 if (model.isPreserveCase()) {
505 return replaceWithCaseRespect (toReplace, foundString);
507 return toReplace;
510 String toFind = model.getStringToFind();
512 Pattern pattern;
513 try{
514 int flags = Pattern.MULTILINE;
515 if (!model.isCaseSensitive()) {
516 flags |= Pattern.CASE_INSENSITIVE;
518 pattern = Pattern.compile(toFind, flags);
520 catch(PatternSyntaxException e){
521 return toReplace;
524 Matcher matcher = pattern.matcher(foundString);
525 if (matcher.matches()) {
526 try {
527 return matcher.replaceAll(StringUtil.unescapeStringCharacters(toReplace));
529 catch (Exception e) {
530 ApplicationManager.getApplication().invokeLater(new Runnable() {
531 public void run() {
532 Messages.showErrorDialog(myProject, FindBundle.message("find.replace.invalid.replacement.string"),
533 FindBundle.message("find.replace.invalid.replacement.string.title"));
536 return null;
538 } else {
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
541 // separately).
542 return toReplace;
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)));
552 } else {
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;
570 if (isTailUpper) {
571 buffer.append(toReplace.substring(1).toUpperCase());
572 } else if (isTailLower) {
573 buffer.append(toReplace.substring(1).toLowerCase());
574 } else {
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;
635 if (!secondPass) {
636 scrollType = isForward ? ScrollType.CENTER_DOWN : ScrollType.CENTER_UP;
638 else {
639 scrollType = isForward ? ScrollType.CENTER_UP : ScrollType.CENTER_DOWN;
641 editor.getScrollingModel().scrollToCaret(scrollType);
642 editor.putUserData(HIGHLIGHTER_WAS_NOT_FOUND_KEY, null);
643 return true;
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");
649 if (isForward) {
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);
655 else {
656 message = FindBundle.message("find.search.again.from.top.action.message", message);
659 else {
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);
665 else {
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);
673 return true;
674 } else if (!secondPass) {
675 offset = isForward ? 0 : editor.getDocument().getTextLength();
676 return highlightNextHighlighter(highlighters, editor, offset, isForward, true);
679 return false;
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;