d1a83f1cd3e4a199da026371facf7f5693a96017
[idea/community.git] / platform / lang-impl / src / com / intellij / find / EditorSearchComponent.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * @author max
19  */
20 package com.intellij.find;
21
22 import com.intellij.codeInsight.highlighting.HighlightManager;
23 import com.intellij.codeInsight.highlighting.HighlightManagerImpl;
24 import com.intellij.featureStatistics.FeatureUsageTracker;
25 import com.intellij.ide.ui.LafManager;
26 import com.intellij.ide.ui.UISettings;
27 import com.intellij.openapi.actionSystem.*;
28 import com.intellij.openapi.editor.Editor;
29 import com.intellij.openapi.editor.ScrollType;
30 import com.intellij.openapi.editor.SelectionModel;
31 import com.intellij.openapi.editor.colors.EditorColors;
32 import com.intellij.openapi.editor.colors.EditorColorsManager;
33 import com.intellij.openapi.editor.colors.EditorFontType;
34 import com.intellij.openapi.editor.markup.RangeHighlighter;
35 import com.intellij.openapi.editor.markup.TextAttributes;
36 import com.intellij.openapi.project.DumbAware;
37 import com.intellij.openapi.project.Project;
38 import com.intellij.openapi.ui.popup.JBPopup;
39 import com.intellij.openapi.ui.popup.JBPopupFactory;
40 import com.intellij.openapi.ui.popup.PopupChooserBuilder;
41 import com.intellij.openapi.util.Comparing;
42 import com.intellij.openapi.util.IconLoader;
43 import com.intellij.openapi.util.SystemInfo;
44 import com.intellij.openapi.util.text.StringUtil;
45 import com.intellij.openapi.vfs.VirtualFile;
46 import com.intellij.openapi.wm.IdeFocusManager;
47 import com.intellij.psi.PsiDocumentManager;
48 import com.intellij.psi.codeStyle.NameUtil;
49 import com.intellij.psi.impl.cache.impl.id.IdTableBuilding;
50 import com.intellij.ui.DocumentAdapter;
51 import com.intellij.ui.LightColors;
52 import com.intellij.ui.NonFocusableCheckBox;
53 import com.intellij.ui.components.panels.NonOpaquePanel;
54 import com.intellij.util.ArrayUtil;
55 import org.jetbrains.annotations.NonNls;
56 import org.jetbrains.annotations.Nullable;
57
58 import javax.swing.*;
59 import javax.swing.event.DocumentEvent;
60 import java.awt.*;
61 import java.awt.event.*;
62 import java.util.*;
63 import java.util.regex.Pattern;
64
65 public class EditorSearchComponent extends JPanel implements DataProvider {
66   private final JLabel myMatchInfoLabel;
67   private final Project myProject;
68   private final Editor myEditor;
69   private final JTextField mySearchField;
70   private final Color myDefaultBackground;
71
72   private final Color GRADIENT_C1;
73   private final Color GRADIENT_C2;
74   private static final Color BORDER_COLOR = new Color(0x87, 0x87, 0x87);
75   private static final Color COMPLETION_BACKGROUND_COLOR = new Color(235, 244, 254);
76   private static final Color FOCUS_CATCHER_COLOR = new Color(0x9999ff);
77   private final JComponent myToolbarComponent;
78   private com.intellij.openapi.editor.event.DocumentAdapter myDocumentListener;
79   private ArrayList<RangeHighlighter> myHighlighters = new ArrayList<RangeHighlighter>();
80   private boolean myOkToSearch = false;
81   private boolean myHasMatches = false;
82   private final JCheckBox myCbRegexp;
83   private final JCheckBox myCbWholeWords;
84   private final JCheckBox myCbInComments;
85   private final JCheckBox myCbInLiterals;
86
87   @Nullable
88   public Object getData(@NonNls final String dataId) {
89     if (DataConstants.EDITOR_EVEN_IF_INACTIVE.equals(dataId)) {
90       return myEditor;
91     }
92     return null;
93   }
94
95   public EditorSearchComponent(final Editor editor, final Project project) {
96     super(new BorderLayout(0, 0));
97
98     GRADIENT_C1 = getBackground();
99     GRADIENT_C2 = new Color(Math.max(0, GRADIENT_C1.getRed() - 0x18), Math.max(0, GRADIENT_C1.getGreen() - 0x18), Math.max(0, GRADIENT_C1.getBlue() - 0x18));
100     
101     myProject = project;
102     myEditor = editor;
103
104     JPanel leadPanel = new NonOpaquePanel(new FlowLayout(FlowLayout.LEFT, 5, 0));
105     add(leadPanel, BorderLayout.WEST);
106
107     mySearchField = new JTextField() {
108       protected void paintBorder(final Graphics g) {
109         super.paintBorder(g);
110
111         final LafManager lafManager = LafManager.getInstance();
112         if (!(lafManager.isUnderAquaLookAndFeel() || lafManager.isUnderQuaquaLookAndFeel()) && isFocusOwner()) {
113           final Rectangle bounds = getBounds();
114           g.setColor(FOCUS_CATCHER_COLOR);
115           g.drawRect(0, 0, bounds.width - 1, bounds.height - 1);
116         }
117       }
118     };
119
120     mySearchField.addFocusListener(new FocusListener() {
121       public void focusGained(final FocusEvent e) {
122         mySearchField.repaint();
123       }
124
125       public void focusLost(final FocusEvent e) {
126         mySearchField.repaint();
127       }
128     });
129
130     mySearchField.putClientProperty("AuxEditorComponent", Boolean.TRUE);
131     leadPanel.add(mySearchField);
132
133     myDefaultBackground = mySearchField.getBackground();
134     mySearchField.setColumns(25);
135
136     setSmallerFont(mySearchField);
137
138     DefaultActionGroup group = new DefaultActionGroup("search bar", false);
139     group.add(new ShowHistoryAction());
140     group.add(new PrevOccurenceAction());
141     group.add(new NextOccurenceAction());
142     group.add(new FindAllAction());
143
144     final ActionToolbar tb = ActionManager.getInstance().createActionToolbar("SearchBar", group, true);
145     tb.setLayoutPolicy(ActionToolbar.NOWRAP_LAYOUT_POLICY);
146     myToolbarComponent = tb.getComponent();
147     myToolbarComponent.setBorder(null);
148     myToolbarComponent.setOpaque(false);
149     leadPanel.add(myToolbarComponent);
150
151     final JCheckBox cbMatchCase = new NonFocusableCheckBox("Case sensitive");
152     myCbWholeWords = new NonFocusableCheckBox("Match whole words only");
153     myCbRegexp = new NonFocusableCheckBox("Regex");
154     myCbInComments = new NonFocusableCheckBox("In comments");
155     myCbInLiterals = new NonFocusableCheckBox("In literals");
156
157     leadPanel.add(cbMatchCase);
158     leadPanel.add(myCbWholeWords);
159     leadPanel.add(myCbRegexp);
160     leadPanel.add(myCbInComments);
161     leadPanel.add(myCbInLiterals);
162
163     cbMatchCase.setSelected(isCaseSensitive());
164     myCbWholeWords.setSelected(isWholeWords());
165     myCbRegexp.setSelected(isRegexp());
166     myCbInComments.setSelected(isInComments());
167     myCbInLiterals.setSelected(isInLiterals());
168
169     cbMatchCase.setMnemonic('C');
170     myCbWholeWords.setMnemonic('M');
171     myCbRegexp.setMnemonic('R');
172     myCbInComments.setMnemonic('o');
173     myCbInLiterals.setMnemonic('l');
174
175     setSmallerFontAndOpaque(myCbWholeWords);
176     setSmallerFontAndOpaque(cbMatchCase);
177     setSmallerFontAndOpaque(myCbRegexp);
178     setSmallerFontAndOpaque(myCbInComments);
179     setSmallerFontAndOpaque(myCbInLiterals);
180
181     cbMatchCase.addActionListener(new ActionListener() {
182       public void actionPerformed(final ActionEvent e) {
183         final boolean b = cbMatchCase.isSelected();
184         FindManager.getInstance(myProject).getFindInFileModel().setCaseSensitive(b);
185         FindSettings.getInstance().setLocalCaseSensitive(b);
186         updateResults(true);
187       }
188     });
189
190     myCbWholeWords.addActionListener(new ActionListener() {
191       public void actionPerformed(final ActionEvent e) {
192         final boolean b = myCbWholeWords.isSelected();
193         FindManager.getInstance(myProject).getFindInFileModel().setWholeWordsOnly(b);
194         FindSettings.getInstance().setLocalWholeWordsOnly(b);
195         updateResults(true);
196       }
197     });
198
199     myCbRegexp.addActionListener(new ActionListener() {
200       public void actionPerformed(final ActionEvent e) {
201         final boolean b = myCbRegexp.isSelected();
202         FindManager.getInstance(myProject).getFindInFileModel().setRegularExpressions(b);
203         myCbWholeWords.setEnabled(!b);
204         updateResults(true);
205       }
206     });
207
208     myCbInComments.addActionListener(new ActionListener() {
209       public void actionPerformed(final ActionEvent e) {
210         final boolean b = myCbInComments.isSelected();
211         FindManager.getInstance(myProject).getFindInFileModel().setInCommentsOnly(b);
212         updateResults(true);
213       }
214     });
215
216     myCbInLiterals.addActionListener(new ActionListener() {
217       public void actionPerformed(final ActionEvent e) {
218         final boolean b = myCbInLiterals.isSelected();
219         FindManager.getInstance(myProject).getFindInFileModel().setInStringLiteralsOnly(b);
220         updateResults(true);
221       }
222     });
223
224     JPanel tailPanel = new NonOpaquePanel(new BorderLayout(5, 0));
225     JPanel tailContainer = new NonOpaquePanel(new BorderLayout(5, 0));
226     tailContainer.add(tailPanel, BorderLayout.EAST);
227     add(tailContainer, BorderLayout.CENTER);
228
229     myMatchInfoLabel = new JLabel();
230     setSmallerFontAndOpaque(myMatchInfoLabel);
231
232     JLabel closeLabel = new JLabel(" ", IconLoader.getIcon("/actions/cross.png"), JLabel.RIGHT);
233     closeLabel.addMouseListener(new MouseAdapter() {
234       public void mousePressed(final MouseEvent e) {
235         close();
236       }
237     });
238
239     closeLabel.setToolTipText("Close search bar (Escape)");
240
241     tailPanel.add(myMatchInfoLabel, BorderLayout.CENTER);
242     tailPanel.add(closeLabel, BorderLayout.EAST);
243
244     mySearchField.getDocument().addDocumentListener(new DocumentAdapter() {
245       protected void textChanged(final DocumentEvent e) {
246         updateResults(true);
247       }
248     });
249
250     mySearchField.registerKeyboardAction(new ActionListener() {
251       public void actionPerformed(final ActionEvent e) {
252         close();
253       }
254     }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);
255
256     mySearchField.registerKeyboardAction(new ActionListener() {
257       public void actionPerformed(final ActionEvent e) {
258         if (getTextInField().length() == 0) {
259           showHistory(false);
260         }
261       }
262     }, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), JComponent.WHEN_FOCUSED);
263
264     mySearchField.registerKeyboardAction(new ActionListener() {
265       public void actionPerformed(final ActionEvent e) {
266         if ("".equals(mySearchField.getText())) {
267           close();
268         }
269         else {
270           requestFocus(myEditor.getContentComponent());
271           addCurrentTextToRecents();
272         }
273       }
274     }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, SystemInfo.isMac ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK), JComponent.WHEN_FOCUSED);
275
276     final String initialText = myEditor.getSelectionModel().getSelectedText();
277
278     SwingUtilities.invokeLater(new Runnable() {
279       public void run() {
280         setInitialText(initialText);
281       }
282     });
283
284     new VariantsCompletionAction(); // It registers a shortcut set automatically on construction
285
286     myDocumentListener = new com.intellij.openapi.editor.event.DocumentAdapter() {
287       public void documentChanged(final com.intellij.openapi.editor.event.DocumentEvent e) {
288         updateResults(false);
289       }
290     };
291
292     myEditor.getDocument().addDocumentListener(myDocumentListener);
293   }
294
295   public void setInitialText(final String initialText) {
296     final String text = initialText != null ? initialText : "";
297     if (text.indexOf("\n") >= 0) {
298       setRegexp(true);
299       setTextInField(StringUtil.escapeToRegexp(text));
300     }
301     else {
302       setTextInField(text);
303     }
304     mySearchField.selectAll();
305   }
306
307   private void requestFocus(Component c) {
308     IdeFocusManager.getInstance(myProject).requestFocus(c, true);
309   }
310
311   private void searchBackward() {
312     if (hasMatches()) {
313       final SelectionModel model = myEditor.getSelectionModel();
314       if (model.hasSelection()) {
315         if (Comparing.equal(mySearchField.getText(), model.getSelectedText(), isCaseSensitive()) && myEditor.getCaretModel().getOffset() == model.getSelectionEnd()) {
316           myEditor.getCaretModel().moveToOffset(model.getSelectionStart());
317         }
318       }
319
320       FindUtil.searchBack(myProject, myEditor);
321       addCurrentTextToRecents();
322     }
323   }
324
325   private void searchForward() {
326     if (hasMatches()) {
327       final SelectionModel model = myEditor.getSelectionModel();
328       if (model.hasSelection()) {
329         if (Comparing.equal(mySearchField.getText(), model.getSelectedText(), isCaseSensitive()) && myEditor.getCaretModel().getOffset() == model.getSelectionStart()) {
330           myEditor.getCaretModel().moveToOffset(model.getSelectionEnd());
331         }
332       }
333
334       FindUtil.searchAgain(myProject, myEditor);
335       addCurrentTextToRecents();
336     }
337   }
338
339   private void addCurrentTextToRecents() {
340     final String text = mySearchField.getText();
341     if (text.length() > 0) {
342       FindSettings.getInstance().addStringToFind(text);
343     }
344   }
345
346   private static void setSmallerFontAndOpaque(final JComponent component) {
347     setSmallerFont(component);
348     component.setOpaque(false);
349   }
350
351   private static void setSmallerFont(final JComponent component) {
352     if (SystemInfo.isMac) {
353       Font f = component.getFont();
354       component.setFont(f.deriveFont(f.getStyle(), f.getSize() - 2));
355     }
356   }
357
358   public void requestFocus() {
359     requestFocus(mySearchField);
360   }
361
362   private void close() {
363     removeCurrentHighlights();
364     if (myEditor.getSelectionModel().hasSelection()) {
365       myEditor.getCaretModel().moveToOffset(myEditor.getSelectionModel().getSelectionStart());
366       myEditor.getSelectionModel().removeSelection();
367     }
368     IdeFocusManager.getInstance(myProject).requestFocus(myEditor.getContentComponent(), false);
369
370     myEditor.setHeaderComponent(null);
371     addCurrentTextToRecents();
372   }
373
374   public void removeNotify() {
375     super.removeNotify();
376
377     if (myDocumentListener != null) {
378       myEditor.getDocument().removeDocumentListener(myDocumentListener);
379       myDocumentListener = null;
380     }
381   }
382
383   private void updateResults(boolean allowedToChangedEditorSelection) {
384     removeCurrentHighlights();
385     myMatchInfoLabel.setFont(myMatchInfoLabel.getFont().deriveFont(Font.PLAIN));
386     String text = mySearchField.getText();
387     if (text.length() == 0) {
388       setRegularBackground();
389       myMatchInfoLabel.setText("");
390       myOkToSearch = false;
391     }
392     else {
393       myOkToSearch = true;
394       FindManager findManager = FindManager.getInstance(myProject);
395       FindModel model = new FindModel();
396       model.setCaseSensitive(isCaseSensitive());
397       model.setInCommentsOnly(isInComments());
398       model.setInStringLiteralsOnly(isInLiterals());
399
400       if (isRegexp()) {
401         model.setWholeWordsOnly(false);
402         model.setRegularExpressions(true);
403         try {
404           Pattern.compile(text);
405         }
406         catch (Exception e) {
407           myOkToSearch = false;
408           setNotFoundBackground();
409           myMatchInfoLabel.setText("Incorrect regular expression");
410           boldMatchInfo();
411           return;
412         }
413       }
414       else {
415         model.setWholeWordsOnly(isWholeWords());
416         model.setRegularExpressions(false);
417       }
418
419       model.setFromCursor(false);
420       model.setStringToFind(text);
421       model.setSearchHighlighters(true);
422       int offset = 0;
423       VirtualFile virtualFile = FindUtil.getVirtualFile(myEditor);
424       ArrayList<FindResult> results = new ArrayList<FindResult>();
425
426       while (true) {
427         FindResult result = findManager.findString(myEditor.getDocument().getCharsSequence(), offset, model, virtualFile);
428         if (!result.isStringFound()) break;
429         offset = result.getEndOffset();
430         results.add(result);
431
432         if (results.size() > 100) break;
433       }
434
435       if (allowedToChangedEditorSelection) {
436         int currentOffset = myEditor.getCaretModel().getOffset();
437         if (myEditor.getSelectionModel().hasSelection()) {
438           currentOffset = Math.min(currentOffset, myEditor.getSelectionModel().getSelectionStart());
439         }
440
441         if (!findAndSelectFirstUsage(findManager, model, currentOffset, virtualFile)) {
442           findAndSelectFirstUsage(findManager, model, 0, virtualFile);
443         }
444       }
445
446       final int count = results.size();
447       if (count <= 100) {
448         highlightResults(text, results);
449
450         if (count > 0) {
451           setRegularBackground();
452           if (count > 1) {
453             myMatchInfoLabel.setText("" + count + " matches");
454           }
455           else {
456             myMatchInfoLabel.setText("1 match");
457           }
458         }
459         else {
460           setNotFoundBackground();
461           myMatchInfoLabel.setText("No matches");
462         }
463       }
464       else {
465         setRegularBackground();
466         myMatchInfoLabel.setText("More than 100 matches");
467         boldMatchInfo();
468       }
469
470       if (allowedToChangedEditorSelection) {
471         findManager.setFindWasPerformed();
472         findManager.setFindNextModel(model);
473       }
474     }
475   }
476
477   private void boldMatchInfo() {
478     myMatchInfoLabel.setFont(myMatchInfoLabel.getFont().deriveFont(Font.BOLD));
479   }
480
481   private void setRegularBackground() {
482     myHasMatches = true;
483     mySearchField.setBackground(myDefaultBackground);
484   }
485
486   private void setNotFoundBackground() {
487     myHasMatches = false;
488     mySearchField.setBackground(LightColors.RED);
489   }
490
491   public String getTextInField() {
492     return mySearchField.getText();
493   }
494
495   private boolean isWholeWords() {
496     return FindManager.getInstance(myProject).getFindInFileModel().isWholeWordsOnly();
497   }
498
499   private boolean isInComments() {
500     return FindManager.getInstance(myProject).getFindInFileModel().isInCommentsOnly();
501   }
502
503   private boolean isInLiterals() {
504     return FindManager.getInstance(myProject).getFindInFileModel().isInStringLiteralsOnly();
505   }
506
507   private boolean isCaseSensitive() {
508     return FindManager.getInstance(myProject).getFindInFileModel().isCaseSensitive();
509   }
510
511   public boolean isRegexp() {
512     return myCbRegexp.isSelected() || FindManager.getInstance(myProject).getFindInFileModel().isRegularExpressions();
513   }
514
515   public void setRegexp(boolean r) {
516     myCbRegexp.setSelected(r);
517     myCbWholeWords.setEnabled(!r);
518     updateResults(false);
519   }
520
521   private void removeCurrentHighlights() {
522     final HighlightManagerImpl highlightManager = (HighlightManagerImpl)HighlightManager.getInstance(myProject);
523     for (RangeHighlighter highlighter : myHighlighters) {
524       highlightManager.removeSegmentHighlighter(myEditor, highlighter);
525     }
526   }
527
528   private boolean findAndSelectFirstUsage(final FindManager findManager, final FindModel model, final int offset, VirtualFile file) {
529     final FindResult firstResult;
530     firstResult = findManager.findString(myEditor.getDocument().getCharsSequence(), offset, model, file);
531     if (firstResult.isStringFound()) {
532       myEditor.getSelectionModel().setSelection(firstResult.getStartOffset(), firstResult.getEndOffset());
533
534       myEditor.getCaretModel().moveToOffset(firstResult.getEndOffset());
535       myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
536
537       myEditor.getCaretModel().moveToOffset(firstResult.getStartOffset());
538       myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
539       return true;
540     }
541
542     return false;
543   }
544
545   private void highlightResults(final String text, final ArrayList<FindResult> results) {
546     HighlightManager highlightManager = HighlightManager.getInstance(myProject);
547     EditorColorsManager colorManager = EditorColorsManager.getInstance();
548     TextAttributes attributes = colorManager.getGlobalScheme().getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES);
549
550     myHighlighters = new ArrayList<RangeHighlighter>();
551     for (FindResult result : results) {
552       highlightManager.addRangeHighlight(myEditor, result.getStartOffset(), result.getEndOffset(), attributes, false, myHighlighters);
553     }
554
555     final String escapedText = StringUtil.escapeXml(text);
556     for (RangeHighlighter highlighter : myHighlighters) {
557       highlighter.setErrorStripeTooltip(escapedText);
558     }
559   }
560
561   public void setTextInField(final String text) {
562     mySearchField.setText(text);
563     updateResults(true);
564   }
565
566   private class PrevOccurenceAction extends AnAction implements DumbAware {
567     public PrevOccurenceAction() {
568       copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_PREVIOUS_OCCURENCE));
569
570       ArrayList<Shortcut> shortcuts = new ArrayList<Shortcut>();
571       shortcuts.addAll(Arrays.asList(ActionManager.getInstance().getAction(IdeActions.ACTION_FIND_PREVIOUS).getShortcutSet().getShortcuts()));
572       shortcuts.addAll(Arrays.asList(ActionManager.getInstance().getAction(IdeActions.ACTION_EDITOR_MOVE_CARET_UP).getShortcutSet().getShortcuts()));
573       shortcuts.add(new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK), null));
574
575       registerCustomShortcutSet(
576         new CustomShortcutSet(shortcuts.toArray(new Shortcut[shortcuts.size()])),
577         mySearchField);
578     }
579
580     public void actionPerformed(final AnActionEvent e) {
581       searchBackward();
582     }
583
584     public void update(final AnActionEvent e) {
585       e.getPresentation().setEnabled(hasMatches());
586     }
587   }
588
589   public boolean hasMatches() {
590     return myOkToSearch && myHasMatches;
591   }
592
593   private class NextOccurenceAction extends AnAction implements DumbAware {
594     public NextOccurenceAction() {
595       copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_NEXT_OCCURENCE));
596       ArrayList<Shortcut> shortcuts = new ArrayList<Shortcut>();
597       shortcuts.addAll(Arrays.asList(ActionManager.getInstance().getAction(IdeActions.ACTION_FIND_NEXT).getShortcutSet().getShortcuts()));
598       shortcuts.addAll(Arrays.asList(ActionManager.getInstance().getAction(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN).getShortcutSet().getShortcuts()));
599       shortcuts.add(new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), null));
600
601       registerCustomShortcutSet(
602         new CustomShortcutSet(shortcuts.toArray(new Shortcut[shortcuts.size()])),
603         mySearchField);
604     }
605
606     public void actionPerformed(final AnActionEvent e) {
607       searchForward();
608     }
609
610     public void update(final AnActionEvent e) {
611       e.getPresentation().setEnabled(hasMatches());
612     }
613   }
614
615   private class ShowHistoryAction extends AnAction implements DumbAware {
616     private ShowHistoryAction() {
617       getTemplatePresentation().setIcon(IconLoader.getIcon("/actions/search.png"));
618       getTemplatePresentation().setDescription("Search history");
619       getTemplatePresentation().setText("Search History");
620
621       ArrayList<Shortcut> shortcuts = new ArrayList<Shortcut>();
622       shortcuts.addAll(Arrays.asList(ActionManager.getInstance().getAction(IdeActions.ACTION_FIND).getShortcutSet().getShortcuts()));
623       shortcuts.add(new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_DOWN_MASK), null));
624       shortcuts.addAll(Arrays.asList(ActionManager.getInstance().getAction("IncrementalSearch").getShortcutSet().getShortcuts()));
625
626       registerCustomShortcutSet(
627         new CustomShortcutSet(shortcuts.toArray(new Shortcut[shortcuts.size()])),
628         mySearchField);
629     }
630
631     public void actionPerformed(final AnActionEvent e) {
632       showHistory(e.getInputEvent() instanceof MouseEvent);
633     }
634   }
635
636   private class FindAllAction extends AnAction implements DumbAware {
637     private FindAllAction() {
638       getTemplatePresentation().setIcon(IconLoader.getIcon("/actions/export.png"));
639       getTemplatePresentation().setDescription("Export matches to Find tool window");
640       getTemplatePresentation().setText("Find All");
641       registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_FIND_USAGES).getShortcutSet(), mySearchField);
642     }
643
644     public void update(final AnActionEvent e) {
645       super.update(e);
646       e.getPresentation().setEnabled(hasMatches() && PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument()) != null);
647     }
648
649     public void actionPerformed(final AnActionEvent e) {
650       final FindModel model = FindManager.getInstance(myProject).getFindInFileModel();
651       final FindModel realModel = (FindModel)model.clone();
652       realModel.setStringToFind(getTextInField());
653       FindUtil.findAll(myProject, myEditor, realModel);
654     }
655   }
656
657   private void showHistory(final boolean byClickingToolbarButton) {
658     FeatureUsageTracker.getInstance().triggerFeatureUsed("find.recent.search");
659     showCompletionPopup(new JList(ArrayUtil.reverseArray(FindSettings.getInstance().getRecentFindStrings())), "Recent Searches",
660                         byClickingToolbarButton);
661   }
662
663   private class VariantsCompletionAction extends AnAction {
664     private VariantsCompletionAction() {
665       final AnAction action = ActionManager.getInstance().getAction(IdeActions.ACTION_CODE_COMPLETION);
666       if (action != null) {
667         registerCustomShortcutSet(action.getShortcutSet(), mySearchField);
668       }
669     }
670
671     public void actionPerformed(final AnActionEvent e) {
672       final String prefix = getPrefix();
673       if (prefix.length() == 0) return;
674
675       final String[] array = calcWords(prefix);
676       if (array.length == 0) {
677         return;
678       }
679
680       FeatureUsageTracker.getInstance().triggerFeatureUsed("find.completion");
681       final JList list = new JList(array) {
682         protected void paintComponent(final Graphics g) {
683           UISettings.setupAntialiasing(g);
684           super.paintComponent(g);
685         }
686       };
687       list.setBackground(COMPLETION_BACKGROUND_COLOR);
688       list.setFont(myEditor.getColorsScheme().getFont(EditorFontType.PLAIN));
689
690       showCompletionPopup(list, null, e.getInputEvent() instanceof MouseEvent);
691     }
692
693     private String getPrefix() {
694       return mySearchField.getText().substring(0, mySearchField.getCaret().getDot());
695     }
696
697     private String[] calcWords(final String prefix) {
698       final NameUtil.Matcher matcher = NameUtil.buildMatcher(prefix, 0, true, true);
699       final Set<String> words = new HashSet<String>();
700       CharSequence chars = myEditor.getDocument().getCharsSequence();
701
702       IdTableBuilding.scanWords(new IdTableBuilding.ScanWordProcessor() {
703         public void run(final CharSequence chars, final int start, final int end) {
704           final String word = chars.subSequence(start, end).toString();
705           if (matcher.matches(word)) {
706             words.add(word);
707           }
708         }
709       }, chars, 0, chars.length());
710
711
712       ArrayList<String> sortedWords = new ArrayList<String>(words);
713       Collections.sort(sortedWords);
714
715       return ArrayUtil.toStringArray(sortedWords);
716     }
717   }
718
719   private void showCompletionPopup(final JList list, String title, final boolean byClickingToolbarButton) {
720
721     final Runnable callback = new Runnable() {
722       public void run() {
723         String selectedValue = (String)list.getSelectedValue();
724         if (selectedValue != null) {
725           mySearchField.setText(selectedValue);
726         }
727       }
728     };
729
730     final PopupChooserBuilder builder = JBPopupFactory.getInstance().createListPopupBuilder(list);
731     if (title != null) {
732       builder.setTitle(title);
733     }
734
735     final JBPopup popup = builder.setMovable(false).setResizable(false)
736       .setRequestFocus(true).setItemChoosenCallback(callback).createPopup();
737
738     if (byClickingToolbarButton) {
739       popup.showUnderneathOf(myToolbarComponent);
740     }
741     else {
742       popup.showUnderneathOf(mySearchField);
743     }
744   }
745
746   @Override
747   protected void paintComponent(Graphics g) {
748     super.paintComponent(g);
749     final Graphics2D g2d = (Graphics2D) g;
750
751     g2d.setPaint(new GradientPaint(0, 0, GRADIENT_C1, 0, getHeight(), GRADIENT_C2));
752     g2d.fillRect(1, 1, getWidth(), getHeight() - 1);
753     
754     g.setColor(BORDER_COLOR);
755     g2d.setPaint(null);
756     g.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1);
757   }
758 }