Merge remote-tracking branch 'origin/master'
[idea/community.git] / platform / lang-impl / src / com / intellij / find / impl / FindDialog.java
1 /*
2  * Copyright 2000-2016 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 package com.intellij.find.impl;
18
19 import com.intellij.CommonBundle;
20 import com.intellij.find.*;
21 import com.intellij.find.actions.ShowUsagesAction;
22 import com.intellij.ide.util.scopeChooser.ScopeChooserCombo;
23 import com.intellij.ide.util.scopeChooser.ScopeDescriptor;
24 import com.intellij.lang.Language;
25 import com.intellij.openapi.Disposable;
26 import com.intellij.openapi.actionSystem.*;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.ModalityState;
29 import com.intellij.openapi.application.TransactionGuard;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.editor.Document;
32 import com.intellij.openapi.editor.event.DocumentAdapter;
33 import com.intellij.openapi.editor.event.DocumentEvent;
34 import com.intellij.openapi.fileChooser.FileChooser;
35 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
36 import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
37 import com.intellij.openapi.fileEditor.UniqueVFilePathBuilder;
38 import com.intellij.openapi.fileTypes.FileType;
39 import com.intellij.openapi.fileTypes.LanguageFileType;
40 import com.intellij.openapi.fileTypes.PlainTextFileType;
41 import com.intellij.openapi.help.HelpManager;
42 import com.intellij.openapi.module.Module;
43 import com.intellij.openapi.module.ModuleManager;
44 import com.intellij.openapi.module.ModuleUtilCore;
45 import com.intellij.openapi.progress.ProcessCanceledException;
46 import com.intellij.openapi.progress.ProgressIndicator;
47 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
48 import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
49 import com.intellij.openapi.progress.util.ReadTask;
50 import com.intellij.openapi.project.DumbAwareAction;
51 import com.intellij.openapi.project.Project;
52 import com.intellij.openapi.ui.*;
53 import com.intellij.openapi.util.Comparing;
54 import com.intellij.openapi.util.Condition;
55 import com.intellij.openapi.util.Disposer;
56 import com.intellij.openapi.util.Ref;
57 import com.intellij.openapi.util.registry.Registry;
58 import com.intellij.openapi.util.text.StringUtil;
59 import com.intellij.openapi.vfs.LocalFileSystem;
60 import com.intellij.openapi.vfs.VirtualFile;
61 import com.intellij.projectImport.ProjectAttachProcessor;
62 import com.intellij.psi.PsiBundle;
63 import com.intellij.psi.PsiDocumentManager;
64 import com.intellij.psi.PsiFile;
65 import com.intellij.psi.PsiFileFactory;
66 import com.intellij.psi.search.SearchScope;
67 import com.intellij.ui.*;
68 import com.intellij.ui.components.JBScrollPane;
69 import com.intellij.ui.table.JBTable;
70 import com.intellij.usageView.UsageInfo;
71 import com.intellij.usages.*;
72 import com.intellij.usages.impl.UsagePreviewPanel;
73 import com.intellij.util.Alarm;
74 import com.intellij.util.ArrayUtil;
75 import com.intellij.util.Consumer;
76 import com.intellij.util.SmartList;
77 import com.intellij.util.containers.Convertor;
78 import com.intellij.util.ui.JBUI;
79 import com.intellij.util.ui.UIUtil;
80 import org.jetbrains.annotations.NotNull;
81 import org.jetbrains.annotations.Nullable;
82 import org.jetbrains.annotations.PropertyKey;
83
84 import javax.swing.*;
85 import javax.swing.event.ListSelectionEvent;
86 import javax.swing.event.ListSelectionListener;
87 import javax.swing.table.DefaultTableModel;
88 import javax.swing.table.TableCellRenderer;
89 import javax.swing.text.JTextComponent;
90 import java.awt.*;
91 import java.awt.event.*;
92 import java.util.*;
93 import java.util.List;
94 import java.util.concurrent.atomic.AtomicInteger;
95 import java.util.regex.Pattern;
96 import java.util.regex.PatternSyntaxException;
97
98 import static com.intellij.ui.SimpleTextAttributes.STYLE_PLAIN;
99
100 public class FindDialog extends DialogWrapper {
101   private static final Logger LOG = Logger.getInstance("#com.intellij.find.impl.FindDialog");
102
103   private ComboBox myInputComboBox;
104   private ComboBox myReplaceComboBox;
105   private StateRestoringCheckBox myCbCaseSensitive;
106   private StateRestoringCheckBox myCbPreserveCase;
107   private StateRestoringCheckBox myCbWholeWordsOnly;
108   private ComboBox mySearchContext;
109   private StateRestoringCheckBox myCbRegularExpressions;
110   private JRadioButton myRbGlobal;
111   private JRadioButton myRbSelectedText;
112   private JRadioButton myRbForward;
113   private JRadioButton myRbBackward;
114   private JRadioButton myRbFromCursor;
115   private JRadioButton myRbEntireScope;
116   private JRadioButton myRbProject;
117   private JRadioButton myRbDirectory;
118   private JRadioButton myRbModule;
119   private ComboBox myModuleComboBox;
120   private ComboBox myDirectoryComboBox;
121   private StateRestoringCheckBox myCbWithSubdirectories;
122   private JCheckBox myCbToOpenInNewTab;
123   private FindModel myModel;
124   private FindModel myPreviousModel;
125   private Consumer<FindModel> myOkHandler;
126   private FixedSizeButton mySelectDirectoryButton;
127   private StateRestoringCheckBox myUseFileFilter;
128   private ComboBox myFileFilter;
129   private JCheckBox myCbToSkipResultsWhenOneUsage;
130   private final Project myProject;
131   private final Map<EditorTextField, DocumentAdapter> myComboBoxListeners = new HashMap<>();
132
133   private Action myFindAllAction;
134   private JRadioButton myRbCustomScope;
135   private ScopeChooserCombo myScopeCombo;
136   protected JLabel myReplacePrompt;
137   private HideableTitledPanel myScopePanel;
138   private static boolean myPreviousResultsExpandedState = true;
139   private static boolean myPreviewResultsTabWasSelected;
140   private static final int RESULTS_PREVIEW_TAB_INDEX = 1;
141
142   private Splitter myPreviewSplitter;
143   private JBTable myResultsPreviewTable;
144   private UsagePreviewPanel myUsagePreviewPanel;
145   private TabbedPane myContent;
146   private Alarm mySearchRescheduleOnCancellationsAlarm;
147   private static final String PREVIEW_TITLE = UIBundle.message("tool.window.name.preview");
148   private volatile ProgressIndicatorBase myResultsPreviewSearchProgress;
149
150   public FindDialog(@NotNull Project project, @NotNull FindModel model, @NotNull Consumer<FindModel> myOkHandler){
151     super(project, true);
152     myProject = project;
153     myModel = model;
154
155     this.myOkHandler = myOkHandler;
156
157     updateTitle();
158     setOKButtonText(FindBundle.message("find.button"));
159     init();
160     initByModel();
161     updateReplaceVisibility();
162     validateFindButton();
163
164     if (haveResultsPreview()) {
165       ApplicationManager.getApplication().invokeLater(() -> scheduleResultsUpdate(), ModalityState.any());
166     }
167   }
168
169   private void updateTitle() {
170     if (myModel.isReplaceState()){
171       if (myModel.isMultipleFiles()){
172         setTitle(FindBundle.message("find.replace.in.project.dialog.title"));
173       }
174       else{
175         setTitle(FindBundle.message("find.replace.text.dialog.title"));
176       }
177     }
178     else{
179       setButtonsMargin(null);
180       if (myModel.isMultipleFiles()){
181         setTitle(FindBundle.message("find.in.path.dialog.title"));
182       }
183       else{
184         setTitle(FindBundle.message("find.text.dialog.title"));
185       }
186     }
187   }
188
189   @Override
190   public void doCancelAction() { // doCancel disposes fields and then calls dispose
191     applyTo(FindManager.getInstance(myProject).getFindInProjectModel(), false);
192     rememberResultsPreviewWasOpen();
193     super.doCancelAction();
194   }
195
196   private void rememberResultsPreviewWasOpen() {
197     if (myResultsPreviewTable != null) {
198       int selectedIndex = myContent.getSelectedIndex();
199       if (selectedIndex != -1) myPreviewResultsTabWasSelected = selectedIndex == RESULTS_PREVIEW_TAB_INDEX;
200     }
201   }
202
203   @Override
204   protected void dispose() {
205     finishPreviousPreviewSearch();
206     if (mySearchRescheduleOnCancellationsAlarm != null) Disposer.dispose(mySearchRescheduleOnCancellationsAlarm);
207     if (myUsagePreviewPanel != null) Disposer.dispose(myUsagePreviewPanel);
208     for(Map.Entry<EditorTextField, DocumentAdapter> e: myComboBoxListeners.entrySet()) {
209       e.getKey().removeDocumentListener(e.getValue());
210     }
211     myComboBoxListeners.clear();
212     if (myScopePanel != null) myPreviousResultsExpandedState = myScopePanel.isExpanded();
213     super.dispose();
214   }
215
216   @Override
217   public JComponent getPreferredFocusedComponent() {
218     return myInputComboBox;
219   }
220
221   @Override
222   protected String getDimensionServiceKey() {
223     return myModel.isReplaceState() ? "replaceTextDialog" : "findTextDialog";
224   }
225
226   @NotNull
227   @Override
228   protected Action[] createActions() {
229     if (!myModel.isMultipleFiles() && !myModel.isReplaceState() && myModel.isFindAllEnabled()) {
230       return new Action[] { getFindAllAction(), getOKAction(), getCancelAction(), getHelpAction() };
231     }
232     return new Action[] { getOKAction(), getCancelAction(), getHelpAction() };
233   }
234
235   @NotNull
236   private Action getFindAllAction() {
237     return myFindAllAction = new AbstractAction(FindBundle.message("find.all.button")) {
238       @Override
239       public void actionPerformed(ActionEvent e) {
240         doOKAction(true);
241       }
242     };
243   }
244
245   @Override
246   public JComponent createNorthPanel() {
247     JPanel panel = new JPanel(new GridBagLayout());
248
249     JLabel prompt = new JLabel(FindBundle.message("find.text.to.find.label"));
250     panel.add(prompt, new GridBagConstraints(0,0,1,1,0,1, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,0,UIUtil.DEFAULT_VGAP,UIUtil.DEFAULT_HGAP), 0,0));
251
252     myInputComboBox = new ComboBox(300);
253     revealWhitespaces(myInputComboBox);
254     initCombobox(myInputComboBox);
255
256     myReplaceComboBox = new ComboBox(300);
257     revealWhitespaces(myReplaceComboBox);
258
259     initCombobox(myReplaceComboBox);
260     final Component editorComponent = myReplaceComboBox.getEditor().getEditorComponent();
261     editorComponent.addFocusListener(
262       new FocusAdapter() {
263         @Override
264         public void focusGained(FocusEvent e) {
265           myReplaceComboBox.getEditor().selectAll();
266           editorComponent.removeFocusListener(this);
267         }
268       }
269     );
270
271
272     panel.add(myInputComboBox, new GridBagConstraints(1,0,1,1,1,1, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0,0,UIUtil.DEFAULT_VGAP,0), 0,0));
273     prompt.setLabelFor(myInputComboBox.getEditor().getEditorComponent());
274
275     myReplacePrompt = new JLabel(FindBundle.message("find.replace.with.label"));
276     panel.add(myReplacePrompt, new GridBagConstraints(0,1,1,1,0,1, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,0,UIUtil.DEFAULT_VGAP,UIUtil.DEFAULT_HGAP), 0,0));
277
278     panel.add(myReplaceComboBox, new GridBagConstraints(1, 1, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
279                                                         new Insets(0, 0, UIUtil.DEFAULT_VGAP, 0), 0, 0));
280     myReplacePrompt.setLabelFor(myReplaceComboBox.getEditor().getEditorComponent());
281     return panel;
282   }
283
284   private void updateReplaceVisibility() {
285     myReplacePrompt.setVisible(myModel.isReplaceState());
286     myReplaceComboBox.setVisible(myModel.isReplaceState());
287     if (myCbToSkipResultsWhenOneUsage != null) {
288       myCbToSkipResultsWhenOneUsage.setVisible(!myModel.isReplaceState());
289     }
290     myCbPreserveCase.setVisible(myModel.isReplaceState());
291   }
292
293   private void revealWhitespaces(@NotNull ComboBox comboBox) {
294     ComboBoxEditor comboBoxEditor = new RevealingSpaceComboboxEditor(myProject, comboBox);
295     comboBox.setEditor(comboBoxEditor);
296     comboBox.setRenderer(new EditorComboBoxRenderer(comboBoxEditor));
297   }
298
299   private void initCombobox(@NotNull final ComboBox comboBox) {
300     comboBox.setEditable(true);
301     comboBox.setMaximumRowCount(8);
302
303     comboBox.addActionListener(new ActionListener() {
304       @Override
305       public void actionPerformed(ActionEvent e) {
306         validateFindButton();
307       }
308     });
309
310     final Component editorComponent = comboBox.getEditor().getEditorComponent();
311
312     if (editorComponent instanceof EditorTextField) {
313       final EditorTextField etf = (EditorTextField) editorComponent;
314
315       DocumentAdapter listener = new DocumentAdapter() {
316         @Override
317         public void documentChanged(final DocumentEvent e) {
318           handleComboBoxValueChanged(comboBox);
319         }
320       };
321       etf.addDocumentListener(listener);
322       myComboBoxListeners.put(etf, listener);
323     }
324     else {
325       if (editorComponent instanceof JTextComponent) {
326         final javax.swing.text.Document document = ((JTextComponent)editorComponent).getDocument();
327         final com.intellij.ui.DocumentAdapter documentAdapter = new com.intellij.ui.DocumentAdapter() {
328           @Override
329           protected void textChanged(javax.swing.event.DocumentEvent e) {
330             handleAnyComboBoxValueChanged(comboBox);
331           }
332         };
333         document.addDocumentListener(documentAdapter);
334         Disposer.register(myDisposable, new Disposable() {
335           @Override
336           public void dispose() {
337             document.removeDocumentListener(documentAdapter);
338           }
339         });
340       } else {
341         assert false;
342       }
343     }
344
345     if (!myModel.isReplaceState()) {
346       makeResultsPreviewActionOverride(
347         comboBox,
348         KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0),
349         "choosePrevious",
350         () -> {
351           int row = myResultsPreviewTable.getSelectedRow();
352           if (row > 0) myResultsPreviewTable.setRowSelectionInterval(row - 1, row - 1);
353           TableUtil.scrollSelectionToVisible(myResultsPreviewTable);
354         }
355       );
356
357       makeResultsPreviewActionOverride(
358         comboBox,
359         KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0),
360         "chooseNext",
361         () -> {
362           int row = myResultsPreviewTable.getSelectedRow();
363           if (row >= -1 && row + 1 < myResultsPreviewTable.getRowCount()) {
364             myResultsPreviewTable.setRowSelectionInterval(row + 1, row + 1);
365             TableUtil.scrollSelectionToVisible(myResultsPreviewTable);
366           }
367         }
368       );
369
370       makeResultsPreviewActionOverride(
371         comboBox,
372         KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0),
373         "scrollUp",
374         () -> {
375           ScrollingUtil.movePageUp(myResultsPreviewTable);
376         }
377       );
378
379       makeResultsPreviewActionOverride(
380         comboBox,
381         KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0),
382         "scrollDown",
383         () -> {
384           ScrollingUtil.movePageDown(myResultsPreviewTable);
385         }
386       );
387
388       AnAction action = new AnAction() {
389         @Override
390         public void actionPerformed(AnActionEvent e) {
391           if (isResultsPreviewTabActive()) {
392             navigateToSelectedUsage(myResultsPreviewTable);
393           }
394         }
395       };
396       action.registerCustomShortcutSet(CommonShortcuts.getEditSource(), comboBox, myDisposable);
397       new AnAction() {
398         @Override
399         public void actionPerformed(AnActionEvent e) {
400           if (!isResultsPreviewTabActive()) doOKAction();
401           else action.actionPerformed(e);
402         }
403       }.registerCustomShortcutSet(CommonShortcuts.ENTER, comboBox, myDisposable);
404     }
405   }
406
407   private boolean isResultsPreviewTabActive() {
408     return myResultsPreviewTable != null &&
409         myContent.getSelectedIndex() == RESULTS_PREVIEW_TAB_INDEX;
410   }
411
412   private void makeResultsPreviewActionOverride(final JComboBox component, KeyStroke keyStroke, String newActionKey, final Runnable newAction) {
413     InputMap inputMap = component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
414     Object action = inputMap.get(keyStroke);
415     inputMap.put(keyStroke, newActionKey);
416     final Action previousAction = action != null ? component.getActionMap().get(action) : null;
417     component.getActionMap().put(newActionKey, new AbstractAction() {
418       @Override
419       public void actionPerformed(ActionEvent e) {
420         if(isResultsPreviewTabActive() && !component.isPopupVisible()) {
421           newAction.run();
422           return;
423         }
424
425         if (previousAction != null) {
426           previousAction.actionPerformed(e);
427         }
428       }
429     });
430   }
431
432   private void handleComboBoxValueChanged(@NotNull ComboBox comboBox) {
433     Object item = comboBox.getEditor().getItem();
434     if (item != null && !item.equals(comboBox.getSelectedItem())){
435       comboBox.setSelectedItem(item);
436     }
437
438     handleAnyComboBoxValueChanged(comboBox);
439   }
440
441   private void handleAnyComboBoxValueChanged(@NotNull ComboBox comboBox) {
442     if (comboBox != myReplaceComboBox) scheduleResultsUpdate();
443     validateFindButton();
444   }
445
446   private void findSettingsChanged() {
447     if (haveResultsPreview()) {
448       final ModalityState state = ModalityState.current();
449       if (state == ModalityState.NON_MODAL) return; // skip initial changes
450
451       finishPreviousPreviewSearch();
452       mySearchRescheduleOnCancellationsAlarm.cancelAllRequests();
453       final FindModel findModel = myModel.clone();
454       applyTo(findModel, false);
455
456       ValidationInfo result = getValidationInfo(findModel);
457
458       final ProgressIndicatorBase progressIndicatorWhenSearchStarted = new ProgressIndicatorBase();
459       myResultsPreviewSearchProgress = progressIndicatorWhenSearchStarted;
460
461       final DefaultTableModel model = new DefaultTableModel() {
462         @Override
463         public boolean isCellEditable(int row, int column) {
464           return false;
465         }
466       };
467
468       // Use previously shown usage files as hint for faster search and better usage preview performance if pattern length increased
469       final LinkedHashSet<VirtualFile> filesToScanInitially = new LinkedHashSet<>();
470
471       if (myPreviousModel != null && myPreviousModel.getStringToFind().length() < findModel.getStringToFind().length()) {
472         final DefaultTableModel previousModel = (DefaultTableModel)myResultsPreviewTable.getModel();
473         for (int i = 0, len = previousModel.getRowCount(); i < len; ++i) {
474           final UsageInfo2UsageAdapter usage = (UsageInfo2UsageAdapter)previousModel.getValueAt(i, 0);
475           final VirtualFile file = usage.getFile();
476           if (file != null) filesToScanInitially.add(file);
477         }
478       }
479
480       myPreviousModel = findModel;
481
482       model.addColumn("Usages");
483
484       myResultsPreviewTable.setModel(model);
485
486       if (result != null) {
487         myResultsPreviewTable.getEmptyText().setText(UIBundle.message("message.nothingToShow"));
488         myContent.setTitleAt(RESULTS_PREVIEW_TAB_INDEX, PREVIEW_TITLE);
489         return;
490       }
491
492       myResultsPreviewTable.getColumnModel().getColumn(0).setCellRenderer(new UsageTableCellRenderer(false, true));
493
494       myResultsPreviewTable.getEmptyText().setText("Searching...");
495       myContent.setTitleAt(RESULTS_PREVIEW_TAB_INDEX, PREVIEW_TITLE);
496
497       final Component component = myInputComboBox.getEditor().getEditorComponent();
498
499       // avoid commit of search text document upon encountering / highlighting of first usage that will restart the search
500       // (UsagePreviewPanel.highlight)
501       if (component instanceof EditorTextField) {
502         final Document document = ((EditorTextField)component).getDocument();
503         if (document != null) {
504           PsiDocumentManager.getInstance(myProject).commitDocument(document);
505         }
506       }
507
508       final AtomicInteger resultsCount = new AtomicInteger();
509       final AtomicInteger resultsFilesCount = new AtomicInteger();
510
511       ProgressIndicatorUtils.scheduleWithWriteActionPriority(myResultsPreviewSearchProgress, new ReadTask() {
512
513         @Nullable
514         @Override
515         public Continuation performInReadAction(@NotNull ProgressIndicator indicator) throws ProcessCanceledException {
516           final UsageViewPresentation presentation =
517             FindInProjectUtil.setupViewPresentation(FindSettings.getInstance().isShowResultsInSeparateView(), findModel);
518           final boolean showPanelIfOnlyOneUsage = !FindSettings.getInstance().isSkipResultsWithOneUsage();
519
520           final FindUsagesProcessPresentation processPresentation =
521             FindInProjectUtil.setupProcessPresentation(myProject, showPanelIfOnlyOneUsage, presentation);
522           Ref<VirtualFile> lastUsageFileRef = new Ref<>();
523
524           FindInProjectUtil.findUsages(findModel, myProject, info -> {
525             final Usage usage = UsageInfo2UsageAdapter.CONVERTER.fun(info);
526             usage.getPresentation().getIcon(); // cache icon
527
528             VirtualFile file = lastUsageFileRef.get();
529             VirtualFile usageFile = info.getVirtualFile();
530             if (file == null || !file.equals(usageFile)) {
531               resultsFilesCount.incrementAndGet();
532               lastUsageFileRef.set(usageFile);
533             }
534
535             ApplicationManager.getApplication().invokeLater(() -> {
536               model.addRow(new Object[]{usage});
537               if (model.getRowCount() == 1 && myResultsPreviewTable.getModel() == model && isResultsPreviewTabActive()) {
538                 myResultsPreviewTable.setRowSelectionInterval(0, 0);
539               }
540             }, state);
541             return resultsCount.incrementAndGet() < ShowUsagesAction.USAGES_PAGE_SIZE;
542           }, processPresentation, filesToScanInitially);
543
544           boolean succeeded = !progressIndicatorWhenSearchStarted.isCanceled();
545           if (succeeded) {
546             return new Continuation(() -> {
547               if (progressIndicatorWhenSearchStarted == myResultsPreviewSearchProgress && !myResultsPreviewSearchProgress.isCanceled()) {
548                 int occurrences = resultsCount.get();
549                 int filesWithOccurrences = resultsFilesCount.get();
550                 if (occurrences == 0) myResultsPreviewTable.getEmptyText().setText(UIBundle.message("message.nothingToShow"));
551                 boolean foundAllUsages = occurrences < ShowUsagesAction.USAGES_PAGE_SIZE;
552
553                 myContent.setTitleAt(RESULTS_PREVIEW_TAB_INDEX,
554                                      PREVIEW_TITLE +
555                                      " (" + (foundAllUsages ? Integer.valueOf(occurrences) : occurrences + "+") +
556                                      UIBundle.message("message.matches", occurrences) +
557                                      " in " + (foundAllUsages ? Integer.valueOf(filesWithOccurrences) : filesWithOccurrences + "+") +
558                                      UIBundle.message("message.files", filesWithOccurrences) +
559                                      ")");
560               }
561             }, state);
562           }
563           return null;
564         }
565
566         @Override
567         public void onCanceled(@NotNull ProgressIndicator indicator) {
568           if (isShowing() && progressIndicatorWhenSearchStarted == myResultsPreviewSearchProgress) {
569             scheduleResultsUpdate();
570           }
571         }
572       });
573     }
574   }
575
576   private void scheduleResultsUpdate() {
577     final Alarm alarm = mySearchRescheduleOnCancellationsAlarm;
578     if (alarm == null || alarm.isDisposed()) return;
579     alarm.cancelAllRequests();
580     alarm.addRequest(() -> TransactionGuard.submitTransaction(myDisposable, this::findSettingsChanged), 100);
581   }
582
583   private void finishPreviousPreviewSearch() {
584     if (myResultsPreviewSearchProgress != null && !myResultsPreviewSearchProgress.isCanceled()) {
585       myResultsPreviewSearchProgress.cancel();
586     }
587   }
588
589   @NotNull
590   public FindModel getModel() {
591     return myModel;
592   }
593
594   public void setOkHandler(@NotNull Consumer<FindModel> okHandler) {
595     myOkHandler = okHandler;
596   }
597
598   public void setModel(@NotNull FindModel model) {
599     String previousStringToFind = getStringToFind();
600     String newStringToFind = model.getStringToFind();
601     myModel = model;
602     updateReplaceVisibility();
603     updateTitle();
604     if (newStringToFind.length() > 0 && !Comparing.equal(newStringToFind, previousStringToFind)) {
605       myInputComboBox.getEditor().setItem(newStringToFind);
606     }
607   }
608
609   private void validateFindButton() {
610     boolean okStatus = canSearchThisString() ||
611                        myRbDirectory != null && myRbDirectory.isSelected() && StringUtil.isEmpty(getDirectory());
612     setOKStatus(okStatus);
613   }
614
615   private boolean canSearchThisString() {
616     return !StringUtil.isEmpty(getStringToFind()) || !myModel.isReplaceState() && !myModel.isFindAllEnabled() && getFileTypeMask() != null;
617   }
618
619   private void setOKStatus(boolean value) {
620     setOKActionEnabled(value);
621     if (myFindAllAction != null) {
622       myFindAllAction.setEnabled(value);
623     }
624   }
625
626   @Override
627   public JComponent createCenterPanel() {
628     JPanel optionsPanel = new JPanel();
629     optionsPanel.setLayout(new GridBagLayout());
630
631     GridBagConstraints gbConstraints = new GridBagConstraints();
632     gbConstraints.weightx = 1;
633     gbConstraints.weighty = 1;
634     gbConstraints.fill = GridBagConstraints.BOTH;
635     gbConstraints.gridwidth = GridBagConstraints.REMAINDER;
636
637     JPanel topOptionsPanel = new JPanel();
638     topOptionsPanel.setLayout(new GridLayout(1, 2, UIUtil.DEFAULT_HGAP, 0));
639     topOptionsPanel.add(createFindOptionsPanel());
640     optionsPanel.add(topOptionsPanel, gbConstraints);
641     
642     JPanel resultsOptionPanel = null;
643     
644     if (myModel.isMultipleFiles()) {
645       optionsPanel.add(createGlobalScopePanel(), gbConstraints);
646       gbConstraints.weightx = 1;
647       gbConstraints.weighty = 1;
648       gbConstraints.fill = GridBagConstraints.HORIZONTAL;
649
650       gbConstraints.gridwidth = GridBagConstraints.REMAINDER;
651       optionsPanel.add(createFilterPanel(),gbConstraints);
652
653       myCbToSkipResultsWhenOneUsage = createCheckbox(FindSettings.getInstance().isSkipResultsWithOneUsage(), FindBundle.message("find.options.skip.results.tab.with.one.occurrence.checkbox"));
654       resultsOptionPanel = createResultsOptionPanel(optionsPanel, gbConstraints);
655       resultsOptionPanel.add(myCbToSkipResultsWhenOneUsage);
656
657       myCbToSkipResultsWhenOneUsage.setVisible(!myModel.isReplaceState());
658
659       if (haveResultsPreview()) {
660         final JBTable table = new JBTable() {
661           @Override
662           public Dimension getPreferredSize() {
663             return new Dimension(myInputComboBox.getWidth(), super.getPreferredSize().height);
664           }
665         };
666         table.setShowColumns(false);
667         table.setShowGrid(false);
668         table.setIntercellSpacing(JBUI.emptySize());
669         new NavigateToSourceListener().installOn(table);
670
671         Splitter previewSplitter = new Splitter(true, 0.5f, 0.1f, 0.9f);
672         myUsagePreviewPanel = new UsagePreviewPanel(myProject, new UsageViewPresentation(), true);
673         myUsagePreviewPanel.setBorder(IdeBorderFactory.createBorder());
674         registerNavigateToSourceShortcutOnComponent(table, myUsagePreviewPanel);
675         myResultsPreviewTable = table;
676         new TableSpeedSearch(table, new Convertor<Object, String>() {
677           @Override
678           public String convert(Object o) {
679             return ((UsageInfo2UsageAdapter)o).getFile().getName();
680           }
681         });
682         myResultsPreviewTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
683           @Override
684           public void valueChanged(ListSelectionEvent e) {
685             if (e.getValueIsAdjusting()) return;
686             int index = myResultsPreviewTable.getSelectionModel().getLeadSelectionIndex();
687             if (index != -1) {
688               UsageInfo usageInfo = ((UsageInfo2UsageAdapter)myResultsPreviewTable.getModel().getValueAt(index, 0)).getUsageInfo();
689               myUsagePreviewPanel.updateLayout(Collections.singletonList(usageInfo));
690               VirtualFile file = usageInfo.getVirtualFile();
691               myUsagePreviewPanel.setBorder(IdeBorderFactory.createTitledBorder(file != null ? file.getPath() : "", false));
692             }
693             else {
694               myUsagePreviewPanel.updateLayout(null);
695               myUsagePreviewPanel.setBorder(IdeBorderFactory.createBorder());
696             }
697           }
698         });
699         mySearchRescheduleOnCancellationsAlarm = new Alarm();
700         previewSplitter.setFirstComponent(new JBScrollPane(myResultsPreviewTable));
701         previewSplitter.setSecondComponent(myUsagePreviewPanel.createComponent());
702         myPreviewSplitter = previewSplitter;
703       }
704     }
705     else {
706       JPanel leftOptionsPanel = new JPanel();
707       leftOptionsPanel.setLayout(new GridLayout(3, 1, 0, 4));
708
709       leftOptionsPanel.add(createDirectionPanel());
710       leftOptionsPanel.add(createOriginPanel());
711       leftOptionsPanel.add(createScopePanel());
712       topOptionsPanel.add(leftOptionsPanel);
713     }
714
715     if (myModel.isOpenInNewTabVisible()){
716       myCbToOpenInNewTab = new JCheckBox(FindBundle.message("find.open.in.new.tab.checkbox"));
717       myCbToOpenInNewTab.setFocusable(false);
718       myCbToOpenInNewTab.setSelected(myModel.isOpenInNewTab());
719       myCbToOpenInNewTab.setEnabled(myModel.isOpenInNewTabEnabled());
720
721       if (resultsOptionPanel == null) resultsOptionPanel = createResultsOptionPanel(optionsPanel, gbConstraints);
722       resultsOptionPanel.add(myCbToOpenInNewTab);
723     }
724
725     if (myPreviewSplitter != null) {
726       TabbedPane pane = new JBTabsPaneImpl(myProject, SwingConstants.TOP, myDisposable);
727       pane.insertTab("Options", null, optionsPanel, null, 0);
728       pane.insertTab(PREVIEW_TITLE, null, myPreviewSplitter, null, RESULTS_PREVIEW_TAB_INDEX);
729       myContent = pane;
730       final AnAction anAction = new DumbAwareAction() {
731         @Override
732         public void actionPerformed(AnActionEvent e) {
733           int selectedIndex = myContent.getSelectedIndex();
734           myContent.setSelectedIndex(1 - selectedIndex);
735         }
736       };
737
738       final ShortcutSet shortcutSet = ActionManager.getInstance().getAction(IdeActions.ACTION_SWITCHER).getShortcutSet();
739
740       anAction.registerCustomShortcutSet(shortcutSet, getRootPane(), myDisposable);
741
742       if (myPreviewResultsTabWasSelected) myContent.setSelectedIndex(RESULTS_PREVIEW_TAB_INDEX);
743
744       return pane.getComponent();
745     }
746
747     return optionsPanel;
748   }
749
750   private boolean haveResultsPreview() {
751     return Registry.is("ide.find.show.preview") && myModel.isMultipleFiles();
752   }
753
754   private JPanel createResultsOptionPanel(JPanel optionsPanel, GridBagConstraints gbConstraints) {
755     JPanel resultsOptionPanel = new JPanel();
756     resultsOptionPanel.setLayout(new BoxLayout(resultsOptionPanel, BoxLayout.Y_AXIS));
757
758     myScopePanel = new HideableTitledPanel(FindBundle.message("results.options.group"), resultsOptionPanel,
759                                            myPreviousResultsExpandedState);
760     optionsPanel.add(myScopePanel, gbConstraints);
761     return resultsOptionPanel;
762   }
763
764   @NotNull
765   private JComponent createFilterPanel() {
766     JPanel filterPanel = new JPanel();
767     filterPanel.setLayout(new BorderLayout());
768     filterPanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.filter.file.name.group"),
769                                                               true));
770
771     myFileFilter = new ComboBox(100);
772     initCombobox(myFileFilter);
773     filterPanel.add(myUseFileFilter = createCheckbox(FindBundle.message("find.filter.file.mask.checkbox")),BorderLayout.WEST);
774     filterPanel.add(myFileFilter,BorderLayout.CENTER);
775     initFileFilter(myFileFilter, myUseFileFilter);
776     myUseFileFilter.addActionListener(new ActionListener() {
777       @Override
778       public void actionPerformed(ActionEvent e) {
779         scheduleResultsUpdate();
780         validateFindButton();
781       }
782     });
783     return filterPanel;
784   }
785
786   public static void initFileFilter(@NotNull final JComboBox fileFilter, @NotNull final JCheckBox useFileFilter) {
787     fileFilter.setEditable(true);
788     String[] fileMasks = FindSettings.getInstance().getRecentFileMasks();
789     for(int i=fileMasks.length-1; i >= 0; i--) {
790       fileFilter.addItem(fileMasks[i]);
791     }
792     fileFilter.setEnabled(false);
793
794     useFileFilter.addActionListener(
795       new ActionListener() {
796         @Override
797         public void actionPerformed(ActionEvent e) {
798           if (useFileFilter.isSelected()) {
799             fileFilter.setEnabled(true);
800             fileFilter.getEditor().selectAll();
801             fileFilter.getEditor().getEditorComponent().requestFocusInWindow();
802           }
803           else {
804             fileFilter.setEnabled(false);
805           }
806         }
807       }
808     );
809   }
810
811   @Override
812   public void doOKAction() {
813     doOKAction(false);
814   }
815
816   private void doOKAction(boolean findAll) {
817     FindModel validateModel = myModel.clone();
818     applyTo(validateModel, findAll);
819
820     ValidationInfo validationInfo = getValidationInfo(validateModel);
821
822     if (validationInfo == null) {
823       myModel.copyFrom(validateModel);
824       updateFindSettings();
825
826       rememberResultsPreviewWasOpen();
827       super.doOKAction();
828       myOkHandler.consume(myModel);
829     }
830     else {
831       String message = validationInfo.message;
832       Messages.showMessageDialog(
833         myProject,
834         message,
835         CommonBundle.getErrorTitle(),
836         Messages.getErrorIcon()
837       );
838     }
839   }
840
841   private void updateFindSettings() {
842     FindSettings findSettings = FindSettings.getInstance();
843     findSettings.setCaseSensitive(myModel.isCaseSensitive());
844     if (myModel.isReplaceState()) {
845       findSettings.setPreserveCase(myModel.isPreserveCase());
846     }
847
848     findSettings.setWholeWordsOnly(myModel.isWholeWordsOnly());
849     boolean saveContextBetweenRestarts = false;
850     findSettings.setInStringLiteralsOnly(saveContextBetweenRestarts && myModel.isInStringLiteralsOnly());
851     findSettings.setInCommentsOnly(saveContextBetweenRestarts && myModel.isInCommentsOnly());
852     findSettings.setExceptComments(saveContextBetweenRestarts && myModel.isExceptComments());
853     findSettings.setExceptStringLiterals(saveContextBetweenRestarts && myModel.isExceptStringLiterals());
854     findSettings.setExceptCommentsAndLiterals(saveContextBetweenRestarts && myModel.isExceptCommentsAndStringLiterals());
855
856     findSettings.setRegularExpressions(myModel.isRegularExpressions());
857     if (!myModel.isMultipleFiles()){
858       findSettings.setForward(myModel.isForward());
859       findSettings.setFromCursor(myModel.isFromCursor());
860
861       findSettings.setGlobal(myModel.isGlobal());
862     } else{
863       String directoryName = myModel.getDirectoryName();
864       if (directoryName != null && !directoryName.isEmpty()) {
865         findSettings.setWithSubdirectories(myModel.isWithSubdirectories());
866       }
867       else if (myRbModule.isSelected()) {
868       }
869       else if (myRbCustomScope.isSelected()) {
870         SearchScope selectedScope = myScopeCombo.getSelectedScope();
871         String customScopeName = selectedScope == null ? null : selectedScope.getDisplayName();
872         findSettings.setCustomScope(customScopeName);
873       }
874     }
875
876     if (myCbToSkipResultsWhenOneUsage != null){
877       findSettings.setSkipResultsWithOneUsage(
878         isSkipResultsWhenOneUsage()
879       );
880     }
881
882     findSettings.setFileMask(myModel.getFileFilter());
883   }
884
885   @Nullable("null means OK")
886   private ValidationInfo getValidationInfo(@NotNull FindModel model) {
887     if (myRbDirectory != null && myRbDirectory.isEnabled() && myRbDirectory.isSelected()) {
888       VirtualFile directory = FindInProjectUtil.getDirectory(model);
889       if (directory == null) {
890         return new ValidationInfo(FindBundle.message("find.directory.not.found.error", getDirectory()), myDirectoryComboBox);
891       }
892     }
893
894     if (!canSearchThisString()) {
895       return new ValidationInfo("String to find is empty", myInputComboBox);
896     }
897
898     if (myCbRegularExpressions != null && myCbRegularExpressions.isSelected() && myCbRegularExpressions.isEnabled()) {
899       String toFind = getStringToFind();
900       try {
901         boolean isCaseSensitive = myCbCaseSensitive != null && myCbCaseSensitive.isSelected() && myCbCaseSensitive.isEnabled();
902         Pattern pattern =
903           Pattern.compile(toFind, isCaseSensitive ? Pattern.MULTILINE : Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
904         if (pattern.matcher("").matches() && !toFind.endsWith("$") && !toFind.startsWith("^")) {
905           return new ValidationInfo(FindBundle.message("find.empty.match.regular.expression.error"), myInputComboBox);
906         }
907       }
908       catch (PatternSyntaxException e) {
909         return new ValidationInfo(FindBundle.message("find.invalid.regular.expression.error", toFind, e.getDescription()), myInputComboBox);
910       }
911     }
912
913     final String mask = getFileTypeMask();
914
915     if (mask != null) {
916       if (mask.isEmpty()) {
917         return new ValidationInfo(FindBundle.message("find.filter.empty.file.mask.error"), myFileFilter);
918       }
919
920       if (mask.contains(";")) {
921         return new ValidationInfo("File masks should be comma-separated", myFileFilter);
922       }
923
924       else {
925         try {
926           FindInProjectUtil.createFileMaskCondition(mask);   // verify that the regexp compiles
927         }
928         catch (PatternSyntaxException ex) {
929           return new ValidationInfo(FindBundle.message("find.filter.invalid.file.mask.error", mask), myFileFilter);
930         }
931       }
932     }
933     return null;
934   }
935
936   @Override
937   protected ValidationInfo doValidate() {
938     FindModel validateModel = myModel.clone();
939     applyTo(validateModel, false);
940
941     ValidationInfo result = getValidationInfo(validateModel);
942
943     setOKStatus(result == null);
944
945     return result;
946   }
947
948   @Override
949   public void doHelpAction() {
950     String id = myModel.isReplaceState()
951                 ? myModel.isMultipleFiles() ? HelpID.REPLACE_IN_PATH : HelpID.REPLACE_OPTIONS
952                 : myModel.isMultipleFiles() ? HelpID.FIND_IN_PATH : HelpID.FIND_OPTIONS;
953     HelpManager.getInstance().invokeHelp(id);
954   }
955
956   private boolean isSkipResultsWhenOneUsage() {
957     return myCbToSkipResultsWhenOneUsage!=null && myCbToSkipResultsWhenOneUsage.isSelected();
958   }
959
960   @NotNull
961   private JPanel createFindOptionsPanel() {
962     JPanel findOptionsPanel = new JPanel();
963     findOptionsPanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.options.group"), true));
964     findOptionsPanel.setLayout(new BoxLayout(findOptionsPanel, BoxLayout.Y_AXIS));
965
966     myCbCaseSensitive = createCheckbox(FindBundle.message("find.options.case.sensitive"));
967     findOptionsPanel.add(myCbCaseSensitive);
968     ItemListener liveResultsPreviewUpdateListener = new ItemListener() {
969       @Override
970       public void itemStateChanged(ItemEvent e) {
971         scheduleResultsUpdate();
972       }
973     };
974     myCbCaseSensitive.addItemListener(liveResultsPreviewUpdateListener);
975
976     myCbPreserveCase = createCheckbox(FindBundle.message("find.options.replace.preserve.case"));
977     myCbPreserveCase.addItemListener(liveResultsPreviewUpdateListener);
978     findOptionsPanel.add(myCbPreserveCase);
979     myCbPreserveCase.setVisible(myModel.isReplaceState());
980     myCbWholeWordsOnly = createCheckbox(FindBundle.message("find.options.whole.words.only"));
981     myCbWholeWordsOnly.addItemListener(liveResultsPreviewUpdateListener);
982
983     findOptionsPanel.add(myCbWholeWordsOnly);
984
985     myCbRegularExpressions = createCheckbox(FindBundle.message("find.options.regular.expressions"));
986     myCbRegularExpressions.addItemListener(liveResultsPreviewUpdateListener);
987
988     final JPanel regExPanel = new JPanel();
989     regExPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
990     regExPanel.setLayout(new BoxLayout(regExPanel, BoxLayout.X_AXIS));
991     regExPanel.add(myCbRegularExpressions);
992
993     regExPanel.add(RegExHelpPopup.createRegExLink("[Help]", regExPanel, LOG));
994
995     findOptionsPanel.add(regExPanel);
996
997     mySearchContext = new ComboBox(new Object[] { getPresentableName(FindModel.SearchContext.ANY),
998       getPresentableName(FindModel.SearchContext.IN_COMMENTS),
999       getPresentableName(FindModel.SearchContext.IN_STRING_LITERALS),
1000       getPresentableName(FindModel.SearchContext.EXCEPT_COMMENTS),
1001       getPresentableName(FindModel.SearchContext.EXCEPT_STRING_LITERALS),
1002       getPresentableName(FindModel.SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS)});
1003     mySearchContext.addActionListener(new ActionListener() {
1004       @Override
1005       public void actionPerformed(ActionEvent e) {
1006         scheduleResultsUpdate();
1007       }
1008     });
1009     final JPanel searchContextPanel = new JPanel(new BorderLayout());
1010     searchContextPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
1011
1012     JLabel searchContextLabel = new JLabel(FindBundle.message("find.context.combo.label"));
1013     searchContextLabel.setLabelFor(mySearchContext);
1014     JPanel panel = new JPanel();
1015     panel.setAlignmentX(Component.LEFT_ALIGNMENT);
1016     panel.add(searchContextLabel);
1017     searchContextPanel.add(panel, BorderLayout.WEST);
1018
1019     panel = new JPanel(new BorderLayout());
1020     panel.add(mySearchContext, BorderLayout.NORTH);
1021     searchContextPanel.add(panel, BorderLayout.CENTER);
1022
1023     findOptionsPanel.add(searchContextPanel);
1024
1025     ActionListener actionListener = new ActionListener() {
1026       @Override
1027       public void actionPerformed(ActionEvent e) {
1028         updateControls();
1029       }
1030     };
1031     myCbRegularExpressions.addActionListener(actionListener);
1032     myCbRegularExpressions.addItemListener(new ItemListener() {
1033       @Override
1034       public void itemStateChanged(final ItemEvent e) {
1035         setupRegExpSetting();
1036       }
1037     });
1038
1039     myCbCaseSensitive.addActionListener(actionListener);
1040     myCbPreserveCase.addActionListener(actionListener);
1041
1042     return findOptionsPanel;
1043   }
1044
1045   public static String getPresentableName(@NotNull FindModel.SearchContext searchContext) {
1046     @PropertyKey(resourceBundle = "messages.FindBundle") String messageKey = null;
1047     if (searchContext == FindModel.SearchContext.ANY) {
1048       messageKey = "find.context.anywhere.scope.label";
1049     } else if (searchContext == FindModel.SearchContext.EXCEPT_COMMENTS) {
1050       messageKey = "find.context.except.comments.scope.label";
1051     } else if (searchContext == FindModel.SearchContext.EXCEPT_STRING_LITERALS) {
1052       messageKey = "find.context.except.literals.scope.label";
1053     } else if (searchContext == FindModel.SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS) {
1054       messageKey = "find.context.except.comments.and.literals.scope.label";
1055     } else if (searchContext == FindModel.SearchContext.IN_COMMENTS) {
1056       messageKey = "find.context.in.comments.scope.label";
1057     } else if (searchContext == FindModel.SearchContext.IN_STRING_LITERALS) {
1058       messageKey = "find.context.in.literals.scope.label";
1059     }
1060     return messageKey != null ? FindBundle.message(messageKey) : searchContext.toString();
1061   }
1062
1063   @NotNull
1064   public static FindModel.SearchContext parseSearchContext(String presentableName) {
1065     FindModel.SearchContext searchContext = FindModel.SearchContext.ANY;
1066     if (FindBundle.message("find.context.in.literals.scope.label").equals(presentableName)) {
1067       searchContext = FindModel.SearchContext.IN_STRING_LITERALS;
1068     }
1069     else if (FindBundle.message("find.context.in.comments.scope.label").equals(presentableName)) {
1070       searchContext = FindModel.SearchContext.IN_COMMENTS;
1071     }
1072     else if (FindBundle.message("find.context.except.comments.scope.label").equals(presentableName)) {
1073       searchContext = FindModel.SearchContext.EXCEPT_COMMENTS;
1074     }
1075     else if (FindBundle.message("find.context.except.literals.scope.label").equals(presentableName)) {
1076       searchContext = FindModel.SearchContext.EXCEPT_STRING_LITERALS;
1077     } else if (FindBundle.message("find.context.except.comments.and.literals.scope.label").equals(presentableName)) {
1078       searchContext = FindModel.SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS;
1079     }
1080     return searchContext;
1081   }
1082
1083   @NotNull
1084   public static String getSearchContextName(FindModel model) {
1085     String searchContext = FindBundle.message("find.context.anywhere.scope.label");
1086     if (model.isInCommentsOnly()) searchContext = FindBundle.message("find.context.in.comments.scope.label");
1087     else if (model.isInStringLiteralsOnly()) searchContext = FindBundle.message("find.context.in.literals.scope.label");
1088     else if (model.isExceptStringLiterals()) searchContext = FindBundle.message("find.context.except.literals.scope.label");
1089     else if (model.isExceptComments()) searchContext = FindBundle.message("find.context.except.comments.scope.label");
1090     else if (model.isExceptCommentsAndStringLiterals()) searchContext = FindBundle.message("find.context.except.comments.and.literals.scope.label");
1091     return searchContext;
1092   }
1093
1094   private void setupRegExpSetting() {
1095     updateFileTypeForEditorComponent(myInputComboBox);
1096     if (myReplaceComboBox != null) updateFileTypeForEditorComponent(myReplaceComboBox);
1097   }
1098
1099   private void updateFileTypeForEditorComponent(@NotNull ComboBox inputComboBox) {
1100     final Component editorComponent = inputComboBox.getEditor().getEditorComponent();
1101
1102     if (editorComponent instanceof EditorTextField) {
1103       boolean isRegexp = myCbRegularExpressions.isSelectedWhenSelectable();
1104       FileType fileType = PlainTextFileType.INSTANCE;
1105       if (isRegexp) {
1106         Language regexpLanguage = Language.findLanguageByID("RegExp");
1107         if (regexpLanguage != null) {
1108           LanguageFileType regexpFileType = regexpLanguage.getAssociatedFileType();
1109           if (regexpFileType != null) {
1110             fileType = regexpFileType;
1111           }
1112         }
1113       }
1114       String fileName = isRegexp ? "a.regexp" : "a.txt";
1115       final PsiFile file = PsiFileFactory.getInstance(myProject).createFileFromText(fileName, fileType, ((EditorTextField)editorComponent).getText(), -1, true);
1116
1117       ((EditorTextField)editorComponent).setNewDocumentAndFileType(fileType, PsiDocumentManager.getInstance(myProject).getDocument(file));
1118     }
1119   }
1120
1121   private void updateControls() {
1122     if (myCbRegularExpressions.isSelected()) {
1123       myCbWholeWordsOnly.makeUnselectable(false);
1124     }
1125     else {
1126       myCbWholeWordsOnly.makeSelectable();
1127     }
1128     if (myModel.isReplaceState()) {
1129       if (myCbRegularExpressions.isSelected() || myCbCaseSensitive.isSelected()) {
1130         myCbPreserveCase.makeUnselectable(false);
1131       }
1132       else {
1133         myCbPreserveCase.makeSelectable();
1134       }
1135
1136       if (myCbPreserveCase.isSelected()) {
1137         myCbRegularExpressions.makeUnselectable(false);
1138         myCbCaseSensitive.makeUnselectable(false);
1139       }
1140       else {
1141         myCbRegularExpressions.makeSelectable();
1142         myCbCaseSensitive.makeSelectable();
1143       }
1144     }
1145
1146     if (!myModel.isMultipleFiles()) {
1147       myRbFromCursor.setEnabled(myRbGlobal.isSelected());
1148       myRbEntireScope.setEnabled(myRbGlobal.isSelected());
1149     }
1150   }
1151
1152   @NotNull
1153   private JPanel createDirectionPanel() {
1154     JPanel directionPanel = new JPanel();
1155     directionPanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.direction.group"), true));
1156     directionPanel.setLayout(new BoxLayout(directionPanel, BoxLayout.Y_AXIS));
1157
1158     myRbForward = new JRadioButton(FindBundle.message("find.direction.forward.radio"), true);
1159     directionPanel.add(myRbForward);
1160     myRbBackward = new JRadioButton(FindBundle.message("find.direction.backward.radio"));
1161     directionPanel.add(myRbBackward);
1162     ButtonGroup bgDirection = new ButtonGroup();
1163     bgDirection.add(myRbForward);
1164     bgDirection.add(myRbBackward);
1165
1166     return directionPanel;
1167   }
1168
1169   @NotNull
1170   private JComponent createGlobalScopePanel() {
1171     JPanel scopePanel = new JPanel();
1172     scopePanel.setLayout(new GridBagLayout());
1173     scopePanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.scope.group"), true));
1174     GridBagConstraints gbConstraints = new GridBagConstraints();
1175     gbConstraints.fill = GridBagConstraints.HORIZONTAL;
1176     gbConstraints.anchor = GridBagConstraints.WEST;
1177
1178     gbConstraints.gridx = 0;
1179     gbConstraints.gridy = 0;
1180     gbConstraints.gridwidth = 3;
1181     gbConstraints.weightx = 1;
1182     final boolean canAttach = ProjectAttachProcessor.canAttachToProject();
1183     myRbProject = new JRadioButton(canAttach
1184                                    ? FindBundle.message("find.scope.all.projects.radio")
1185                                    : FindBundle.message("find.scope.whole.project.radio"), true);
1186     scopePanel.add(myRbProject, gbConstraints);
1187     ItemListener resultsPreviewUpdateListener = new ItemListener() {
1188       @Override
1189       public void itemStateChanged(ItemEvent e) {
1190         scheduleResultsUpdate();
1191       }
1192     };
1193     myRbProject.addItemListener(resultsPreviewUpdateListener);
1194
1195     gbConstraints.gridx = 0;
1196     gbConstraints.gridy++;
1197     gbConstraints.weightx = 0;
1198     gbConstraints.gridwidth = 1;
1199     myRbModule = new JRadioButton(canAttach
1200                                   ? FindBundle.message("find.scope.project.radio")
1201                                   : FindBundle.message("find.scope.module.radio"), false);
1202     scopePanel.add(myRbModule, gbConstraints);
1203     myRbModule.addItemListener(resultsPreviewUpdateListener);
1204
1205     gbConstraints.gridx = 1;
1206     gbConstraints.gridwidth = 2;
1207     gbConstraints.weightx = 1;
1208     Module[] modules = ModuleManager.getInstance(myProject).getModules();
1209     String[] names = new String[modules.length];
1210     for (int i = 0; i < modules.length; i++) {
1211       names[i] = modules[i].getName();
1212     }
1213
1214     Arrays.sort(names,String.CASE_INSENSITIVE_ORDER);
1215     myModuleComboBox = new ComboBox(names);
1216     myModuleComboBox.addActionListener(new ActionListener() {
1217       @Override
1218       public void actionPerformed(ActionEvent e) {
1219         scheduleResultsUpdate();
1220       }
1221     });
1222     scopePanel.add(myModuleComboBox, gbConstraints);
1223
1224     if (modules.length == 1) {
1225       myModuleComboBox.setVisible(false);
1226       myRbModule.setVisible(false);
1227     }
1228
1229     gbConstraints.gridx = 0;
1230     gbConstraints.gridy++;
1231     gbConstraints.weightx = 0;
1232     gbConstraints.gridwidth = 1;
1233     myRbDirectory = new JRadioButton(FindBundle.message("find.scope.directory.radio"), false);
1234     scopePanel.add(myRbDirectory, gbConstraints);
1235     myRbDirectory.addItemListener(resultsPreviewUpdateListener);
1236
1237     gbConstraints.gridx = 1;
1238     gbConstraints.weightx = 1;
1239     myDirectoryComboBox = new ComboBox(200);
1240     Component editorComponent = myDirectoryComboBox.getEditor().getEditorComponent();
1241     if (editorComponent instanceof JTextField) {
1242       JTextField field = (JTextField)editorComponent;
1243       field.setColumns(40);
1244     }
1245     initCombobox(myDirectoryComboBox);
1246     myDirectoryComboBox.setSwingPopup(false);
1247     myDirectoryComboBox.addActionListener(new ActionListener() {
1248       @Override
1249       public void actionPerformed(ActionEvent e) {
1250         scheduleResultsUpdate();
1251       }
1252     });
1253     scopePanel.add(myDirectoryComboBox, gbConstraints);
1254
1255     gbConstraints.weightx = 0;
1256     gbConstraints.gridx = 2;
1257     mySelectDirectoryButton = new FixedSizeButton(myDirectoryComboBox);
1258     TextFieldWithBrowseButton.MyDoClickAction.addTo(mySelectDirectoryButton, myDirectoryComboBox);
1259     mySelectDirectoryButton.setMargin(new Insets(0, 0, 0, 0));
1260     scopePanel.add(mySelectDirectoryButton, gbConstraints);
1261
1262     gbConstraints.gridx = 0;
1263     gbConstraints.gridy++;
1264     gbConstraints.weightx = 1;
1265     gbConstraints.gridwidth = 3;
1266     gbConstraints.insets = new Insets(0, 16, 0, 0);
1267     myCbWithSubdirectories = createCheckbox(true, FindBundle.message("find.scope.directory.recursive.checkbox"));
1268     myCbWithSubdirectories.setSelected(true);
1269     myCbWithSubdirectories.addItemListener(resultsPreviewUpdateListener);
1270     scopePanel.add(myCbWithSubdirectories, gbConstraints);
1271
1272
1273     gbConstraints.gridx = 0;
1274     gbConstraints.gridy++;
1275     gbConstraints.weightx = 0;
1276     gbConstraints.gridwidth = 1;
1277     gbConstraints.insets = new Insets(0, 0, 0, 0);
1278     myRbCustomScope = new JRadioButton(FindBundle.message("find.scope.custom.radio"), false);
1279     scopePanel.add(myRbCustomScope, gbConstraints);
1280
1281     gbConstraints.gridx++;
1282     gbConstraints.weightx = 1;
1283     gbConstraints.gridwidth = 2;
1284     myScopeCombo = new ScopeChooserCombo();
1285     myScopeCombo.init(myProject, true, true, FindSettings.getInstance().getDefaultScopeName(), new Condition<ScopeDescriptor>() {
1286       //final String projectFilesScopeName = PsiBundle.message("psi.search.scope.project");
1287       private final String moduleFilesScopeName;
1288       {
1289         String moduleScopeName = PsiBundle.message("search.scope.module", "");
1290         final int ind = moduleScopeName.indexOf(' ');
1291         moduleFilesScopeName = moduleScopeName.substring(0, ind + 1);
1292       }
1293       @Override
1294       public boolean value(ScopeDescriptor descriptor) {
1295         final String display = descriptor.getDisplay();
1296         return /*!projectFilesScopeName.equals(display) &&*/ !display.startsWith(moduleFilesScopeName);
1297       }
1298     });
1299     myScopeCombo.getComboBox().addActionListener(new ActionListener() {
1300       @Override
1301       public void actionPerformed(ActionEvent e) {
1302         scheduleResultsUpdate();
1303       }
1304     });
1305     myRbCustomScope.addItemListener(resultsPreviewUpdateListener);
1306
1307     Disposer.register(myDisposable, myScopeCombo);
1308     scopePanel.add(myScopeCombo, gbConstraints);
1309
1310
1311     ButtonGroup bgScope = new ButtonGroup();
1312     bgScope.add(myRbProject);
1313     bgScope.add(myRbModule);
1314     bgScope.add(myRbDirectory);
1315     bgScope.add(myRbCustomScope);
1316
1317     myRbProject.addActionListener(new ActionListener() {
1318       @Override
1319       public void actionPerformed(ActionEvent e) {
1320         validateScopeControls();
1321         validateFindButton();
1322       }
1323     });
1324     myRbCustomScope.addActionListener(new ActionListener() {
1325       @Override
1326       public void actionPerformed(ActionEvent e) {
1327         validateScopeControls();
1328         validateFindButton();
1329         myScopeCombo.getComboBox().requestFocusInWindow();
1330       }
1331     });
1332
1333     myRbDirectory.addActionListener(new ActionListener() {
1334       @Override
1335       public void actionPerformed(ActionEvent e) {
1336         validateScopeControls();
1337         validateFindButton();
1338         myDirectoryComboBox.getEditor().getEditorComponent().requestFocusInWindow();
1339       }
1340     });
1341
1342     myRbModule.addActionListener(new ActionListener() {
1343       @Override
1344       public void actionPerformed(ActionEvent e) {
1345         validateScopeControls();
1346         validateFindButton();
1347         myModuleComboBox.requestFocusInWindow();
1348       }
1349     });
1350
1351     mySelectDirectoryButton.addActionListener(new ActionListener() {
1352       @Override
1353       public void actionPerformed(ActionEvent e) {
1354         FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
1355         FileChooser.chooseFiles(descriptor, myProject, null, files -> myDirectoryComboBox.setSelectedItem(files.get(0).getPresentableUrl()));
1356       }
1357     });
1358
1359     return scopePanel;
1360   }
1361
1362   @NotNull
1363   static StateRestoringCheckBox createCheckbox(@NotNull String message) {
1364     final StateRestoringCheckBox cb = new StateRestoringCheckBox(message);
1365     cb.setFocusable(false);
1366     return cb;
1367   }
1368
1369   @NotNull
1370   static StateRestoringCheckBox createCheckbox(boolean selected, @NotNull String message) {
1371     final StateRestoringCheckBox cb = new StateRestoringCheckBox(message, selected);
1372     cb.setFocusable(false);
1373     return cb;
1374   }
1375
1376   private void validateScopeControls() {
1377     if (myRbDirectory.isSelected()) {
1378       myCbWithSubdirectories.makeSelectable();
1379     }
1380     else {
1381       myCbWithSubdirectories.makeUnselectable(myCbWithSubdirectories.isSelected());
1382     }
1383     myDirectoryComboBox.setEnabled(myRbDirectory.isSelected());
1384     mySelectDirectoryButton.setEnabled(myRbDirectory.isSelected());
1385
1386     myModuleComboBox.setEnabled(myRbModule.isSelected());
1387     myScopeCombo.setEnabled(myRbCustomScope.isSelected());
1388   }
1389
1390   @NotNull
1391   private JPanel createScopePanel() {
1392     JPanel scopePanel = new JPanel();
1393     scopePanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.scope.group"), true));
1394     scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
1395
1396     myRbGlobal = new JRadioButton(FindBundle.message("find.scope.global.radio"), true);
1397     scopePanel.add(myRbGlobal);
1398     myRbSelectedText = new JRadioButton(FindBundle.message("find.scope.selected.text.radio"));
1399     scopePanel.add(myRbSelectedText);
1400     ButtonGroup bgScope = new ButtonGroup();
1401     bgScope.add(myRbGlobal);
1402     bgScope.add(myRbSelectedText);
1403
1404     ActionListener actionListener = new ActionListener() {
1405       @Override
1406       public void actionPerformed(ActionEvent e) {
1407         updateControls();
1408       }
1409     };
1410     myRbGlobal.addActionListener(actionListener);
1411     myRbSelectedText.addActionListener(actionListener);
1412
1413     return scopePanel;
1414   }
1415
1416   @NotNull
1417   private JPanel createOriginPanel() {
1418     JPanel originPanel = new JPanel();
1419     originPanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.origin.group"), true));
1420     originPanel.setLayout(new BoxLayout(originPanel, BoxLayout.Y_AXIS));
1421
1422     myRbFromCursor = new JRadioButton(FindBundle.message("find.origin.from.cursor.radio"), true);
1423     originPanel.add(myRbFromCursor);
1424     myRbEntireScope = new JRadioButton(FindBundle.message("find.origin.entire.scope.radio"));
1425     originPanel.add(myRbEntireScope);
1426     ButtonGroup bgOrigin = new ButtonGroup();
1427     bgOrigin.add(myRbFromCursor);
1428     bgOrigin.add(myRbEntireScope);
1429
1430     return originPanel;
1431   }
1432
1433   @NotNull
1434   private String getStringToFind() {
1435     String string = (String)myInputComboBox.getEditor().getItem();
1436     return string == null ? "" : string;
1437   }
1438
1439   @NotNull
1440   private String getStringToReplace() {
1441     String item = (String)myReplaceComboBox.getEditor().getItem();
1442     return item == null ? "" : item;
1443   }
1444
1445   private String getDirectory() {
1446     return (String)myDirectoryComboBox.getEditor().getItem();
1447   }
1448
1449   private static void setStringsToComboBox(@NotNull String[] strings, @NotNull ComboBox combo, String selected) {
1450     if (combo.getItemCount() > 0){
1451       combo.removeAllItems();
1452     }
1453     if (selected != null && selected.indexOf('\n') < 0) {
1454       strings = ArrayUtil.remove(strings, selected);
1455       // this ensures that last searched string will be selected if selected == ""
1456       if (!selected.isEmpty()) strings = ArrayUtil.append(strings, selected);
1457     }
1458     for(int i = strings.length - 1; i >= 0; i--){
1459       combo.addItem(strings[i]);
1460     }
1461   }
1462
1463   private void setDirectories(@NotNull List<String> strings, String directoryName) {
1464     if (myDirectoryComboBox.getItemCount() > 0){
1465       myReplaceComboBox.removeAllItems();
1466     }
1467     int ignoredIdx = -1;
1468     if (directoryName != null && !directoryName.isEmpty()){
1469       ignoredIdx = strings.indexOf(directoryName);
1470       myDirectoryComboBox.addItem(directoryName);
1471     }
1472     for(int i = strings.size() - 1; i >= 0; i--){
1473       if (i == ignoredIdx) continue;
1474       myDirectoryComboBox.addItem(strings.get(i));
1475     }
1476     if (myDirectoryComboBox.getItemCount() == 0){
1477       myDirectoryComboBox.addItem("");
1478     }
1479   }
1480
1481   private void applyTo(@NotNull FindModel model, boolean findAll) {
1482     model.setCaseSensitive(myCbCaseSensitive.isSelected());
1483
1484     if (model.isReplaceState()) {
1485       model.setPreserveCase(myCbPreserveCase.isSelected());
1486     }
1487
1488     model.setWholeWordsOnly(myCbWholeWordsOnly.isSelected());
1489
1490     String selectedSearchContextInUi = (String)mySearchContext.getSelectedItem();
1491     FindModel.SearchContext searchContext = parseSearchContext(selectedSearchContextInUi);
1492
1493     model.setSearchContext(searchContext);
1494
1495     model.setRegularExpressions(myCbRegularExpressions.isSelected());
1496     String stringToFind = getStringToFind();
1497     model.setStringToFind(stringToFind);
1498
1499     if (model.isReplaceState()){
1500       model.setPromptOnReplace(true);
1501       model.setReplaceAll(false);
1502       String stringToReplace = getStringToReplace();
1503       model.setStringToReplace(StringUtil.convertLineSeparators(stringToReplace));
1504     }
1505
1506     if (!model.isMultipleFiles()){
1507       model.setForward(myRbForward.isSelected());
1508       model.setFromCursor(myRbFromCursor.isSelected());
1509       model.setGlobal(myRbGlobal.isSelected());
1510     }
1511     else{
1512       if (myCbToOpenInNewTab != null){
1513         model.setOpenInNewTab(myCbToOpenInNewTab.isSelected());
1514       }
1515
1516       model.setProjectScope(myRbProject.isSelected());
1517       model.setDirectoryName(null);
1518       model.setModuleName(null);
1519       model.setCustomScopeName(null);
1520       model.setCustomScope(null);
1521       model.setCustomScope(false);
1522
1523       if (myRbDirectory.isSelected()) {
1524         String directory = getDirectory();
1525         model.setDirectoryName(directory == null ? "" : directory);
1526         model.setWithSubdirectories(myCbWithSubdirectories.isSelected());
1527       }
1528       else if (myRbModule.isSelected()) {
1529         model.setModuleName((String)myModuleComboBox.getSelectedItem());
1530       }
1531       else if (myRbCustomScope.isSelected()) {
1532         SearchScope selectedScope = myScopeCombo.getSelectedScope();
1533         String customScopeName = selectedScope == null ? null : selectedScope.getDisplayName();
1534         model.setCustomScopeName(customScopeName);
1535         model.setCustomScope(selectedScope == null ? null : selectedScope);
1536         model.setCustomScope(true);
1537       }
1538     }
1539
1540     model.setFindAll(findAll);
1541
1542     String mask = getFileTypeMask();
1543     model.setFileFilter(mask);
1544   }
1545
1546   @Nullable
1547   private String getFileTypeMask() {
1548     String mask = null;
1549     if (myUseFileFilter !=null && myUseFileFilter.isSelected()) {
1550       mask = (String)myFileFilter.getEditor().getItem();
1551     }
1552     return mask;
1553   }
1554
1555
1556   private void initByModel() {
1557     myCbCaseSensitive.setSelected(myModel.isCaseSensitive());
1558     myCbWholeWordsOnly.setSelected(myModel.isWholeWordsOnly());
1559     String searchContext = getSearchContextName(myModel);
1560     mySearchContext.setSelectedItem(searchContext);
1561
1562     myCbRegularExpressions.setSelected(myModel.isRegularExpressions());
1563
1564     if (myModel.isMultipleFiles()) {
1565       final String dirName = myModel.getDirectoryName();
1566       setDirectories(FindInProjectSettings.getInstance(myProject).getRecentDirectories(), dirName);
1567
1568       if (!StringUtil.isEmptyOrSpaces(dirName)) {
1569         VirtualFile dir = LocalFileSystem.getInstance().findFileByPath(dirName);
1570         if (dir != null) {
1571           Module module = ModuleUtilCore.findModuleForFile(dir, myProject);
1572           if (module != null) {
1573             myModuleComboBox.setSelectedItem(module.getName());
1574           }
1575         }
1576       }
1577       if (myModel.isCustomScope()) {
1578         myRbCustomScope.setSelected(true);
1579
1580         myScopeCombo.setEnabled(true);
1581
1582         myCbWithSubdirectories.setEnabled(false);
1583         myDirectoryComboBox.setEnabled(false);
1584         mySelectDirectoryButton.setEnabled(false);
1585         myModuleComboBox.setEnabled(false);
1586       }
1587       else if (myModel.isProjectScope()) {
1588         myRbProject.setSelected(true);
1589
1590         myCbWithSubdirectories.setEnabled(false);
1591         myDirectoryComboBox.setEnabled(false);
1592         mySelectDirectoryButton.setEnabled(false);
1593         myModuleComboBox.setEnabled(false);
1594         myScopeCombo.setEnabled(false);
1595       }
1596       else if (dirName != null) {
1597         myRbDirectory.setSelected(true);
1598         myCbWithSubdirectories.setEnabled(true);
1599         myDirectoryComboBox.setEnabled(true);
1600         mySelectDirectoryButton.setEnabled(true);
1601         myModuleComboBox.setEnabled(false);
1602         myScopeCombo.setEnabled(false);
1603       }
1604       else if (myModel.getModuleName() != null) {
1605         myRbModule.setSelected(true);
1606
1607         myCbWithSubdirectories.setEnabled(false);
1608         myDirectoryComboBox.setEnabled(false);
1609         mySelectDirectoryButton.setEnabled(false);
1610         myModuleComboBox.setEnabled(true);
1611         myModuleComboBox.setSelectedItem(myModel.getModuleName());
1612         myScopeCombo.setEnabled(false);
1613
1614         // force showing even if we have only one module
1615         myRbModule.setVisible(true);
1616         myModuleComboBox.setVisible(true);
1617       }
1618       else {
1619         assert false : myModel;
1620       }
1621
1622       myCbWithSubdirectories.setSelected(myModel.isWithSubdirectories());
1623
1624       if (myModel.getFileFilter()!=null && !myModel.getFileFilter().isEmpty()) {
1625         myFileFilter.setSelectedItem(myModel.getFileFilter());
1626         myFileFilter.setEnabled(true);
1627         myUseFileFilter.setSelected(true);
1628       }
1629     }
1630     else {
1631       if (myModel.isForward()){
1632         myRbForward.setSelected(true);
1633       }
1634       else{
1635         myRbBackward.setSelected(true);
1636       }
1637
1638       if (myModel.isFromCursor()){
1639         myRbFromCursor.setSelected(true);
1640       }
1641       else{
1642         myRbEntireScope.setSelected(true);
1643       }
1644
1645       if (myModel.isGlobal()){
1646         myRbGlobal.setSelected(true);
1647       }
1648       else{
1649         myRbSelectedText.setSelected(true);
1650       }
1651     }
1652
1653     FindInProjectSettings findInProjectSettings = FindInProjectSettings.getInstance(myProject);
1654     setStringsToComboBox(findInProjectSettings.getRecentFindStrings(), myInputComboBox, myModel.getStringToFind());
1655     if (myModel.isReplaceState()){
1656       myCbPreserveCase.setSelected(myModel.isPreserveCase());
1657       setStringsToComboBox(findInProjectSettings.getRecentReplaceStrings(), myReplaceComboBox, myModel.getStringToReplace());
1658     }
1659     updateControls();
1660   }
1661
1662   private void navigateToSelectedUsage(JBTable source) {
1663     int[] rows = source.getSelectedRows();
1664     List<Usage> navigations = null;
1665     for(int row:rows) {
1666       Object valueAt = source.getModel().getValueAt(row, 0);
1667       if (valueAt instanceof Usage) {
1668         if (navigations == null) navigations = new SmartList<>();
1669         Usage at = (Usage)valueAt;
1670         navigations.add(at);
1671       }
1672     }
1673
1674     if (navigations != null) {
1675       doCancelAction();
1676       navigations.get(0).navigate(true);
1677       for(int i = 1; i < navigations.size(); ++i) navigations.get(i).highlightInEditor();
1678     }
1679   }
1680
1681   static class UsageTableCellRenderer extends JPanel implements TableCellRenderer {
1682     private final ColoredTableCellRenderer myUsageRenderer = new ColoredTableCellRenderer() {
1683       @Override
1684       protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
1685         if (value instanceof UsageInfo2UsageAdapter) {
1686           TextChunk[] text = ((UsageInfo2UsageAdapter)value).getPresentation().getText();
1687
1688           // skip line number / file info
1689           for (int i = 1; i < text.length; ++i) {
1690             TextChunk textChunk = text[i];
1691             SimpleTextAttributes attributes = getAttributes(textChunk);
1692             myUsageRenderer.append(textChunk.getText(), attributes);
1693           }
1694         }
1695         setBorder(null);
1696       }
1697
1698       @NotNull
1699       private SimpleTextAttributes getAttributes(@NotNull TextChunk textChunk) {
1700         SimpleTextAttributes at = textChunk.getSimpleAttributesIgnoreBackground();
1701         if (myUseBold) return at;
1702         boolean highlighted = textChunk.getType() != null || at.getFontStyle() == Font.BOLD;
1703         return highlighted
1704                ? new SimpleTextAttributes(null, at.getFgColor(), at.getWaveColor(),
1705                                           (at.getStyle() & ~SimpleTextAttributes.STYLE_BOLD) |
1706                                           SimpleTextAttributes.STYLE_SEARCH_MATCH)
1707                : at;
1708       }
1709     };
1710     private final ColoredTableCellRenderer myFileAndLineNumber = new ColoredTableCellRenderer() {
1711       private final SimpleTextAttributes REPEATED_FILE_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, new JBColor(0xCCCCCC, 0x5E5E5E));
1712       private final SimpleTextAttributes ORDINAL_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, new JBColor(0x999999, 0x999999));
1713       
1714       @Override
1715       protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
1716         if (value instanceof UsageInfo2UsageAdapter) {
1717           UsageInfo2UsageAdapter usageAdapter = (UsageInfo2UsageAdapter)value;
1718           TextChunk[] text = usageAdapter.getPresentation().getText();
1719           // line number / file info
1720           VirtualFile file = usageAdapter.getFile();
1721           String uniqueVirtualFilePath = getFilePath(usageAdapter);
1722           VirtualFile prevFile = findPrevFile(table, row, column);
1723           SimpleTextAttributes attributes = Comparing.equal(file, prevFile) ? REPEATED_FILE_ATTRIBUTES : ORDINAL_ATTRIBUTES;
1724           append(uniqueVirtualFilePath, attributes);
1725           if (text.length > 0) append(" " + text[0].getText(), ORDINAL_ATTRIBUTES);
1726         }
1727         setBorder(null);
1728       }
1729
1730       @NotNull
1731       private String getFilePath(@NotNull UsageInfo2UsageAdapter ua) {
1732         String uniquePath =
1733           UniqueVFilePathBuilder.getInstance().getUniqueVirtualFilePath(ua.getUsageInfo().getProject(), ua.getFile());
1734         return myOmitFileExtension ? StringUtil.trimExtension(uniquePath) : uniquePath;
1735       }
1736
1737       @Nullable
1738       private VirtualFile findPrevFile(@NotNull JTable table, int row, int column) {
1739         if (row <= 0) return null;
1740         Object prev = table.getValueAt(row - 1, column);
1741         return prev instanceof UsageInfo2UsageAdapter ? ((UsageInfo2UsageAdapter)prev).getFile() : null;
1742       }
1743     };
1744
1745     private static final int MARGIN = 2;
1746     private final boolean myOmitFileExtension;
1747     private final boolean myUseBold;
1748
1749     UsageTableCellRenderer(boolean omitFileExtension, boolean useBold) {
1750       myOmitFileExtension = omitFileExtension;
1751       myUseBold = useBold;
1752       setLayout(new BorderLayout());
1753       add(myUsageRenderer, BorderLayout.WEST);
1754       add(myFileAndLineNumber, BorderLayout.EAST);
1755       setBorder(IdeBorderFactory.createEmptyBorder(MARGIN, MARGIN, MARGIN, 0));
1756     }
1757
1758     @Override
1759     public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1760       myUsageRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1761       myFileAndLineNumber.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1762       setBackground(myUsageRenderer.getBackground());
1763       if (!isSelected && value instanceof UsageInfo2UsageAdapter) {
1764         UsageInfo2UsageAdapter usageAdapter = (UsageInfo2UsageAdapter)value;
1765         Color color = FileColorManager.getInstance(usageAdapter.getUsageInfo().getProject()).getFileColor(usageAdapter.getFile());
1766         setBackground(color);
1767         myUsageRenderer.setBackground(color);
1768         myFileAndLineNumber.setBackground(color);
1769       }
1770       return this;
1771     }
1772   }
1773
1774   private class NavigateToSourceListener extends DoubleClickListener {
1775
1776     @Override
1777     protected boolean onDoubleClick(MouseEvent event) {
1778       Object source = event.getSource();
1779       if (!(source instanceof JBTable)) return false;
1780       navigateToSelectedUsage((JBTable)source);
1781       return true;
1782     }
1783
1784     @Override
1785     public void installOn(@NotNull final Component c) {
1786       super.installOn(c);
1787
1788       if (c instanceof JBTable) {
1789         String key = "navigate.to.usage";
1790         JComponent component = (JComponent)c;
1791         component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
1792                                                                                        key);
1793         component.getActionMap().put(key, new AbstractAction() {
1794           @Override
1795           public void actionPerformed(ActionEvent e) {
1796             navigateToSelectedUsage((JBTable)c);
1797           }
1798         });
1799         //anAction.registerCustomShortcutSet(CommonShortcuts.ENTER, component);
1800         registerNavigateToSourceShortcutOnComponent((JBTable)c, component);
1801       }
1802     }
1803   }
1804
1805   protected void registerNavigateToSourceShortcutOnComponent(@NotNull final JBTable c, JComponent component) {
1806     AnAction anAction = new AnAction() {
1807       @Override
1808       public void actionPerformed(AnActionEvent e) {
1809         navigateToSelectedUsage(c);
1810       }
1811     };
1812     anAction.registerCustomShortcutSet(CommonShortcuts.getEditSource(), component, myDisposable);
1813   }
1814 }
1815