From d18ce782573a1bc989d07071aad89a0183e2a06d Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Fri, 8 Jun 2018 10:31:28 +0200 Subject: [PATCH] SSR: new Structural Search dialog WIP --- .../JavaStructuralSearchProfile.java | 44 -- .../StructuralSearchProfile.java | 43 +- .../SSBasedInspectionOptions.java | 27 +- .../plugin/StructuralSearchAction.java | 26 +- .../plugin/ui/ScopePanel.java | 63 ++ .../plugin/ui/SearchDialog.java | 6 +- .../plugin/ui/StructuralSearchDialog.java | 676 ++++++++++++++++++ .../structuralsearch/plugin/ui/UIUtil.java | 16 +- .../source/messages/SSRBundle.properties | 4 +- .../util/resources/misc/registry.properties | 2 + 10 files changed, 819 insertions(+), 88 deletions(-) create mode 100644 platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ScopePanel.java create mode 100644 platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/StructuralSearchDialog.java diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java index f74c9607815e..4dacd71d1e27 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java @@ -2,7 +2,6 @@ package com.intellij.structuralsearch; import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInsight.template.JavaCodeContextType; import com.intellij.codeInsight.template.TemplateContextType; import com.intellij.dupLocator.iterators.NodeIterator; @@ -10,9 +9,6 @@ import com.intellij.dupLocator.util.NodeFilter; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.lang.Language; import com.intellij.lang.java.JavaLanguage; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.fileTypes.StdFileTypes; @@ -39,7 +35,6 @@ import com.intellij.structuralsearch.plugin.replace.impl.ReplacementBuilder; import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext; import com.intellij.structuralsearch.plugin.replace.impl.Replacer; import com.intellij.structuralsearch.plugin.ui.Configuration; -import com.intellij.structuralsearch.plugin.ui.SearchContext; import com.intellij.structuralsearch.plugin.ui.UIUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; @@ -377,45 +372,6 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile { return false; } - @NotNull - @Override - public Editor createEditor(@NotNull SearchContext searchContext, - @NotNull FileType fileType, - Language dialect, - String text, - boolean useLastConfiguration) { - // provides autocompletion - - PsiElement element = searchContext.getFile(); - - final Project project = searchContext.getProject(); - if (element != null && !useLastConfiguration) { - final Editor selectedEditor = FileEditorManager.getInstance(project).getSelectedTextEditor(); - - if (selectedEditor != null) { - int caretPosition = selectedEditor.getCaretModel().getOffset(); - PsiElement positionedElement = searchContext.getFile().findElementAt(caretPosition); - - if (positionedElement == null) { - positionedElement = searchContext.getFile().findElementAt(caretPosition + 1); - } - - if (positionedElement != null) { - element = PsiTreeUtil.getParentOfType( - positionedElement, - PsiClass.class, PsiCodeBlock.class - ); - } - } - } - - final PsiCodeFragment file = createCodeFragment(project, text, element); - final Document doc = PsiDocumentManager.getInstance(project).getDocument(file); - assert doc != null; - DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(file, false); - return UIUtil.createEditor(doc, project, true, true, getTemplateContextType()); - } - @NotNull @Override public Class getTemplateContextTypeClass() { diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java index 2c49c33bfcc1..a707e112ec06 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java @@ -27,7 +27,6 @@ import com.intellij.structuralsearch.plugin.replace.impl.ReplacementBuilder; import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext; import com.intellij.structuralsearch.plugin.replace.impl.Replacer; import com.intellij.structuralsearch.plugin.ui.Configuration; -import com.intellij.structuralsearch.plugin.ui.SearchContext; import com.intellij.structuralsearch.plugin.ui.UIUtil; import com.intellij.util.LocalTimeCounter; import com.intellij.util.containers.ContainerUtil; @@ -93,33 +92,49 @@ public abstract class StructuralSearchProfile { } @NotNull - public Editor createEditor(@NotNull SearchContext searchContext, - @NotNull FileType fileType, - Language dialect, - String text, - boolean useLastConfiguration) { - PsiFile codeFragment = createCodeFragment(searchContext.getProject(), text, null); + public Document createDocument(@NotNull Project project, @NotNull FileType fileType, Language dialect, String text) { + PsiFile codeFragment = createCodeFragment(project, text, null); if (codeFragment == null) { - codeFragment = createFileFragment(searchContext, fileType, dialect, text); + codeFragment = createFileFragment(project, fileType, dialect, text); } if (codeFragment != null) { - final Document doc = PsiDocumentManager.getInstance(searchContext.getProject()).getDocument(codeFragment); + final Document doc = PsiDocumentManager.getInstance(project).getDocument(codeFragment); assert doc != null : "code fragment element should be physical"; - DaemonCodeAnalyzer.getInstance(searchContext.getProject()).setHighlightingEnabled(codeFragment, false); - return UIUtil.createEditor(doc, searchContext.getProject(), true, true, getTemplateContextType()); + //DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(codeFragment, false); + return doc; + } + + return EditorFactory.getInstance().createDocument(text); + } + + @NotNull + public Editor createEditor(@NotNull Project project, + @NotNull FileType fileType, + Language dialect, + String text) { + PsiFile codeFragment = createCodeFragment(project, text, null); + if (codeFragment == null) { + codeFragment = createFileFragment(project, fileType, dialect, text); + } + + if (codeFragment != null) { + final Document doc = PsiDocumentManager.getInstance(project).getDocument(codeFragment); + assert doc != null : "code fragment element should be physical"; + DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(codeFragment, false); + return UIUtil.createEditor(doc, project, true, true, getTemplateContextType()); } final EditorFactory factory = EditorFactory.getInstance(); final Document document = factory.createDocument(text); - final EditorEx editor = (EditorEx)factory.createEditor(document, searchContext.getProject()); + final EditorEx editor = (EditorEx)factory.createEditor(document, project); editor.getSettings().setFoldingOutlineShown(false); return editor; } - private static PsiFile createFileFragment(SearchContext searchContext, FileType fileType, Language dialect, String text) { + private static PsiFile createFileFragment(Project project, FileType fileType, Language dialect, String text) { final String name = "__dummy." + fileType.getDefaultExtension(); - final PsiFileFactory factory = PsiFileFactory.getInstance(searchContext.getProject()); + final PsiFileFactory factory = PsiFileFactory.getInstance(project); return dialect == null ? factory.createFileFromText(name, fileType, text, LocalTimeCounter.currentTime(), true, true) : diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionOptions.java b/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionOptions.java index bf7468993f48..900ae6b5cd36 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionOptions.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionOptions.java @@ -53,7 +53,7 @@ public class SSBasedInspectionOptions { return; } - configurationsChanged(dialog.getSearchContext()); + configurationsChanged(dialog.getProject()); } private static SearchDialog createDialog(final SearchDialogFactory searchDialogFactory) { @@ -67,9 +67,9 @@ public class SSBasedInspectionOptions { return SearchContext.buildFromDataContext(event.getDataContext()); } - public void configurationsChanged(final SearchContext searchContext) { + public void configurationsChanged(Project project) { ((MyListModel)myTemplatesList.getModel()).fireContentsChanged(); - DaemonCodeAnalyzer.getInstance(searchContext.getProject()).restart(); + DaemonCodeAnalyzer.getInstance(project).restart(); } public JPanel getComponent() { @@ -112,7 +112,10 @@ public class SSBasedInspectionOptions { }).setEditAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton button) { - performEditAction(); + final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(panel)); + if (project != null && !DumbService.isDumb(project)) { + performEditAction(project); + } } }).setEditActionUpdater(new AnActionButtonUpdater() { @Override @@ -123,12 +126,13 @@ public class SSBasedInspectionOptions { }).setRemoveAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton button) { - final SearchContext context = createSearchContext(); + final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(panel)); + if (project == null) return; for (Configuration configuration : myTemplatesList.getSelectedValuesList()) { myConfigurations.remove(configuration); - SSBasedInspectionCompiledPatternsCache.removeFromCache(configuration, context.getProject()); + SSBasedInspectionCompiledPatternsCache.removeFromCache(configuration, project); } - configurationsChanged(context); + configurationsChanged(project); } }).setRemoveActionUpdater(new AnActionButtonUpdater() { @Override @@ -156,7 +160,7 @@ public class SSBasedInspectionOptions { protected boolean onDoubleClick(MouseEvent e) { final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(panel)); if (project != null && !DumbService.isDumb(project)) { - performEditAction(); + performEditAction(project); } return true; } @@ -183,7 +187,7 @@ public class SSBasedInspectionOptions { } } - void performEditAction() { + void performEditAction(Project project) { final Configuration configuration = myTemplatesList.getSelectedValue(); if (configuration == null) return; @@ -216,9 +220,8 @@ public class SSBasedInspectionOptions { final Configuration newConfiguration = dialog.getConfiguration(); final int index = myConfigurations.indexOf(configuration); myConfigurations.set(index, newConfiguration); - final SearchContext context = dialog.getSearchContext(); - SSBasedInspectionCompiledPatternsCache.removeFromCache(configuration, context.getProject()); - configurationsChanged(context); + SSBasedInspectionCompiledPatternsCache.removeFromCache(configuration, project); + configurationsChanged(project); } private class MyListModel extends AbstractListModel { diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchAction.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchAction.java index c752f1684478..b37ae831497a 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchAction.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchAction.java @@ -1,12 +1,15 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.structuralsearch.plugin; import com.intellij.internal.statistic.UsageTrigger; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.registry.Registry; import com.intellij.psi.PsiDocumentManager; import com.intellij.structuralsearch.plugin.ui.Configuration; import com.intellij.structuralsearch.plugin.ui.SearchContext; import com.intellij.structuralsearch.plugin.ui.SearchDialog; +import com.intellij.structuralsearch.plugin.ui.StructuralSearchDialog; public class StructuralSearchAction extends AnAction { @@ -25,15 +28,22 @@ public class StructuralSearchAction extends AnAction { } PsiDocumentManager.getInstance(project).commitAllDocuments(); - //StructuralSearchPlugin.getInstance(searchContext.getProject()); - final SearchDialog searchDialog = new SearchDialog(searchContext); - - if (config!=null) { - searchDialog.setUseLastConfiguration(true); - searchDialog.setValuesFromConfig(config); + if (Registry.is("ssr.use.new.search.dialog")) { + final StructuralSearchDialog searchDialog = new StructuralSearchDialog(searchContext); + if (config != null) { + searchDialog.setUseLastConfiguration(true); + searchDialog.setValuesFromConfig(config); + } + searchDialog.show(); + } + else { + final SearchDialog searchDialog = new SearchDialog(searchContext); + if (config != null) { + searchDialog.setUseLastConfiguration(true); + searchDialog.setValuesFromConfig(config); + } + searchDialog.show(); } - - searchDialog.show(); } /** Updates the state of the action diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ScopePanel.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ScopePanel.java new file mode 100644 index 000000000000..63e4d3db4ccc --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ScopePanel.java @@ -0,0 +1,63 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.find.FindBundle; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.ui.JBUI; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; + +/** + * @author Bas Leijdekkers + */ +public class ScopePanel extends JPanel { + + private final JPanel myScopeDetailsPanel = new JPanel(new CardLayout()); + + public ScopePanel() { + super(new FlowLayout(FlowLayout.LEFT, 0, 0)); + myScopeDetailsPanel.setBorder(JBUI.Borders.emptyBottom(UIUtil.isUnderDefaultMacTheme() ? 0 : 3)); + final DefaultActionGroup scopeActionGroup = new DefaultActionGroup( + new ScopeToggleAction(FindBundle.message("find.popup.scope.project")), + new ScopeToggleAction(FindBundle.message("find.popup.scope.module")), + new ScopeToggleAction(FindBundle.message("find.popup.scope.directory")), + new ScopeToggleAction(FindBundle.message("find.popup.scope.scope")) + ); + ActionToolbarImpl toolbar = + (ActionToolbarImpl)ActionManager.getInstance().createActionToolbar("ScopePanel", scopeActionGroup, true); + toolbar.setForceMinimumSize(true); + toolbar.setLayoutPolicy(ActionToolbar.NOWRAP_LAYOUT_POLICY); + //toolbar.setMinimumButtonSize(ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE); + add(toolbar); + add(myScopeDetailsPanel); + } + + class ScopeToggleAction extends ToggleAction { + + public ScopeToggleAction(@Nullable String text) { + super(text, null, EmptyIcon.ICON_0); + //getTemplatePresentation().setHoveredIcon(EmptyIcon.ICON_0); + getTemplatePresentation().setDisabledIcon(EmptyIcon.ICON_0); + } + + @Override + public boolean isSelected(AnActionEvent e) { + return false; + } + + @Override + public void setSelected(AnActionEvent e, boolean state) { + + } + + @Override + public boolean displayTextInToolbar() { + return true; + } + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java index 5535a403e180..d671fa215d35 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java @@ -141,7 +141,7 @@ public class SearchDialog extends DialogWrapper { final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType); if (profile != null) { - editor = profile.createEditor(searchContext, fileType, dialect, text, useLastConfiguration); + editor = profile.createEditor(searchContext.getProject(), fileType, dialect, text); } } } @@ -747,8 +747,4 @@ public class SearchDialog extends DialogWrapper { protected String getHelpId() { return "find.structuredSearch"; } - - public SearchContext getSearchContext() { - return searchContext; - } } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/StructuralSearchDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/StructuralSearchDialog.java new file mode 100644 index 000000000000..022ec824668e --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/StructuralSearchDialog.java @@ -0,0 +1,676 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.codeInsight.template.impl.TemplateEditorUtil; +import com.intellij.find.FindBundle; +import com.intellij.find.FindSettings; +import com.intellij.icons.AllIcons; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.util.scopeChooser.ScopeChooserCombo; +import com.intellij.lang.Language; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.event.DocumentListener; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.ProjectManagerListener; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.SearchScope; +import com.intellij.structuralsearch.*; +import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler; +import com.intellij.structuralsearch.plugin.StructuralSearchPlugin; +import com.intellij.ui.EditorTextField; +import com.intellij.util.Alarm; +import com.intellij.util.ObjectUtils; +import com.intellij.util.SmartList; +import com.intellij.util.ui.LafIconLookup; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Class to show the user the request for search + */ +public class StructuralSearchDialog extends DialogWrapper { + protected SearchContext searchContext; + + // text for search + protected EditorTextField searchCriteriaEdit; + + // options of search scope + private ScopeChooserCombo myScopeChooserCombo; + + private JCheckBox recursiveMatching; + private JCheckBox caseSensitiveMatch; + + private FileTypeSelector fileTypes; + private JComboBox contexts; + + protected Configuration myConfiguration; + private JCheckBox openInNewTab; + private final Alarm myAlarm; + + public static final String USER_DEFINED = SSRBundle.message("new.template.defaultname"); + private boolean useLastConfiguration; + + @NonNls private FileType ourFtSearchVariant = StructuralSearchUtil.getDefaultFileType(); + private static Language ourDialect = null; + private static String ourContext = null; + + private final boolean myShowScopePanel; + private final boolean myRunFindActionOnClose; + private boolean myDoingOkAction; + + private String mySavedEditorText; + private JPanel myContentPanel; + private boolean myFilterEnabled = false; + + public StructuralSearchDialog(SearchContext searchContext) { + this(searchContext, true, true); + } + + public StructuralSearchDialog(SearchContext searchContext, boolean showScope, boolean runFindActionOnClose) { + super(searchContext.getProject(), true); + + if (showScope) setModal(false); + myShowScopePanel = showScope; + myRunFindActionOnClose = runFindActionOnClose; + this.searchContext = searchContext; + setTitle(getDefaultTitle()); + + if (runFindActionOnClose) { + setOKButtonText(FindBundle.message("find.dialog.find.button")); + } + + myConfiguration = createConfiguration(null); + + init(); + myAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, myDisposable); + ProjectManager.getInstance().addProjectManagerListener(searchContext.getProject(), new ProjectManagerListener() { + @Override + public void projectClosing(Project project) { + close(CANCEL_EXIT_CODE); + } + }); + } + + public void setUseLastConfiguration(boolean useLastConfiguration) { + this.useLastConfiguration = useLastConfiguration; + } + + void setSearchPattern(final Configuration config) { + setValuesFromConfig(config); + initiateValidation(); + } + + protected EditorTextField createEditor(final SearchContext searchContext, String text) { + final FileTypeInfo info = fileTypes.getSelectedItem(); + assert info != null; + final FileType fileType = info.getFileType(); + final Language dialect = info.getDialect(); + + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType); + assert profile != null; + final Document document = profile.createDocument(searchContext.getProject(), fileType, dialect, text); + document.addDocumentListener(new DocumentListener() { + @Override + public void documentChanged(final DocumentEvent event) { + initiateValidation(); + } + }); + + return new EditorTextField(document, searchContext.getProject(), fileType); + } + + void initiateValidation() { + myAlarm.cancelAllRequests(); + myAlarm.addRequest(() -> { + try { + final boolean valid = isValid(); + final boolean compiled = isCompiled(); + ApplicationManager.getApplication().invokeLater(() -> { + myFilterEnabled = compiled; + getOKAction().setEnabled(valid); + }); + } + catch (ProcessCanceledException e) { + throw e; + } + catch (RuntimeException e) { + Logger.getInstance(StructuralSearchDialog.class).error(e); + } + }, 250); + } + + private boolean isCompiled() { + try { + return PatternCompiler.compilePattern(getProject(), myConfiguration.getMatchOptions(), false) != null; + } catch (MalformedPatternException e) { + return false; + } + } + + void updateEditor() { + if (myContentPanel != null) { + if (searchCriteriaEdit != null) { + mySavedEditorText = searchCriteriaEdit.getText(); + myContentPanel.remove(searchCriteriaEdit); + } + searchCriteriaEdit = createEditor(searchContext, mySavedEditorText != null ? mySavedEditorText : ""); + myContentPanel.add(searchCriteriaEdit, BorderLayout.CENTER); + myContentPanel.revalidate(); + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(ourFtSearchVariant); + assert profile != null; + + searchCriteriaEdit.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + final Editor editor = searchCriteriaEdit.getEditor(); + if (editor == null) return; + TemplateEditorUtil.setHighlighter(editor, profile.getTemplateContextType()); + SubstitutionShortInfoHandler.install(editor); + editor.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, myConfiguration); + final Project project = searchContext.getProject(); + PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(searchCriteriaEdit.getDocument()); + if (psiFile != null) { + DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(psiFile, false); + } + searchCriteriaEdit.removePropertyChangeListener(this); + } + }); + + } + } + + void updateContexts() { + final FileType fileType = fileTypes.getSelectedFileType(); + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType); + + if (profile instanceof StructuralSearchProfileBase) { + final String[] contextNames = ((StructuralSearchProfileBase)profile).getContextNames(); + if (contextNames.length > 0) { + contexts.setModel(new DefaultComboBoxModel<>(contextNames)); + contexts.setSelectedItem(contextNames[0]); + contexts.setEnabled(true); + return; + } + } + contexts.setSelectedItem(null); + contexts.setEnabled(false); + } + + private void detectFileTypeAndDialect() { + final PsiFile file = searchContext.getFile(); + if (file != null) { + PsiElement context = null; + + if (searchContext.getEditor() != null) { + context = file.findElementAt(searchContext.getEditor().getCaretModel().getOffset()); + if (context != null) { + context = context.getParent(); + } + } + if (context == null) { + context = file; + } + + FileType detectedFileType = null; + + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(context); + if (profile != null) { + FileType fileType = profile.detectFileType(context); + if (fileType != null) { + detectedFileType = fileType; + } + } + + if (detectedFileType == null) { + for (FileType fileType : StructuralSearchUtil.getSuitableFileTypes()) { + if (fileType instanceof LanguageFileType && ((LanguageFileType)fileType).getLanguage().equals(context.getLanguage())) { + detectedFileType = fileType; + break; + } + } + } + + ourFtSearchVariant = detectedFileType != null ? + detectedFileType : + StructuralSearchUtil.getDefaultFileType(); + } + } + + protected boolean isRecursiveSearchEnabled() { + return myShowScopePanel; + } + + public void setValuesFromConfig(Configuration configuration) { + myConfiguration = createConfiguration(configuration); + final MatchOptions matchOptions = myConfiguration.getMatchOptions(); + + //UIUtil.setContent(searchCriteriaEdit.getEditor(), matchOptions.getSearchPattern()); + final Document document = searchCriteriaEdit.getDocument(); + CommandProcessor.getInstance().executeCommand(searchContext.getProject(), () -> { + ApplicationManager.getApplication().runWriteAction(() -> { + document.replaceString(0, document.getTextLength(), matchOptions.getSearchPattern()); + }); + }, null, null); + + recursiveMatching.setSelected(isRecursiveSearchEnabled() && matchOptions.isRecursiveSearch()); + caseSensitiveMatch.setSelected(matchOptions.isCaseSensitiveMatch()); + + fileTypes.setSelectedItem(matchOptions.getFileType(), matchOptions.getDialect()); + if (matchOptions.getPatternContext() != null) { + contexts.setSelectedItem(matchOptions.getPatternContext()); + } + final Editor editor = searchCriteriaEdit.getEditor(); + if (editor != null) { + editor.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, myConfiguration); + } + } + + public Configuration createConfiguration(Configuration template) { + return (template == null) ? new SearchConfiguration(USER_DEFINED, USER_DEFINED) : new SearchConfiguration(template); + } + + protected void setText(String text) { + setTextForEditor(text, searchCriteriaEdit); + } + + protected final void setTextForEditor(final String selection, EditorTextField editor) { + editor.setText(selection); + editor.selectAll(); + final Project project = searchContext.getProject(); + final Document document = editor.getDocument(); + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + documentManager.commitDocument(document); + final PsiFile file = documentManager.getPsiFile(document); + if (file == null) return; + + WriteCommandAction.writeCommandAction(project, file).run(() -> { + CodeStyleManager.getInstance(project).adjustLineIndent(file, new TextRange(0, document.getTextLength())); + }); + } + + protected void startSearching() { + new SearchCommand(myConfiguration, searchContext).startSearching(); + } + + protected String getDefaultTitle() { + return SSRBundle.message("structural.search.title"); + } + + @Override + protected JComponent createCenterPanel() { + myContentPanel = new JPanel(new BorderLayout()); + searchCriteriaEdit = createEditor(searchContext, mySavedEditorText != null ? mySavedEditorText : ""); + myContentPanel.add(BorderLayout.CENTER, searchCriteriaEdit); + + if (myShowScopePanel) { // todo remove me + myScopeChooserCombo = new ScopeChooserCombo( + searchContext.getProject(), + true, + false, + FindSettings.getInstance().getDefaultScopeName() + ); + Disposer.register(myDisposable, myScopeChooserCombo); + myScopeChooserCombo.getComboBox().addItemListener(e -> initiateValidation()); + } + else { + myScopeChooserCombo = null; + } + + JPanel scopePanel = new ScopePanel(); + myContentPanel.add(BorderLayout.SOUTH, scopePanel); + + updateEditor(); + return myContentPanel; + } + + @Nullable + @Override + protected JComponent createNorthPanel() { + final DefaultActionGroup historyActionGroup = new DefaultActionGroup(new AnAction(getShowHistoryIcon()) { + @Override + public void actionPerformed(AnActionEvent e) { + SelectTemplateDialog dialog = new SelectTemplateDialog(searchContext.getProject(), true, isReplaceDialog()); + if (!dialog.showAndGet()) { + return; + } + Configuration[] configurations = dialog.getSelectedConfigurations(); + if (configurations.length == 1) { + setSearchPattern(configurations[0]); + initiateValidation(); + } + } + }); + ActionToolbarImpl historyToolbar = + (ActionToolbarImpl)ActionManager.getInstance().createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, historyActionGroup, true); + historyToolbar.setLayoutPolicy(ActionToolbar.NOWRAP_LAYOUT_POLICY); + final JLabel label = new JLabel(SSRBundle.message("search.template")); + UIUtil.installCompleteMatchInfo(label, () -> myConfiguration); + + recursiveMatching = new JCheckBox(SSRBundle.message("recursive.matching.checkbox"), true); + recursiveMatching.setVisible(isRecursiveSearchEnabled()); + caseSensitiveMatch = new JCheckBox(FindBundle.message("find.popup.case.sensitive"), true); + final List types = new ArrayList<>(); + for (FileType fileType : StructuralSearchUtil.getSuitableFileTypes()) { + if (StructuralSearchUtil.getProfileByFileType(fileType) != null) { + types.add(fileType); + } + } + Collections.sort(types, (o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName())); + fileTypes = new FileTypeSelector(types); + fileTypes.setMinimumAndPreferredWidth(200); + fileTypes.setSelectedItem(ourFtSearchVariant, ourDialect); + fileTypes.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + updateContexts(); + updateEditor(); + initiateValidation(); + } + } + }); + final JLabel fileTypeLabel = new JLabel(SSRBundle.message("search.dialog.file.type.label")); + fileTypeLabel.setLabelFor(fileTypes); + contexts = new ComboBox<>(60); // todo remove me, dummy + final DefaultActionGroup templateActionGroup = new DefaultActionGroup( + new AnAction(SSRBundle.message("save.template.text.button")) { + + @Override + public void actionPerformed(AnActionEvent e) { + ConfigurationManager.getInstance(getProject()).showSaveTemplateAsDialog(getConfiguration()); + } + }, + new AnAction(SSRBundle.message("copy.existing.template.button")) { + @Override + public void actionPerformed(AnActionEvent e) { + SelectTemplateDialog dialog = new SelectTemplateDialog(searchContext.getProject(), false, isReplaceDialog()); + if (!dialog.showAndGet()) { + return; + } + Configuration[] configurations = dialog.getSelectedConfigurations(); + if (configurations.length == 1) { + setSearchPattern(configurations[0]); + } + } + } + ); + templateActionGroup.setPopup(true); + templateActionGroup.getTemplatePresentation().setIcon(AllIcons.General.GearPlain); + + final AnAction filterAction = new AnAction(AllIcons.General.Filter) { + @Override + public void actionPerformed(AnActionEvent e) { + new EditVarConstraintsDialog( + searchContext.getProject(), + myConfiguration, + getVariablesFromListeners(), + fileTypes.getSelectedFileType() + ).show(); + initiateValidation(); + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(myFilterEnabled); + } + }; + final DefaultActionGroup optionsActionGroup = new DefaultActionGroup(filterAction, templateActionGroup); + ActionToolbarImpl optionsToolbar = + (ActionToolbarImpl)ActionManager.getInstance().createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, optionsActionGroup, true); + optionsToolbar.setLayoutPolicy(ActionToolbar.NOWRAP_LAYOUT_POLICY); + optionsToolbar.setForceMinimumSize(true); + + final JPanel northPanel = new JPanel(null); + final GroupLayout layout = new GroupLayout(northPanel); + northPanel.setLayout(layout); + layout.setHonorsVisibility(true); + layout.setHorizontalGroup( + layout.createSequentialGroup() + .addComponent(historyToolbar, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(label) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, 20, Integer.MAX_VALUE) + .addComponent(recursiveMatching) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, 15, 15) + .addComponent(caseSensitiveMatch) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, 15, 15) + .addComponent(fileTypeLabel) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, 5, 5) + .addComponent(fileTypes) + .addComponent(optionsToolbar, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(historyToolbar) + .addComponent(label) + .addComponent(recursiveMatching) + .addComponent(caseSensitiveMatch) + .addComponent(fileTypeLabel) + .addComponent(fileTypes) + .addComponent(optionsToolbar) + ); + + detectFileTypeAndDialect(); + return northPanel; + } + + @Nullable + @Override + protected JPanel createSouthAdditionalPanel() { + if (!myRunFindActionOnClose) return null; + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 0)); + openInNewTab = new JCheckBox(SSRBundle.message("open.in.new.tab.checkbox")); + openInNewTab.setSelected(FindSettings.getInstance().isShowResultsInSeparateView()); + panel.add(openInNewTab, BorderLayout.EAST); + return panel; + } + + protected List getVariablesFromListeners() { + final Editor editor = searchCriteriaEdit.getEditor(); + if (editor == null) { + return new SmartList<>(); + } + return getVarsFrom(editor); + } + + protected static List getVarsFrom(Editor searchCriteriaEdit) { + SubstitutionShortInfoHandler handler = SubstitutionShortInfoHandler.retrieve(searchCriteriaEdit); + return (handler == null) ? new SmartList<>() : new ArrayList<>(handler.getVariables()); + } + + public final Project getProject() { + return searchContext.getProject(); + } + + protected boolean isReplaceDialog() { + return false; + } + + @Override + public void show() { + StructuralSearchPlugin.getInstance(getProject()).setDialogVisible(true); + + if (!useLastConfiguration) { + final Editor editor = searchContext.getEditor(); + boolean setSomeText = false; + + if (editor != null) { + final SelectionModel selectionModel = editor.getSelectionModel(); + + if (selectionModel.hasSelection()) { + setText(selectionModel.getSelectedText()); + setSomeText = true; + } + } + + if (!setSomeText) { + final Configuration configuration = ConfigurationManager.getInstance(getProject()).getMostRecentConfiguration(); + if (configuration != null) { + setValuesFromConfig(configuration); + } + } + } + + initiateValidation(); + + super.show(); + } + + @Override + public JComponent getPreferredFocusedComponent() { + return searchCriteriaEdit; + } + + @Override + protected void doOKAction() { + myDoingOkAction = true; + final boolean result = isValid(); + myDoingOkAction = false; + if (!result) return; + + myAlarm.cancelAllRequests(); + super.doOKAction(); + if (!myRunFindActionOnClose) return; + + final SearchScope selectedScope = myScopeChooserCombo.getSelectedScope(); + if (selectedScope == null) return; + + final FindSettings findSettings = FindSettings.getInstance(); + findSettings.setDefaultScopeName(selectedScope.getDisplayName()); + findSettings.setShowResultsInSeparateView(openInNewTab.isSelected()); + + try { + removeUnusedVariableConstraints(myConfiguration); + ConfigurationManager.getInstance(getProject()).addHistoryConfiguration(myConfiguration); + + startSearching(); + } + catch (MalformedPatternException ex) { + reportMessage(SSRBundle.message("this.pattern.is.malformed.message", ex.getMessage()), searchCriteriaEdit); + } + } + + private void removeUnusedVariableConstraints(Configuration configuration) { + final List variableNames = getVariablesFromListeners(); + variableNames.add(Configuration.CONTEXT_VAR_NAME); + configuration.getMatchOptions().retainVariableConstraints(variableNames); + } + + public Configuration getConfiguration() { + removeUnusedVariableConstraints(myConfiguration); + setValuesToConfig(myConfiguration); + return myConfiguration; + } + + protected boolean isValid() { + try { + Matcher.validate(searchContext.getProject(), getConfiguration().getMatchOptions()); + } + catch (MalformedPatternException ex) { + reportMessage(SSRBundle.message("this.pattern.is.malformed.message", + (ex.getMessage() != null) ? ex.getMessage() : ""), searchCriteriaEdit); + return false; + } + catch (UnsupportedPatternException ex) { + reportMessage(SSRBundle.message("this.pattern.is.unsupported.message", ex.getMessage()), searchCriteriaEdit); + return false; + } + catch (NoMatchFoundException e) { + reportMessage(e.getMessage(), searchCriteriaEdit); + return false; + } + reportMessage(null, searchCriteriaEdit); + return true; + } + + protected void reportMessage(String message, EditorTextField editor) { + com.intellij.util.ui.UIUtil.invokeLaterIfNeeded(() -> { + editor.putClientProperty("JComponent.outline", message == null ? null : "error"); + editor.setToolTipText(message); + editor.repaint(); + //status.setText(message); + //status.setToolTipText(message); + //status.revalidate(); + //statusText.setLabelFor(editor != null ? editor.getContentComponent() : null); + }); + } + + protected void setValuesToConfig(Configuration config) { + MatchOptions options = config.getMatchOptions(); + + if (myShowScopePanel) { + boolean searchWithinHierarchy = IdeBundle.message("scope.class.hierarchy").equals(myScopeChooserCombo.getSelectedScopeName()); + // We need to reset search within hierarchy scope during online validation since the scope works with user participation + options.setScope( + searchWithinHierarchy && !myDoingOkAction ? GlobalSearchScope.projectScope(getProject()) : myScopeChooserCombo.getSelectedScope()); + } + options.setRecursiveSearch(isRecursiveSearchEnabled() && recursiveMatching.isSelected()); + + final FileTypeInfo info = fileTypes.getSelectedItem(); + ourFtSearchVariant = info != null ? info.getFileType() : null; + ourDialect = info != null ? info.getDialect() : null; + ourContext = (String)contexts.getSelectedItem(); + FileType fileType = ourFtSearchVariant; + options.setFileType(fileType); + options.setDialect(ourDialect); + options.setPatternContext(ourContext); + + options.setSearchPattern(searchCriteriaEdit.getDocument().getText()); + options.setCaseSensitiveMatch(caseSensitiveMatch.isSelected()); + } + + @Override + protected String getDimensionServiceKey() { + return "#com.intellij.structuralsearch.plugin.ui.SearchDialog"; + } + + @Override + public void dispose() { + StructuralSearchPlugin.getInstance(getProject()).setDialogVisible(false); + myAlarm.cancelAllRequests(); + searchCriteriaEdit.removeNotify(); + super.dispose(); + } + + @Override + protected String getHelpId() { + return "find.structuredSearch"; + } + + private static Icon getShowHistoryIcon() { + return ObjectUtils.coalesce(UIManager.getIcon("TextField.darcula.searchWithHistory.icon"), LafIconLookup.getIcon("searchWithHistory")); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java index 5a54a5de7639..a8b01a06f690 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java @@ -168,12 +168,18 @@ public class UIUtil { @NotNull public static JComponent createCompleteMatchInfo(final Producer configurationProducer) { - final JLabel completeMatchInfo = new JLabel(AllIcons.RunConfigurations.Variables); - final Point location = completeMatchInfo.getLocation(); + return installCompleteMatchInfo(new JLabel(AllIcons.RunConfigurations.Variables), configurationProducer); + } + + @NotNull + public static JComponent installCompleteMatchInfo(JLabel completeMatchInfo, + Producer configurationProducer) { + final Rectangle bounds = completeMatchInfo.getBounds(); + final Point location = new Point(bounds.x + (bounds.width / 2) , bounds.y); final JLabel label = new JLabel(SSRBundle.message("complete.match.variable.tooltip.message", SSRBundle.message("no.constraints.specified.tooltip.message"))); final IdeTooltip tooltip = new IdeTooltip(completeMatchInfo, location, label); - tooltip.setPreferredPosition(Balloon.Position.atRight).setCalloutShift(6).setHint(true).setExplicitClose(true); + tooltip.setHint(true).setExplicitClose(true); completeMatchInfo.addMouseListener(new MouseAdapter() { @Override @@ -189,6 +195,10 @@ public class UIUtil { } label.setText(SSRBundle.message("complete.match.variable.tooltip.message", SubstitutionShortInfoHandler.getShortParamString(constraint))); + tooltip.setPreferredPosition(Balloon.Position.below); + final Rectangle bounds = completeMatchInfo.getBounds(); + final Point location = new Point(bounds.x + (bounds.width / 2) , bounds.y + bounds.height); + tooltip.setPoint(location); IdeTooltipManager.getInstance().show(tooltip, true); configuration.setCurrentVariableName(Configuration.CONTEXT_VAR_NAME); } diff --git a/platform/structuralsearch/source/messages/SSRBundle.properties b/platform/structuralsearch/source/messages/SSRBundle.properties index eb45856e0b24..2aa95dd82fea 100644 --- a/platform/structuralsearch/source/messages/SSRBundle.properties +++ b/platform/structuralsearch/source/messages/SSRBundle.properties @@ -14,13 +14,13 @@ save.template.text.button=S&ave Template... ssdialog.options.group.border=Options new.template.defaultname=user defined -recursive.matching.checkbox=&Recursive matching +recursive.matching.checkbox=&Recursive edit.variables.button=E&dit Variables... history.button=&History... copy.existing.template.button=E&xisting Templates... -open.in.new.tab.checkbox=Open in new &tab +open.in.new.tab.checkbox=Open results in new &tab search.dialog.scope.label=&Scope search.dialog.file.type.label=File t&ype: diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 1170dec12976..455ae4eb3874 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -1511,6 +1511,8 @@ goto.action.skip.tophits.and.options.description=Skip top hits and options in go ssr.use.regexp.to.specify.type=false ssr.use.regexp.to.specify.type.description=Revert back to using regexp to specify expression types +ssr.use.new.search.dialog=false +ssr.use.new.search.dialog.description=Enables new redesigned Structural Search dialog dsm.show.cycle.count.in.tooltip=false dsm.show.cycle.count.in.tooltip.description=Shows number of cycles found in the tooltip of the Toggle Cycles toolbar button after some calculation time