SSR: more reliable variable tooltips
[idea/community.git] / platform / structuralsearch / source / com / intellij / structuralsearch / plugin / ui / SearchDialog.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 package com.intellij.structuralsearch.plugin.ui;
17
18 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
19 import com.intellij.codeInsight.template.impl.Variable;
20 import com.intellij.find.FindBundle;
21 import com.intellij.find.FindSettings;
22 import com.intellij.ide.IdeBundle;
23 import com.intellij.ide.util.scopeChooser.ScopeChooserCombo;
24 import com.intellij.lang.Language;
25 import com.intellij.lang.LanguageUtil;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.application.Result;
28 import com.intellij.openapi.command.WriteCommandAction;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.editor.Document;
31 import com.intellij.openapi.editor.Editor;
32 import com.intellij.openapi.editor.EditorFactory;
33 import com.intellij.openapi.editor.SelectionModel;
34 import com.intellij.openapi.editor.event.DocumentEvent;
35 import com.intellij.openapi.editor.event.DocumentListener;
36 import com.intellij.openapi.fileTypes.FileType;
37 import com.intellij.openapi.fileTypes.LanguageFileType;
38 import com.intellij.openapi.fileTypes.impl.FileTypeRenderer;
39 import com.intellij.openapi.progress.ProcessCanceledException;
40 import com.intellij.openapi.project.Project;
41 import com.intellij.openapi.ui.ComboBox;
42 import com.intellij.openapi.ui.DialogWrapper;
43 import com.intellij.openapi.util.Disposer;
44 import com.intellij.openapi.util.TextRange;
45 import com.intellij.openapi.wm.ToolWindow;
46 import com.intellij.openapi.wm.ToolWindowId;
47 import com.intellij.openapi.wm.ToolWindowManager;
48 import com.intellij.psi.PsiDocumentManager;
49 import com.intellij.psi.PsiElement;
50 import com.intellij.psi.PsiFile;
51 import com.intellij.psi.codeStyle.CodeStyleManager;
52 import com.intellij.psi.search.GlobalSearchScope;
53 import com.intellij.psi.search.SearchScope;
54 import com.intellij.structuralsearch.*;
55 import com.intellij.structuralsearch.impl.matcher.MatcherImpl;
56 import com.intellij.structuralsearch.plugin.StructuralSearchPlugin;
57 import com.intellij.ui.ComboboxSpeedSearch;
58 import com.intellij.ui.IdeBorderFactory;
59 import com.intellij.ui.ListCellRendererWrapper;
60 import com.intellij.ui.TitledSeparator;
61 import com.intellij.util.Alarm;
62 import com.intellij.util.ui.JBUI;
63 import org.jetbrains.annotations.NonNls;
64 import org.jetbrains.annotations.NotNull;
65
66 import javax.swing.*;
67 import java.awt.*;
68 import java.awt.event.ActionEvent;
69 import java.awt.event.ItemEvent;
70 import java.awt.event.ItemListener;
71 import java.util.*;
72 import java.util.List;
73
74 /**
75  *  Class to show the user the request for search
76  */
77 @SuppressWarnings({"RefusedBequest", "AssignmentToStaticFieldFromInstanceMethod"})
78 public class SearchDialog extends DialogWrapper {
79   protected SearchContext searchContext;
80
81   // text for search
82   protected Editor searchCriteriaEdit;
83
84   // options of search scope
85   private ScopeChooserCombo myScopeChooserCombo;
86
87   private JCheckBox recursiveMatching;
88   private JCheckBox caseSensitiveMatch;
89
90   private JComboBox<FileType> fileTypes;
91   private JComboBox<String> contexts;
92   private JComboBox<Language> dialects;
93   private JLabel status;
94   private JLabel statusText;
95
96   protected SearchModel model;
97   private JCheckBox openInNewTab;
98   private final Alarm myAlarm;
99
100   public static final String USER_DEFINED = SSRBundle.message("new.template.defaultname");
101   protected final ExistingTemplatesComponent existingTemplatesComponent;
102
103   private boolean useLastConfiguration;
104
105   @NonNls private FileType ourFtSearchVariant = StructuralSearchUtil.getDefaultFileType();
106   private static Language ourDialect = null;
107   private static String ourContext = null;
108
109   private final boolean myShowScopePanel;
110   private final boolean myRunFindActionOnClose;
111   private boolean myDoingOkAction;
112
113   private String mySavedEditorText;
114   private JPanel myContentPanel;
115   private JComponent myEditorPanel;
116
117   public SearchDialog(SearchContext searchContext) {
118     this(searchContext, true, true);
119   }
120
121   public SearchDialog(SearchContext searchContext, boolean showScope, boolean runFindActionOnClose) {
122     super(searchContext.getProject(), true);
123
124     if (showScope) setModal(false);
125     myShowScopePanel = showScope;
126     myRunFindActionOnClose = runFindActionOnClose;
127     this.searchContext = searchContext;
128     setTitle(getDefaultTitle());
129
130     if (runFindActionOnClose) {
131       setOKButtonText(FindBundle.message("find.dialog.find.button"));
132     }
133
134     existingTemplatesComponent = ExistingTemplatesComponent.getInstance(this.searchContext.getProject());
135     model = new SearchModel(createConfiguration());
136
137     init();
138     myAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD,myDisposable);
139   }
140
141   public void setUseLastConfiguration(boolean useLastConfiguration) {
142     this.useLastConfiguration = useLastConfiguration;
143   }
144
145   private void setSearchPattern(final Configuration config) {
146     model.setShadowConfig(config);
147     setValuesFromConfig(config);
148     initiateValidation();
149   }
150
151   protected Editor createEditor(final SearchContext searchContext, String text) {
152     Editor editor = null;
153
154     if (fileTypes != null) {
155       final FileType fileType = (FileType)fileTypes.getSelectedItem();
156       final Language dialect = (Language)dialects.getSelectedItem();
157
158       final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType);
159       if (profile != null) {
160         editor = profile.createEditor(searchContext, fileType, dialect, text, useLastConfiguration);
161       }
162     }
163
164     if (editor == null) {
165       final EditorFactory factory = EditorFactory.getInstance();
166       final Document document = factory.createDocument("");
167       editor = factory.createEditor(document, searchContext.getProject());
168       editor.getSettings().setFoldingOutlineShown(false);
169     }
170
171     editor.getDocument().addDocumentListener(new DocumentListener() {
172       @Override
173       public void beforeDocumentChange(final DocumentEvent event) {
174       }
175
176       @Override
177       public void documentChanged(final DocumentEvent event) {
178         initiateValidation();
179       }
180     });
181
182     return editor;
183   }
184
185   private void initiateValidation() {
186     myAlarm.cancelAllRequests();
187     myAlarm.addRequest(() -> {
188       try {
189         ApplicationManager.getApplication().runReadAction(() -> {
190           final boolean valid = isValid();
191           ApplicationManager.getApplication().invokeLater(() -> {
192             if (!valid) {
193               getOKAction().setEnabled(false);
194             }
195             else {
196               getOKAction().setEnabled(true);
197               reportMessage(null, null);
198             }
199           });
200         });
201       }
202       catch (ProcessCanceledException e) {
203         throw e;
204       }
205       catch (RuntimeException e) {
206         Logger.getInstance(SearchDialog.class).error(e);
207       }
208     }, 500);
209   }
210
211   protected void buildOptions(JPanel searchOptions) {
212     recursiveMatching = new JCheckBox(SSRBundle.message("recursive.matching.checkbox"), true);
213     if (isRecursiveSearchEnabled()) {
214       searchOptions.add(UIUtil.createOptionLine(recursiveMatching));
215     }
216
217     caseSensitiveMatch = new JCheckBox(FindBundle.message("find.options.case.sensitive"), true);
218     searchOptions.add(UIUtil.createOptionLine(caseSensitiveMatch));
219
220     final List<FileType> types = new ArrayList<>();
221
222     for (FileType fileType : StructuralSearchUtil.getSuitableFileTypes()) {
223       if (StructuralSearchUtil.getProfileByFileType(fileType) != null) {
224         types.add(fileType);
225       }
226     }
227     Collections.sort(types, (o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName()));
228
229     final DefaultComboBoxModel<FileType> comboBoxModel = new DefaultComboBoxModel<>(types.toArray(new FileType[types.size()]));
230     fileTypes = new ComboBox<>(comboBoxModel);
231     fileTypes.setRenderer(new FileTypeRenderer());
232     new ComboboxSpeedSearch(fileTypes) {
233       @Override
234       protected String getElementText(Object element) {
235         return ((FileType)element).getName();
236       }
237     };
238     contexts = new ComboBox<>();
239     contexts.setPreferredSize(new Dimension(60, -1));
240
241     dialects = new ComboBox<>();
242     dialects.setRenderer(new ListCellRendererWrapper<Language>() {
243       @Override
244       public void customize(JList list, Language value, int index, boolean selected, boolean hasFocus) {
245         if (value == null) {
246           setText("None");
247         }
248         else {
249           setText(value.getDisplayName());
250         }
251       }
252     });
253     dialects.addItemListener(new ItemListener() {
254       @Override
255       public void itemStateChanged(ItemEvent e) {
256         updateEditor();
257       }
258     });
259     new ComboboxSpeedSearch(dialects);
260     dialects.setPreferredSize(new Dimension(120, -1));
261
262     final JLabel jLabel = new JLabel(SSRBundle.message("search.dialog.file.type.label"));
263     final JLabel jLabel2 = new JLabel(SSRBundle.message("search.dialog.context.label"));
264     final JLabel jLabel3 = new JLabel(SSRBundle.message("search.dialog.file.dialect.label"));
265     searchOptions.add(
266       UIUtil.createOptionLine(
267         new JComponent[]{
268           jLabel,
269           fileTypes,
270           (JComponent)Box.createHorizontalStrut(8),
271           jLabel2,
272           contexts,
273           (JComponent)Box.createHorizontalStrut(8),
274           jLabel3,
275           dialects,
276         }
277       )
278     );
279
280     jLabel.setLabelFor(fileTypes);
281     jLabel2.setLabelFor(contexts);
282     jLabel3.setLabelFor(dialects);
283
284     detectFileTypeAndDialect();
285
286     fileTypes.setSelectedItem(ourFtSearchVariant);
287     fileTypes.addItemListener(new ItemListener() {
288       @Override
289       public void itemStateChanged(ItemEvent e) {
290         if (e.getStateChange() == ItemEvent.SELECTED) {
291           updateDialectsAndContexts();
292           updateEditor();
293           initiateValidation();
294         }
295       }
296     });
297     dialects.setSelectedItem(ourDialect);
298     contexts.setSelectedItem(ourContext);
299
300     updateDialectsAndContexts();
301   }
302
303   private void updateEditor() {
304     if (myContentPanel != null) {
305       if (myEditorPanel != null) {
306         myContentPanel.remove(myEditorPanel);
307       }
308       disposeEditorContent();
309       myEditorPanel = createEditorContent();
310       myContentPanel.add(myEditorPanel, BorderLayout.CENTER);
311       myContentPanel.revalidate();
312       searchCriteriaEdit.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, model.getConfig());
313     }
314   }
315
316   private void updateDialectsAndContexts() {
317     final FileType fileType = (FileType)fileTypes.getSelectedItem();
318     if (fileType instanceof LanguageFileType) {
319       Language language = ((LanguageFileType)fileType).getLanguage();
320       Language[] languageDialects = LanguageUtil.getLanguageDialects(language);
321       Arrays.sort(languageDialects, Comparator.comparing(Language::getDisplayName));
322       Language[] variants = new Language[languageDialects.length + 1];
323       variants[0] = null;
324       System.arraycopy(languageDialects, 0, variants, 1, languageDialects.length);
325       dialects.setModel(new DefaultComboBoxModel<>(variants));
326       dialects.setEnabled(variants.length > 1);
327     }
328
329     final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType);
330
331     if (profile instanceof StructuralSearchProfileBase) {
332       final String[] contextNames = ((StructuralSearchProfileBase)profile).getContextNames();
333       if (contextNames.length > 0) {
334         contexts.setModel(new DefaultComboBoxModel<>(contextNames));
335         contexts.setSelectedItem(contextNames[0]);
336         contexts.setEnabled(true);
337         return;
338       }
339     }
340     contexts.setSelectedItem(null);
341     contexts.setEnabled(false);
342   }
343
344   private void detectFileTypeAndDialect() {
345     final PsiFile file = searchContext.getFile();
346     if (file != null) {
347       PsiElement context = null;
348
349       if (searchContext.getEditor() != null) {
350         context = file.findElementAt(searchContext.getEditor().getCaretModel().getOffset());
351         if (context != null) {
352           context = context.getParent();
353         }
354       }
355       if (context == null) {
356         context = file;
357       }
358
359       FileType detectedFileType = null;
360
361       StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(context);
362       if (profile != null) {
363         FileType fileType = profile.detectFileType(context);
364         if (fileType != null) {
365           detectedFileType = fileType;
366         }
367       }
368
369       if (detectedFileType == null) {
370         for (FileType fileType : StructuralSearchUtil.getSuitableFileTypes()) {
371           if (fileType instanceof LanguageFileType && ((LanguageFileType)fileType).getLanguage().equals(context.getLanguage())) {
372             detectedFileType = fileType;
373             break;
374           }
375         }
376       }
377
378       ourFtSearchVariant = detectedFileType != null ?
379                            detectedFileType :
380                            StructuralSearchUtil.getDefaultFileType();
381     }
382   }
383
384   protected boolean isRecursiveSearchEnabled() {
385     return true;
386   }
387
388   public void setValuesFromConfig(Configuration configuration) {
389     setDialogTitle(configuration);
390     final MatchOptions matchOptions = configuration.getMatchOptions();
391
392     UIUtil.setContent(
393       searchCriteriaEdit,
394       matchOptions.getSearchPattern(),
395       0,
396       searchCriteriaEdit.getDocument().getTextLength(),
397       searchContext.getProject()
398     );
399
400     model.getConfig().getMatchOptions().setSearchPattern(
401       matchOptions.getSearchPattern()
402     );
403
404     recursiveMatching.setSelected(
405       isRecursiveSearchEnabled() && matchOptions.isRecursiveSearch()
406     );
407
408     caseSensitiveMatch.setSelected(
409       matchOptions.isCaseSensitiveMatch()
410     );
411
412     model.getConfig().getMatchOptions().clearVariableConstraints();
413     for (String name : matchOptions.getVariableConstraintNames()) {
414       final MatchVariableConstraint constraint = (MatchVariableConstraint)matchOptions.getVariableConstraint(name).clone();
415       model.getConfig().getMatchOptions().addVariableConstraint(constraint);
416     }
417
418     MatchOptions options = configuration.getMatchOptions();
419     StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(options.getFileType());
420     assert profile != null;
421     fileTypes.setSelectedItem(options.getFileType());
422     dialects.setSelectedItem(options.getDialect());
423     if (options.getPatternContext() != null) {
424       contexts.setSelectedItem(options.getPatternContext());
425     }
426   }
427
428   private void setDialogTitle(final Configuration configuration) {
429     setTitle(getDefaultTitle() + " - " + configuration.getName());
430   }
431
432   public Configuration createConfiguration() {
433     SearchConfiguration configuration = new SearchConfiguration();
434     configuration.setName(USER_DEFINED);
435     return configuration;
436   }
437
438   protected void addOrReplaceSelection(final String selection) {
439     addOrReplaceSelectionForEditor(selection, searchCriteriaEdit);
440   }
441
442   protected final void addOrReplaceSelectionForEditor(final String selection, Editor editor) {
443     final Project project = searchContext.getProject();
444     UIUtil.setContent(editor, selection, 0, -1, project);
445     final Document document = editor.getDocument();
446     editor.getSelectionModel().setSelection(0, document.getTextLength());
447     final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
448     documentManager.commitDocument(document);
449     final PsiFile file = documentManager.getPsiFile(document);
450     if (file == null) return;
451
452     new WriteCommandAction(project, file) {
453       @Override protected void run(@NotNull Result result) throws Throwable {
454         CodeStyleManager.getInstance(project).adjustLineIndent(file, new TextRange(0, document.getTextLength()));
455       }
456     }.execute();
457   }
458
459   protected void startSearching() {
460     new SearchCommand(model.getConfig(), searchContext).startSearching();
461   }
462
463   protected String getDefaultTitle() {
464     return SSRBundle.message("structural.search.title");
465   }
466
467   protected JComponent createEditorContent() {
468     final JPanel result = new JPanel(new BorderLayout());
469
470     searchCriteriaEdit = createEditor(searchContext, mySavedEditorText != null ? mySavedEditorText : "");
471     result.add(BorderLayout.CENTER, searchCriteriaEdit.getComponent());
472     result.setMinimumSize(new Dimension(150, 100));
473
474     final JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 0));
475     labelPanel.add(new JLabel(SSRBundle.message("search.template")));
476
477     labelPanel.add(UIUtil.createCompleteMatchInfo(() -> model.getConfig()));
478     result.add(BorderLayout.NORTH, labelPanel);
479
480     return result;
481   }
482
483   protected int getRowsCount() {
484     return 4;
485   }
486
487   @Override
488   protected JComponent createCenterPanel() {
489     myContentPanel = new JPanel(new BorderLayout());
490     myEditorPanel = createEditorContent();
491     myContentPanel.add(BorderLayout.CENTER, myEditorPanel);
492     myContentPanel.add(BorderLayout.SOUTH, Box.createVerticalStrut(8));
493     JComponent centerPanel = new JPanel(new BorderLayout());
494     {
495       JPanel panel = new JPanel(new BorderLayout());
496       panel.add(BorderLayout.CENTER, myContentPanel);
497       panel.add(BorderLayout.SOUTH, createTemplateManagementButtons());
498       centerPanel.add(BorderLayout.CENTER, panel);
499     }
500
501     JPanel optionsContent = new JPanel(new BorderLayout());
502     centerPanel.add(BorderLayout.SOUTH, optionsContent);
503
504     JPanel searchOptions = new JPanel();
505     searchOptions.setLayout(new GridLayout(getRowsCount(), 1, 0, 0));
506     searchOptions.setBorder(IdeBorderFactory.createTitledBorder(SSRBundle.message("ssdialog.options.group.border"),
507                                                                 true));
508
509     myScopeChooserCombo = new ScopeChooserCombo(
510       searchContext.getProject(),
511       true,
512       false,
513       FindSettings.getInstance().getDefaultScopeName()
514     );
515     Disposer.register(myDisposable, myScopeChooserCombo);
516     JPanel allOptions = new JPanel(new BorderLayout());
517     if (myShowScopePanel) {
518       JPanel scopePanel = new JPanel(new GridBagLayout());
519
520       TitledSeparator separator = new TitledSeparator(SSRBundle.message("search.dialog.scope.label"), myScopeChooserCombo.getComboBox());
521       scopePanel.add(separator, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
522                                                        JBUI.insetsTop(5), 0, 0));
523
524       scopePanel.add(myScopeChooserCombo, new GridBagConstraints(0, 1, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
525                                                                  JBUI.insetsLeft(10), 0, 0));
526
527       allOptions.add(
528         scopePanel,
529         BorderLayout.SOUTH
530       );
531
532       myScopeChooserCombo.getComboBox().addItemListener(new ItemListener() {
533         @Override
534         public void itemStateChanged(ItemEvent e) {
535           initiateValidation();
536         }
537       });
538     }
539
540     buildOptions(searchOptions);
541
542     allOptions.add(searchOptions, BorderLayout.CENTER);
543     optionsContent.add(allOptions, BorderLayout.CENTER);
544
545     if (myRunFindActionOnClose) {
546       JPanel panel = new JPanel(new BorderLayout());
547       panel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0));
548       openInNewTab = new JCheckBox(FindBundle.message("find.open.in.new.tab.checkbox"));
549       openInNewTab.setSelected(FindSettings.getInstance().isShowResultsInSeparateView());
550       ToolWindow findWindow = ToolWindowManager.getInstance(searchContext.getProject()).getToolWindow(ToolWindowId.FIND);
551       openInNewTab.setEnabled(findWindow != null && findWindow.isAvailable());
552       panel.add(openInNewTab, BorderLayout.EAST);
553
554       optionsContent.add(BorderLayout.SOUTH, panel);
555     }
556
557     updateEditor();
558     return centerPanel;
559   }
560
561
562   @Override
563   protected JComponent createSouthPanel() {
564     final JPanel statusPanel = new JPanel(new BorderLayout(5, 0));
565     statusPanel.add(super.createSouthPanel(), BorderLayout.NORTH);
566     statusPanel.add(statusText = new JLabel(SSRBundle.message("status.message")), BorderLayout.WEST);
567     statusPanel.add(status = new JLabel(), BorderLayout.CENTER);
568     return statusPanel;
569   }
570
571   private JPanel createTemplateManagementButtons() {
572     JPanel panel = new JPanel(null);
573     panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
574     panel.add(Box.createHorizontalGlue());
575
576     panel.add(
577       createJButtonForAction(new AbstractAction() {
578         {
579           putValue(NAME, SSRBundle.message("save.template.text.button"));
580         }
581
582         @Override
583         public void actionPerformed(ActionEvent e) {
584           String name = showSaveTemplateAsDialog();
585
586           if (name != null) {
587             final Project project = searchContext.getProject();
588             final ConfigurationManager configurationManager = ConfigurationManager.getInstance(project);
589             final Collection<Configuration> configurations = configurationManager.getConfigurations();
590
591             if (configurations != null) {
592               name = ConfigurationManager.findAppropriateName(configurations, name, project);
593               if (name == null) return;
594             }
595
596             final Configuration configuration = model.getConfig();
597             model = new SearchModel(createConfiguration());
598             model.setShadowConfig(configuration);
599             configuration.setName(name);
600             setValuesToConfig(configuration);
601             setDialogTitle(configuration);
602
603             filterOutUnusedVariableConstraints(configuration);
604             configurationManager.addConfiguration(configuration);
605             existingTemplatesComponent.setUserTemplates(configurationManager);
606           }
607         }
608       })
609     );
610
611     panel.add(Box.createHorizontalStrut(8));
612
613     panel.add(
614       createJButtonForAction(
615         new AbstractAction() {
616           {
617             putValue(NAME, SSRBundle.message("edit.variables.button"));
618           }
619
620           @Override
621           public void actionPerformed(ActionEvent e) {
622             EditVarConstraintsDialog.setProject(searchContext.getProject());
623             new EditVarConstraintsDialog(
624               searchContext.getProject(),
625               model.getConfig(), getVariablesFromListeners(),
626               (FileType)fileTypes.getSelectedItem()
627             ).show();
628             initiateValidation();
629             EditVarConstraintsDialog.setProject(null);
630           }
631         }
632       )
633     );
634
635     panel.add(
636       Box.createHorizontalStrut(8)
637     );
638
639     panel.add(
640       createJButtonForAction(
641         new AbstractAction() {
642           {
643             putValue(NAME, SSRBundle.message("history.button"));
644           }
645
646           @Override
647           public void actionPerformed(ActionEvent e) {
648             SelectTemplateDialog dialog = new SelectTemplateDialog(searchContext.getProject(), true, isReplaceDialog());
649             if (!dialog.showAndGet()) {
650               return;
651             }
652             Configuration[] configurations = dialog.getSelectedConfigurations();
653             if (configurations.length == 1) {
654               setSearchPattern(configurations[0]);
655             }
656           }
657         }
658       )
659     );
660
661     panel.add(
662       Box.createHorizontalStrut(8)
663     );
664
665     panel.add(
666       createJButtonForAction(
667         new AbstractAction() {
668           {
669             putValue(NAME, SSRBundle.message("copy.existing.template.button"));
670           }
671
672           @Override
673           public void actionPerformed(ActionEvent e) {
674             SelectTemplateDialog dialog = new SelectTemplateDialog(searchContext.getProject(), false, isReplaceDialog());
675             if (!dialog.showAndGet()) {
676               return;
677             }
678             Configuration[] configurations = dialog.getSelectedConfigurations();
679             if (configurations.length == 1) {
680               setSearchPattern(configurations[0]);
681             }
682           }
683         }
684       )
685     );
686
687     return panel;
688   }
689
690   protected List<Variable> getVariablesFromListeners() {
691     return getVarsFrom(searchCriteriaEdit);
692   }
693
694   protected static List<Variable> getVarsFrom(Editor searchCriteriaEdit) {
695     SubstitutionShortInfoHandler handler = searchCriteriaEdit.getUserData(UIUtil.LISTENER_KEY);
696     return (handler == null) ? new ArrayList<>() : new ArrayList<>(handler.getVariables());
697   }
698
699   public final Project getProject() {
700     return searchContext.getProject();
701   }
702
703   public String showSaveTemplateAsDialog() {
704     return ConfigurationManager.showSaveTemplateAsDialog(
705       model.getShadowConfig() != null ? model.getShadowConfig().getName() : SSRBundle.message("user.defined.category"),
706       searchContext.getProject()
707     );
708   }
709
710   protected boolean isReplaceDialog() {
711     return false;
712   }
713
714   @Override
715   public void show() {
716     StructuralSearchPlugin.getInstance(getProject()).setDialogVisible(true);
717
718     if (!useLastConfiguration) {
719       final Editor editor = searchContext.getEditor();
720       boolean setSomeText = false;
721
722       if (editor != null) {
723         final SelectionModel selectionModel = editor.getSelectionModel();
724
725         if (selectionModel.hasSelection()) {
726           addOrReplaceSelection(selectionModel.getSelectedText());
727           existingTemplatesComponent.getPatternTree().setSelectionPath(null);
728           existingTemplatesComponent.getHistoryList().setSelectedIndex(-1);
729           setSomeText = true;
730         }
731       }
732
733       if (!setSomeText) {
734         int selection = existingTemplatesComponent.getHistoryList().getSelectedIndex();
735         if (selection != -1) {
736           setValuesFromConfig((Configuration)existingTemplatesComponent.getHistoryList().getSelectedValue());
737         }
738       }
739     }
740
741     initiateValidation();
742
743     super.show();
744   }
745
746   @Override
747   public JComponent getPreferredFocusedComponent() {
748     return searchCriteriaEdit.getContentComponent();
749   }
750
751   // Performs ok action
752   @Override
753   protected void doOKAction() {
754     SearchScope selectedScope = getSelectedScope();
755     if (selectedScope == null) return;
756
757     myDoingOkAction = true;
758     boolean result = isValid();
759     myDoingOkAction = false;
760     if (!result) return;
761
762     myAlarm.cancelAllRequests();
763     super.doOKAction();
764     if (!myRunFindActionOnClose) return;
765
766     final FindSettings findSettings = FindSettings.getInstance();
767     findSettings.setDefaultScopeName(selectedScope.getDisplayName());
768     findSettings.setShowResultsInSeparateView(openInNewTab.isSelected());
769
770     try {
771       final Configuration configuration = model.getConfig();
772       if (model.getShadowConfig() != null) {
773         if (model.getShadowConfig().isPredefined()) {
774           configuration.setName(model.getShadowConfig().getName()
775           );
776         } //else {
777         //  // user template, save it
778         //  setValuesToConfig(model.getShadowConfig());
779         //}
780       }
781       filterOutUnusedVariableConstraints(configuration);
782       existingTemplatesComponent.addConfigurationToHistory(configuration);
783
784       startSearching();
785     }
786     catch (MalformedPatternException ex) {
787       reportMessage("this.pattern.is.malformed.message", searchCriteriaEdit, ex.getMessage());
788     }
789   }
790
791   private void filterOutUnusedVariableConstraints(Configuration configuration) {
792     final List<Variable> variables = getVariablesFromListeners();
793     final List<String> variableNames = new ArrayList<>();
794     for (Variable variable : variables) {
795       variableNames.add(variable.getName());
796     }
797     variableNames.add(Configuration.CONTEXT_VAR_NAME);
798     configuration.getMatchOptions().retainVariableConstraints(variableNames);
799   }
800
801   public Configuration getConfiguration() {
802     return model.getConfig();
803   }
804
805   private SearchScope getSelectedScope() {
806     return myScopeChooserCombo.getSelectedScope();
807   }
808
809   protected boolean isValid() {
810     setValuesToConfig(model.getConfig());
811     boolean result = true;
812
813     try {
814       MatcherImpl.validate(searchContext.getProject(), model.getConfig().getMatchOptions());
815     }
816     catch (MalformedPatternException ex) {
817       if (myRunFindActionOnClose) {
818         reportMessage("this.pattern.is.malformed.message", searchCriteriaEdit, (ex.getMessage() != null) ? ex.getMessage() : "");
819         result = false;
820       }
821     }
822     catch (UnsupportedPatternException ex) {
823       reportMessage("this.pattern.is.unsupported.message", searchCriteriaEdit, ex.getMessage());
824       result = false;
825     }
826
827     return result;
828   }
829
830   protected void reportMessage(@NonNls final String messageId, final Editor editor, final Object... params) {
831     com.intellij.util.ui.UIUtil.invokeLaterIfNeeded(() -> {
832       final String message = messageId != null ? SSRBundle.message(messageId, params) : "";
833       status.setText(message);
834       status.setToolTipText(message);
835       status.revalidate();
836       statusText.setLabelFor(editor != null ? editor.getContentComponent() : null);
837     });
838   }
839
840   protected void setValuesToConfig(Configuration config) {
841
842     MatchOptions options = config.getMatchOptions();
843
844     boolean searchWithinHierarchy = IdeBundle.message("scope.class.hierarchy").equals(myScopeChooserCombo.getSelectedScopeName());
845     // We need to reset search within hierarchy scope during online validation since the scope works with user participation
846     options.setScope(
847       searchWithinHierarchy && !myDoingOkAction ? GlobalSearchScope.projectScope(getProject()) : myScopeChooserCombo.getSelectedScope());
848     options.setLooseMatching(true);
849     options.setRecursiveSearch(isRecursiveSearchEnabled() && recursiveMatching.isSelected());
850
851     ourFtSearchVariant = (FileType)fileTypes.getSelectedItem();
852     ourDialect = (Language)dialects.getSelectedItem();
853     ourContext = (String)contexts.getSelectedItem();
854     FileType fileType = ourFtSearchVariant;
855     options.setFileType(fileType);
856     options.setDialect(ourDialect);
857     options.setPatternContext(ourContext);
858
859     options.setSearchPattern(searchCriteriaEdit.getDocument().getText());
860     options.setCaseSensitiveMatch(caseSensitiveMatch.isSelected());
861   }
862
863   @Override
864   protected String getDimensionServiceKey() {
865     return "#com.intellij.structuralsearch.plugin.ui.SearchDialog";
866   }
867
868   @Override
869   public void dispose() {
870     disposeEditorContent();
871
872     myAlarm.cancelAllRequests();
873
874     super.dispose();
875     StructuralSearchPlugin.getInstance(getProject()).setDialogVisible(false);
876   }
877
878   protected void disposeEditorContent() {
879     mySavedEditorText = searchCriteriaEdit.getDocument().getText();
880
881     // this will remove from myExcludedSet
882     final PsiFile file = PsiDocumentManager.getInstance(searchContext.getProject()).getPsiFile(searchCriteriaEdit.getDocument());
883     if (file != null) {
884       DaemonCodeAnalyzer.getInstance(searchContext.getProject()).setHighlightingEnabled(file, true);
885     }
886
887     EditorFactory.getInstance().releaseEditor(searchCriteriaEdit);
888   }
889
890   @Override
891   protected String getHelpId() {
892     return "find.structuredSearch";
893   }
894
895   public SearchContext getSearchContext() {
896     return searchContext;
897   }
898 }