SSR: new Structural Search dialog WIP

This commit is contained in:
Bas Leijdekkers
2018-06-08 11:11:17 +02:00
parent b8e67781c7
commit d18ce78257
10 changed files with 819 additions and 88 deletions
@@ -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<? extends TemplateContextType> getTemplateContextTypeClass() {
@@ -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) :
@@ -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<Configuration> {
@@ -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
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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<String> 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<FileType> 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<String> getVariablesFromListeners() {
final Editor editor = searchCriteriaEdit.getEditor();
if (editor == null) {
return new SmartList<>();
}
return getVarsFrom(editor);
}
protected static List<String> 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<String> 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"));
}
}
@@ -168,12 +168,18 @@ public class UIUtil {
@NotNull
public static JComponent createCompleteMatchInfo(final Producer<Configuration> 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<? extends Configuration> 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);
}
@@ -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:
@@ -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