mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-13 15:52:01 +07:00
[regexp] Custom RegExp inspections
Co-authored-by: Louis Vignier <louis.vignier@jetbrains.com> Co-authored-by: Bas Leijdekkers <bas@jetbrains.com> GitOrigin-RevId: 025ad2acefe111b0e8d2111446a8190d6d28b965
This commit is contained in:
committed by
intellij-monorepo-bot
parent
fe76e924fb
commit
839640ae3c
@@ -15,9 +15,9 @@
|
||||
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
|
||||
<orderEntry type="library" name="Jaxen" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.xml.psi" />
|
||||
<orderEntry type="library" name="fastutil-min" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core.ui" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.jdom" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -36,6 +36,9 @@
|
||||
<categoryKey>inspection.group.name.regexp</categoryKey>
|
||||
</intentionAction>
|
||||
|
||||
<localInspection shortName="CustomRegExpInspection" enabledByDefault="true" level="NON_SWITCHABLE_WARNING" dynamicGroup="true"
|
||||
bundle="messages.RegExpBundle" groupKey="inspection.group.name.regexp" key="inspection.name.custom.regexp"
|
||||
implementationClass="org.intellij.lang.regexp.inspection.custom.CustomRegExpInspection" language=""/>
|
||||
<localInspection language="RegExp" shortName="RegExpRepeatedSpace" enabledByDefault="true" level="WARNING"
|
||||
bundle="messages.RegExpBundle" groupKey="inspection.group.name.regexp" key="inspection.name.consecutive.spaces"
|
||||
implementationClass="org.intellij.lang.regexp.inspection.RepeatedSpaceInspection"/>
|
||||
@@ -84,4 +87,10 @@
|
||||
bundle="messages.RegExpBundle" groupKey="inspection.group.name.regexp" key="inspection.name.redundant.digit.class.element"
|
||||
implementationClass="org.intellij.lang.regexp.inspection.RegExpRedundantClassElementInspection"/>
|
||||
</extensions>
|
||||
|
||||
<actions resource-bundle="messages.RegExpBundle">
|
||||
<action id="open.regexp.dialog"
|
||||
class="org.intellij.lang.regexp.inspection.custom.OpenRegExpDialogAction"
|
||||
icon="AllIcons.Actions.Regex"/>
|
||||
</actions>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
Custom Regex Inspection
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +1,9 @@
|
||||
action.add.pattern.text=Add pattern\u2026
|
||||
action.add.regexp.replace.template.text=Add RegExp Replace Template\u2026
|
||||
action.add.regexp.search.template.text=Add RegExp Search Template\u2026
|
||||
action.open.regexp.dialog.text=Open RegExp Dialog\u2026
|
||||
button.enable.replace=Enable Replace
|
||||
button.search.only=Search Only
|
||||
checker.sample.text=Sample Text
|
||||
color.settings.bad.character=Bad character
|
||||
color.settings.brace=Brace
|
||||
@@ -18,8 +24,13 @@ color.settings.quantifier=Quantifier
|
||||
color.settings.quote.character=Quote escape
|
||||
color.settings.redundant.escape.sequence=Redundant escape sequence
|
||||
color.settings.title.regexp=RegExp
|
||||
dialog.message.name.must.not.be.empty=Name must not be empty
|
||||
dialog.message.suppress.id.already.in.use.by.another.inspection=Suppress ID ''{0}'' is already in use by another inspection
|
||||
dialog.message.suppress.id.must.match.regex.za.z=Suppress ID must match regex [a-zA-Z_0-9.-]+
|
||||
dialog.title.custom.regexp.inspection=Custom RegExp Inspection
|
||||
doc.property.block.stands.for.0=Property block stands for {0}
|
||||
doc.property.block.stands.for.characters.not.matching.0=Property block stands for characters not matching {0}
|
||||
edit.metadata.button=Edit Metadata...
|
||||
error.0.repetition.not.allowed.inside.lookbehind={0} repetition not allowed inside lookbehind
|
||||
error.alternation.alternatives.needs.to.have.the.same.length.inside.lookbehind=Alternation alternatives need to have the same length inside lookbehind
|
||||
error.atomic.groups.are.not.supported.in.this.regex.dialect=Atomic groups are not supported in this regex dialect
|
||||
@@ -28,10 +39,10 @@ error.conditional.group.reference.not.allowed.inside.lookbehind=Conditional grou
|
||||
error.conditionals.are.not.supported.in.this.regex.dialect=Conditionals are not supported in this regex dialect
|
||||
error.dangling.metacharacter=Dangling quantifier ''{0}''
|
||||
error.dangling.opening.bracket=Unexpected start of quantifier '{'
|
||||
error.define.subpattern.contains.more.than.one.branch=DEFINE subpattern contains more than one branch
|
||||
error.embedded.comments.are.not.supported.in.this.regex.dialect=Embedded comments are not supported in this regex dialect
|
||||
error.empty.group=Empty group
|
||||
error.group.reference.is.nested.into.the.named.group.it.refers.to=Group reference is nested into the named group it refers to
|
||||
error.define.subpattern.contains.more.than.one.branch=DEFINE subpattern contains more than one branch
|
||||
error.group.reference.not.allowed.inside.lookbehind=Group reference not allowed inside lookbehind
|
||||
error.group.with.name.0.already.defined=Group with name ''{0}'' already defined
|
||||
error.illegal.character.range.to.from=Illegal character range (to < from)
|
||||
@@ -69,27 +80,28 @@ inspection.group.name.regexp=RegExp
|
||||
inspection.name.anonymous.group.or.numeric.back.reference=Anonymous capturing group or numeric back reference
|
||||
inspection.name.begin.or.end.anchor.in.unexpected.position=Begin or end anchor in unexpected position
|
||||
inspection.name.consecutive.spaces=Consecutive spaces
|
||||
inspection.name.custom.regexp=Custom RegExp inspection
|
||||
inspection.name.duplicate.branch.in.alternation=Duplicate branch in alternation
|
||||
inspection.name.duplicate.character.in.class=Duplicate character in character class
|
||||
inspection.name.empty.branch.in.alternation=Empty branch in alternation
|
||||
inspection.name.escaped.meta.character=Escaped meta character
|
||||
inspection.name.octal.escape=Octal escape
|
||||
inspection.name.redundant.character.escape=Redundant character escape
|
||||
inspection.name.redundant.digit.class.element=Redundant '\\d', '[:digit:]', or '\\D' class elements
|
||||
inspection.name.redundant.nested.character.class=Redundant nested character class
|
||||
inspection.name.simplifiable.expression=Regular expression can be simplified
|
||||
inspection.name.single.character.alternation=Single character alternation
|
||||
inspection.name.suspicious.backref=Suspicious back reference
|
||||
inspection.name.unnecessary.non.capturing.group=Unnecessary non-capturing group
|
||||
inspection.name.redundant.digit.class.element=Redundant '\\d', '[:digit:]', or '\\D' class elements
|
||||
inspection.option.ignore.escaped.closing.brackets=Ignore escaped closing brackets '}' and ']'
|
||||
inspection.quick.fix.remove.duplicate.0.from.character.class=Remove duplicate ''{0}'' from character class
|
||||
inspection.quick.fix.remove.duplicate.branch=Remove duplicate branch
|
||||
inspection.quick.fix.remove.duplicate.element.from.character.class=Remove duplicate element from character class
|
||||
inspection.quick.fix.remove.empty.branch=Remove empty branch
|
||||
inspection.quick.fix.remove.redundant.escape=Remove redundant escape
|
||||
inspection.quick.fix.remove.unnecessary.non.capturing.group=Unwrap unnecessary non-capturing group
|
||||
inspection.quick.fix.remove.redundant.0.class.element=Remove redundant ''{0}''
|
||||
inspection.quick.fix.remove.redundant.class.element=Remove redundant class element
|
||||
inspection.quick.fix.remove.redundant.escape=Remove redundant escape
|
||||
inspection.quick.fix.remove.unnecessary.non.capturing.group=Unwrap unnecessary non-capturing group
|
||||
inspection.quick.fix.replace.alternation.with.character.class=Replace alternation with character class
|
||||
inspection.quick.fix.replace.redundant.character.class.with.contents=Replace redundant character class with contents
|
||||
inspection.quick.fix.replace.with.character.inside.class=Replace with character inside class
|
||||
@@ -109,13 +121,21 @@ inspection.warning.numeric.back.reference=Numeric back reference
|
||||
inspection.warning.octal.escape.code.ref.code.in.regexp=Octal escape <code>#ref</code> in RegExp
|
||||
inspection.warning.potential.exponential.backtracking=Potential exponential backtracking
|
||||
inspection.warning.redundant.character.escape.0.in.regexp=Redundant character escape <code>{0}</code> in RegExp
|
||||
inspection.warning.redundant.nested.character.class=Redundant nested character class
|
||||
inspection.warning.redundant.class.element=Redundant ''{0}'' in RegExp
|
||||
inspection.warning.redundant.nested.character.class=Redundant nested character class
|
||||
inspection.warning.single.character.alternation.in.regexp=Single character alternation in RegExp
|
||||
inspection.warning.unnecessary.non.capturing.group=Unnecessary non-capturing group <code>{0}</code>
|
||||
intention.family.name.replace=Replace
|
||||
intention.name.check.regexp=Check RegExp
|
||||
label.any=Any
|
||||
label.description=Description:
|
||||
label.inspection.name=Inspection name:
|
||||
label.problem.tool.tip=Problem tool tip (use macro #ref to insert highlighted code):
|
||||
label.regexp.patterns=RegExp patterns:
|
||||
label.regexp=&RegExp:
|
||||
label.sample=&Sample:
|
||||
label.suppress.id=Suppress ID:
|
||||
no.description.provided.description=No description provided
|
||||
parse.error.category.shorthand.not.allowed.in.this.regular.expression.dialect=Category shorthand not allowed in this regular expression dialect
|
||||
parse.error.character.class.expected=Character class expected
|
||||
parse.error.character.expected=Character expected
|
||||
@@ -125,8 +145,8 @@ parse.error.closing.brace.or.number.expected='}' or number expected
|
||||
parse.error.comma.expected=',' expected
|
||||
parse.error.empty.property=Empty property
|
||||
parse.error.group.name.expected=Group name expected
|
||||
parse.error.group.number.expected=Group number expected
|
||||
parse.error.group.name.or.number.expected=Group name or number expected
|
||||
parse.error.group.number.expected=Group number expected
|
||||
parse.error.illegal.category.shorthand=Illegal category shorthand
|
||||
parse.error.illegal.character.range=Illegal character range
|
||||
parse.error.negating.a.property.not.allowed.in.this.regular.expression.dialect=Negating a property not allowed in this regular expression dialect
|
||||
@@ -148,6 +168,10 @@ parse.error.unclosed.posix.bracket.expression=Unclosed POSIX bracket expression
|
||||
parse.error.unclosed.property=Unclosed property
|
||||
parse.error.unicode.character.name.expected=Unicode character name expected
|
||||
parse.error.unmatched.closing.bracket=Unmatched closing ''{0}''
|
||||
regexp.dialog.language=&Language:
|
||||
regexp.dialog.replace.template=Replace RegExp:
|
||||
regexp.dialog.search.template=Search RegExp:
|
||||
regexp.dialog.title=RegExp
|
||||
surrounder.atomic.group.pattern=Atomic Group (?:pattern)
|
||||
surrounder.capturing.group.pattern=Capturing Group (pattern)
|
||||
surrounder.negative.lookahead.pattern=Negative Lookahead (?!pattern)
|
||||
@@ -163,4 +187,4 @@ tooltip.more.input.expected=More example input expected
|
||||
tooltip.no.match=Expression and example do not match
|
||||
tooltip.pattern.is.too.complex=Regular expression pattern is too complex
|
||||
warning.duplicate.character.0.inside.character.class=Duplicate character ''{0}'' inside character class
|
||||
warning.duplicate.predefined.character.class.0.inside.character.class=Duplicate predefined character class ''{0}'' inside character class
|
||||
warning.duplicate.predefined.character.class.0.inside.character.class=Duplicate predefined character class ''{0}'' inside character class
|
||||
@@ -0,0 +1,313 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.intellij.lang.regexp.inspection.custom;
|
||||
|
||||
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
|
||||
import com.intellij.codeInspection.InspectionProfile;
|
||||
import com.intellij.codeInspection.InspectionsBundle;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileModifiableModel;
|
||||
import com.intellij.codeInspection.ex.InspectionToolWrapper;
|
||||
import com.intellij.find.FindModel;
|
||||
import com.intellij.ide.DataManager;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.profile.codeInspection.ui.SingleInspectionProfilePanel;
|
||||
import com.intellij.ui.AnActionButton;
|
||||
import com.intellij.ui.DoubleClickListener;
|
||||
import com.intellij.ui.LayeredIcon;
|
||||
import com.intellij.ui.ToolbarDecorator;
|
||||
import com.intellij.ui.awt.RelativePoint;
|
||||
import com.intellij.ui.components.JBList;
|
||||
import com.intellij.util.ui.FormBuilder;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.intellij.lang.regexp.RegExpBundle;
|
||||
import org.intellij.lang.regexp.inspection.custom.RegExpInspectionConfiguration.InspectionPattern;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public class CustomRegExpFakeInspection extends LocalInspectionTool {
|
||||
|
||||
@NotNull private final RegExpInspectionConfiguration myConfiguration;
|
||||
|
||||
public CustomRegExpFakeInspection(@NotNull RegExpInspectionConfiguration configuration) {
|
||||
if (configuration.getPatterns().isEmpty()) throw new IllegalArgumentException();
|
||||
|
||||
myConfiguration = configuration;
|
||||
}
|
||||
|
||||
public @NotNull RegExpInspectionConfiguration getConfiguration() {
|
||||
return myConfiguration;
|
||||
}
|
||||
|
||||
@Nls(capitalization = Nls.Capitalization.Sentence)
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return myConfiguration.getName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return myConfiguration.getUuid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getID() {
|
||||
final HighlightDisplayKey key = HighlightDisplayKey.find(getShortName());
|
||||
if (key != null) {
|
||||
return key.getID(); // to avoid using a new suppress id before it is registered.
|
||||
}
|
||||
final String suppressId = myConfiguration.getSuppressId();
|
||||
return !StringUtil.isEmpty(suppressId) ? suppressId : CustomRegExpInspection.SHORT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getAlternativeID() {
|
||||
return CustomRegExpInspection.SHORT_NAME;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getMainToolId() {
|
||||
return CustomRegExpInspection.SHORT_NAME;
|
||||
}
|
||||
|
||||
@Nls(capitalization = Nls.Capitalization.Sentence)
|
||||
@Override
|
||||
public @NotNull String getGroupDisplayName() {
|
||||
return "RegExp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nls(capitalization = Nls.Capitalization.Sentence) String @NotNull [] getGroupPath() {
|
||||
return new String[] {InspectionsBundle.message("group.names.user.defined"), getGroupDisplayName()};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getStaticDescription() {
|
||||
final String description = myConfiguration.getDescription();
|
||||
if (StringUtil.isEmpty(description)) {
|
||||
return RegExpBundle.message("no.description.provided.description");
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull JComponent createOptionsPanel() {
|
||||
final MyListModel model = new MyListModel();
|
||||
final JButton button = new JButton(RegExpBundle.message("edit.metadata.button"));
|
||||
button.addActionListener(__ -> performEditMetaData(button));
|
||||
|
||||
final JList<InspectionPattern> list = new JBList<>(model);
|
||||
list.setVisibleRowCount(3);
|
||||
list.setCellRenderer(new RegExpInspectionConfigurationCellRenderer());
|
||||
final JPanel listPanel = ToolbarDecorator.createDecorator(list)
|
||||
.setAddAction(b -> performAdd(list, b))
|
||||
.setAddActionName(RegExpBundle.message("action.add.pattern.text"))
|
||||
.setAddIcon(LayeredIcon.ADD_WITH_DROPDOWN)
|
||||
.setRemoveAction(b -> performRemove(list))
|
||||
.setRemoveActionUpdater(e -> list.getSelectedValuesList().size() < list.getModel().getSize())
|
||||
.setEditAction(b -> performEdit(list))
|
||||
.setMoveUpAction(b -> performMove(list, true))
|
||||
.setMoveDownActionUpdater(e -> list.getSelectedValuesList().size() == 1)
|
||||
.setMoveDownAction(b -> performMove(list, false))
|
||||
.setMoveDownActionUpdater(e -> list.getSelectedValuesList().size() == 1)
|
||||
.createPanel();
|
||||
|
||||
new DoubleClickListener() {
|
||||
@Override
|
||||
protected boolean onDoubleClick(@NotNull MouseEvent e) {
|
||||
performEdit(list);
|
||||
return true;
|
||||
}
|
||||
}.installOn(list);
|
||||
|
||||
final JPanel panel = new FormBuilder()
|
||||
.addComponent(button)
|
||||
.addVerticalGap(UIUtil.DEFAULT_VGAP)
|
||||
.addLabeledComponentFillVertically(RegExpBundle.message("label.regexp.patterns"), listPanel)
|
||||
.getPanel();
|
||||
panel.setBorder(JBUI.Borders.emptyTop(10));
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void performEditMetaData(@NotNull Component context) {
|
||||
final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(context));
|
||||
final InspectionProfileModifiableModel profile = getInspectionProfile(context);
|
||||
if (profile == null) {
|
||||
return;
|
||||
}
|
||||
final CustomRegExpInspection inspection = getRegExpInspection(profile);
|
||||
final MetaDataDialog dialog = new MetaDataDialog(project, inspection, myConfiguration, false);
|
||||
if (!dialog.showAndGet()) {
|
||||
return;
|
||||
}
|
||||
inspection.updateConfiguration(myConfiguration);
|
||||
profile.setModified(true);
|
||||
profile.getProfileManager().fireProfileChanged(profile);
|
||||
}
|
||||
|
||||
private void performMove(JList<InspectionPattern> list, boolean up) {
|
||||
final MyListModel model = (MyListModel)list.getModel();
|
||||
final int index = list.getSelectedIndex();
|
||||
final List<InspectionPattern> patterns = myConfiguration.getPatterns();
|
||||
final int newIndex = up ? index - 1 : index + 1;
|
||||
Collections.swap(patterns, index, newIndex);
|
||||
model.fireContentsChanged(list);
|
||||
list.setSelectedIndex(newIndex);
|
||||
//saveChangesToProfile(list);
|
||||
}
|
||||
|
||||
private void performAdd(JList<InspectionPattern> list, @NotNull AnActionButton b) {
|
||||
final AnAction[] children = new AnAction[]{new AddTemplateAction(list, false), new AddTemplateAction(list, true)};
|
||||
final RelativePoint point = b.getPreferredPopupPoint();
|
||||
JBPopupFactory.getInstance().createActionGroupPopup(null, new DefaultActionGroup(children),
|
||||
DataManager.getInstance().getDataContext(b.getContextComponent()),
|
||||
JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true).show(point);
|
||||
}
|
||||
|
||||
private void performRemove(JList<InspectionPattern> list) {
|
||||
final List<InspectionPattern> patterns = myConfiguration.getPatterns();
|
||||
for (InspectionPattern configuration : list.getSelectedValuesList()) {
|
||||
patterns.remove(configuration);
|
||||
}
|
||||
final int size = patterns.size();
|
||||
final int maxIndex = list.getMaxSelectionIndex();
|
||||
if (maxIndex != list.getMinSelectionIndex()) {
|
||||
list.setSelectedIndex(maxIndex);
|
||||
}
|
||||
((MyListModel)list.getModel()).fireContentsChanged(list);
|
||||
if (list.getSelectedIndex() >= size) {
|
||||
list.setSelectedIndex(size - 1);
|
||||
}
|
||||
final int index = list.getSelectedIndex();
|
||||
list.scrollRectToVisible(list.getCellBounds(index, index));
|
||||
saveChangesToProfile(list);
|
||||
}
|
||||
|
||||
private void performEdit(JList<InspectionPattern> list) {
|
||||
final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(list));
|
||||
if (project == null) return;
|
||||
final int index = list.getSelectedIndex();
|
||||
if (index < 0) return;
|
||||
final List<InspectionPattern> patterns = myConfiguration.getPatterns();
|
||||
final InspectionPattern configuration = patterns.get(index);
|
||||
if (configuration == null) return;
|
||||
final RegExpDialog dialog = new RegExpDialog(project, true, configuration);
|
||||
if (!dialog.showAndGet()) return;
|
||||
final InspectionPattern newConfiguration = dialog.getPattern();
|
||||
patterns.set(index, newConfiguration);
|
||||
((MyListModel)list.getModel()).fireContentsChanged(list);
|
||||
saveChangesToProfile(list);
|
||||
}
|
||||
|
||||
private void saveChangesToProfile(JList<InspectionPattern> list) {
|
||||
final InspectionProfileModifiableModel profile = getInspectionProfile(list);
|
||||
if (profile == null) return;
|
||||
final CustomRegExpInspection inspection = getRegExpInspection(profile);
|
||||
inspection.updateConfiguration(myConfiguration);
|
||||
profile.setModified(true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static CustomRegExpInspection getRegExpInspection(@NotNull InspectionProfile profile) {
|
||||
final InspectionToolWrapper<?, ?> wrapper = profile.getInspectionTool(CustomRegExpInspection.SHORT_NAME, (Project)null);
|
||||
assert wrapper != null;
|
||||
return (CustomRegExpInspection)wrapper.getTool();
|
||||
}
|
||||
|
||||
private static InspectionProfileModifiableModel getInspectionProfile(@NotNull Component c) {
|
||||
final SingleInspectionProfilePanel panel = UIUtil.uiParents(c, true).filter(SingleInspectionProfilePanel.class).first();
|
||||
if (panel == null) return null;
|
||||
return panel.getProfile();
|
||||
}
|
||||
|
||||
private final class AddTemplateAction extends DumbAwareAction {
|
||||
private final JList<InspectionPattern> myList;
|
||||
private final boolean myReplace;
|
||||
|
||||
private AddTemplateAction(JList<InspectionPattern> list, boolean replace) {
|
||||
super(replace
|
||||
? RegExpBundle.message("action.add.regexp.replace.template.text")
|
||||
: RegExpBundle.message("action.add.regexp.search.template.text"));
|
||||
myList = list;
|
||||
myReplace = replace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
final Project project = e.getData(CommonDataKeys.PROJECT);
|
||||
assert project != null;
|
||||
|
||||
FileType fileType = null;
|
||||
FindModel.SearchContext context = FindModel.SearchContext.ANY;
|
||||
if (myList.getModel().getSize() > 0) {
|
||||
final InspectionPattern pattern = myList.getModel().getElementAt(0);
|
||||
fileType = pattern.fileType();
|
||||
context = pattern.searchContext();
|
||||
}
|
||||
final String replace = myReplace ? "" : null;
|
||||
final InspectionPattern defaultPattern = new InspectionPattern("", fileType, context, replace);
|
||||
|
||||
final RegExpDialog dialog = new RegExpDialog(project, true, defaultPattern);
|
||||
if (!dialog.showAndGet()) return;
|
||||
|
||||
final InspectionPattern configuration = dialog.getPattern();
|
||||
final List<InspectionPattern> patterns = myConfiguration.getPatterns();
|
||||
patterns.add(configuration);
|
||||
((MyListModel)myList.getModel()).fireContentsChanged(myList);
|
||||
saveChangesToProfile(myList);
|
||||
}
|
||||
}
|
||||
|
||||
private class MyListModel extends AbstractListModel<InspectionPattern> {
|
||||
@Override
|
||||
public int getSize() {
|
||||
return myConfiguration.getPatterns().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InspectionPattern getElementAt(int index) {
|
||||
return myConfiguration.getPatterns().get(index);
|
||||
}
|
||||
|
||||
public void fireContentsChanged(Object source) {
|
||||
fireContentsChanged(source, -1, -1);
|
||||
}
|
||||
|
||||
public void swap(int first, int second) {
|
||||
if (second == -1) return;
|
||||
final List<InspectionPattern> patterns = myConfiguration.getPatterns();
|
||||
final InspectionPattern one = patterns.get(first);
|
||||
final InspectionPattern two = patterns.get(second);
|
||||
patterns.set(second, one);
|
||||
patterns.set(first, two);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.intellij.lang.regexp.inspection.custom;
|
||||
|
||||
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
|
||||
import com.intellij.codeInsight.daemon.impl.ProblemDescriptorWithReporterName;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.ex.*;
|
||||
import com.intellij.find.FindManager;
|
||||
import com.intellij.find.FindModel;
|
||||
import com.intellij.find.FindResult;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.profile.codeInspection.InspectionProfileManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.intellij.lang.regexp.RegExpBundle;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public class CustomRegExpInspection extends LocalInspectionTool implements DynamicGroupTool {
|
||||
|
||||
public static final String SHORT_NAME = "CustomRegExpInspection";
|
||||
public final List<RegExpInspectionConfiguration> myConfigurations = new SmartList<>();
|
||||
private InspectionProfileImpl mySessionProfile;
|
||||
|
||||
public CustomRegExpInspection() {
|
||||
/*
|
||||
final FileType javaFileType = FileTypeManager.getInstance().getStdFileType("JAVA");
|
||||
final RegExpInspectionConfiguration one = new RegExpInspectionConfiguration("No spaces within parentheses");
|
||||
one.patterns.add(new RegExpInspectionConfiguration.InspectionPattern("(\\()\\s+|\\s+(\\))", null, FindModel.SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS, "$1"));
|
||||
one.suppressId = "NoSpaces";
|
||||
one.description = "We don't like spaces within parentheses in our code style";
|
||||
myConfigurations.add(one);
|
||||
|
||||
final RegExpInspectionConfiguration two = new RegExpInspectionConfiguration("No more than one empty line in Java");
|
||||
two.patterns.add(new RegExpInspectionConfiguration.InspectionPattern("\\n\\n\\n+", javaFileType, FindModel.SearchContext.EXCEPT_STRING_LITERALS, "\n\n"));
|
||||
two.suppressId = "EmptyLines";
|
||||
two.description = "One empty line should be enough for everybody";
|
||||
myConfigurations.add(two);
|
||||
|
||||
final RegExpInspectionConfiguration three = new RegExpInspectionConfiguration("Trailing whitespace");
|
||||
three.patterns.add(new RegExpInspectionConfiguration.InspectionPattern(" +\\n", PlainTextFileType.INSTANCE, FindModel.SearchContext.ANY, ""));
|
||||
three.patterns.add(new RegExpInspectionConfiguration.InspectionPattern("\t+\\n", PlainTextFileType.INSTANCE, FindModel.SearchContext.ANY, ""));
|
||||
three.description = "Trailing whitespace is unnecessary";
|
||||
myConfigurations.add(three);
|
||||
|
||||
final RegExpInspectionConfiguration four = new RegExpInspectionConfiguration("Multiple spaces in Java");
|
||||
four.patterns.add(new RegExpInspectionConfiguration.InspectionPattern("(?<=\\S) {2,}", javaFileType, FindModel.SearchContext.EXCEPT_COMMENTS, " "));
|
||||
four.description = "Double spaced";
|
||||
myConfigurations.add(four);
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(@NotNull GlobalInspectionContext context) {
|
||||
super.initialize(context);
|
||||
mySessionProfile = ((GlobalInspectionContextBase)context).getCurrentProfile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(@NotNull Project project) {
|
||||
super.cleanup(project);
|
||||
mySessionProfile = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean runForWholeFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProblemDescriptor @Nullable [] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) {
|
||||
final Document document = file.getViewProvider().getDocument();
|
||||
final CharSequence text = document.getCharsSequence();
|
||||
final FindManager findManager = FindManager.getInstance(file.getProject());
|
||||
final Project project = manager.getProject();
|
||||
final InspectionProfileImpl profile =
|
||||
(mySessionProfile != null && !isOnTheFly) ? mySessionProfile : InspectionProfileManager.getInstance(project).getCurrentProfile();
|
||||
final List<ProblemDescriptor> descriptors = new SmartList<>();
|
||||
for (RegExpInspectionConfiguration configuration : myConfigurations) {
|
||||
final String uuid = configuration.getUuid();
|
||||
final ToolsImpl tools = profile.getToolsOrNull(uuid, project);
|
||||
if (tools != null && !tools.isEnabled(file)) {
|
||||
continue;
|
||||
}
|
||||
addInspectionToProfile(project, profile, configuration); // hack
|
||||
register(configuration);
|
||||
|
||||
for (RegExpInspectionConfiguration.InspectionPattern pattern : configuration.getPatterns()) {
|
||||
if (file.getFileType() != pattern.fileType()) continue;
|
||||
final FindModel model = new FindModel();
|
||||
model.setRegularExpressions(true);
|
||||
model.setStringToFind(pattern.regExp());
|
||||
final String replacement = pattern.replacement();
|
||||
if (replacement != null) {
|
||||
model.setStringToReplace(replacement);
|
||||
}
|
||||
model.setSearchContext(pattern.searchContext());
|
||||
FindResult result = findManager.findString(text, 0, model);
|
||||
while (result.isStringFound()) {
|
||||
final TextRange range = new TextRange(result.getStartOffset(), result.getEndOffset());
|
||||
PsiElement element = file.findElementAt(result.getStartOffset());
|
||||
assert element != null;
|
||||
while (!element.getTextRange().contains(range)) {
|
||||
element = element.getParent();
|
||||
}
|
||||
final TextRange elementRange = element.getTextRange();
|
||||
final int start = result.getStartOffset() - elementRange.getStartOffset();
|
||||
final TextRange warningRange = new TextRange(start, result.getEndOffset() - result.getStartOffset() + start);
|
||||
final String problemDescriptor = StringUtil.defaultIfEmpty(configuration.getProblemDescriptor(), configuration.getName());
|
||||
final ProblemDescriptor descriptor =
|
||||
manager.createProblemDescriptor(element, warningRange, problemDescriptor,
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, isOnTheFly,
|
||||
new CustomRegExpQuickFix(findManager, model, text, result));
|
||||
descriptors.add(new ProblemDescriptorWithReporterName((ProblemDescriptorBase)descriptor, uuid));
|
||||
result = findManager.findString(text, result.getEndOffset(), model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return descriptors.toArray(ProblemDescriptor.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
public static void register(@NotNull RegExpInspectionConfiguration configuration) {
|
||||
// modify from single (event) thread, to prevent race conditions.
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
final String shortName = configuration.getUuid();
|
||||
final HighlightDisplayKey key = HighlightDisplayKey.find(shortName);
|
||||
if (key != null) {
|
||||
if (!isMetaDataChanged(configuration, key)) return;
|
||||
HighlightDisplayKey.unregister(shortName);
|
||||
}
|
||||
final String suppressId = configuration.getSuppressId();
|
||||
final String name = configuration.getName();
|
||||
if (suppressId == null) {
|
||||
HighlightDisplayKey.register(shortName, () -> name, SHORT_NAME);
|
||||
}
|
||||
else {
|
||||
HighlightDisplayKey.register(shortName, () -> name, suppressId, SHORT_NAME);
|
||||
}
|
||||
}, ModalityState.NON_MODAL);
|
||||
}
|
||||
|
||||
private static boolean isMetaDataChanged(@NotNull RegExpInspectionConfiguration configuration, @NotNull HighlightDisplayKey key) {
|
||||
if (StringUtil.isEmpty(configuration.getSuppressId())) {
|
||||
if (!SHORT_NAME.equals(key.getID())) return true;
|
||||
}
|
||||
else if (!configuration.getSuppressId().equals(key.getID())) return true;
|
||||
return !configuration.getName().equals(HighlightDisplayKey.getDisplayNameByKey(key));
|
||||
}
|
||||
|
||||
public static void addInspectionToProfile(@NotNull Project project,
|
||||
@NotNull InspectionProfileImpl profile,
|
||||
@NotNull RegExpInspectionConfiguration configuration) {
|
||||
final String shortName = configuration.getUuid();
|
||||
final InspectionToolWrapper<?, ?> toolWrapper = profile.getInspectionTool(shortName, project);
|
||||
if (toolWrapper != null) {
|
||||
// already added
|
||||
return;
|
||||
}
|
||||
final CustomRegExpInspectionToolWrapper wrapped = new CustomRegExpInspectionToolWrapper(configuration);
|
||||
profile.addTool(project, wrapped, null);
|
||||
profile.setToolEnabled(shortName, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<LocalInspectionToolWrapper> getChildren() {
|
||||
return ContainerUtil.map(myConfigurations, CustomRegExpInspectionToolWrapper::new);
|
||||
}
|
||||
|
||||
public void addConfiguration(RegExpInspectionConfiguration configuration) {
|
||||
myConfigurations.add(configuration);
|
||||
}
|
||||
|
||||
public void updateConfiguration(RegExpInspectionConfiguration configuration) {
|
||||
myConfigurations.remove(configuration);
|
||||
myConfigurations.add(configuration);
|
||||
}
|
||||
|
||||
public void removeConfigurationWithUuid(String uuid) {
|
||||
myConfigurations.removeIf(c -> c.getUuid().equals(uuid));
|
||||
}
|
||||
|
||||
public List<RegExpInspectionConfiguration> getConfigurations() {
|
||||
return myConfigurations;
|
||||
}
|
||||
|
||||
private static class CustomRegExpQuickFix implements LocalQuickFix {
|
||||
private final int myStartOffset;
|
||||
private final int myEndOffset;
|
||||
private final String myReplacement;
|
||||
private final String myOriginal;
|
||||
|
||||
private CustomRegExpQuickFix(FindManager findManager, FindModel findModel, CharSequence text, FindResult result) {
|
||||
myStartOffset = result.getStartOffset();
|
||||
myEndOffset = result.getEndOffset();
|
||||
myOriginal = text.subSequence(myStartOffset, myEndOffset).toString();
|
||||
try {
|
||||
myReplacement = findManager.getStringToReplace(myOriginal, findModel, myStartOffset, text);
|
||||
}
|
||||
catch (FindManager.MalformedReplacementStringException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return CommonQuickFixBundle.message("fix.replace.with.x", myReplacement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return RegExpBundle.message("intention.family.name.replace");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiFile file = descriptor.getPsiElement().getContainingFile();
|
||||
final Document document = file.getViewProvider().getDocument();
|
||||
if (myOriginal.equals(document.getText(TextRange.create(myStartOffset, myEndOffset)))) {
|
||||
document.replaceString(myStartOffset, myEndOffset, myReplacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.intellij.lang.regexp.inspection.custom;
|
||||
|
||||
import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public class CustomRegExpInspectionToolWrapper extends LocalInspectionToolWrapper {
|
||||
|
||||
CustomRegExpInspectionToolWrapper(RegExpInspectionConfiguration configuration) {
|
||||
super(new CustomRegExpFakeInspection(configuration));
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return myTool.getShortName().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj.getClass() == CustomRegExpInspectionToolWrapper.class &&
|
||||
((CustomRegExpInspectionToolWrapper)obj).myTool.getShortName().equals(myTool.getShortName());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public LocalInspectionToolWrapper createCopy() {
|
||||
final CustomRegExpFakeInspection inspection = (CustomRegExpFakeInspection)getTool();
|
||||
RegExpInspectionConfiguration configuration = inspection.getConfiguration();
|
||||
return new CustomRegExpInspectionToolWrapper(configuration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.intellij.lang.regexp.inspection.custom;
|
||||
|
||||
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.openapi.editor.colors.EditorFontType;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.fileTypes.FileTypeManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.ValidationInfo;
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.ui.EditorTextField;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.ui.FormBuilder;
|
||||
import org.intellij.lang.regexp.RegExpBundle;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MetaDataDialog extends DialogWrapper {
|
||||
private final Pattern mySuppressIdPattern = Pattern.compile(LocalInspectionTool.VALID_ID_PATTERN);
|
||||
|
||||
private final CustomRegExpInspection myInspection;
|
||||
@NotNull private final RegExpInspectionConfiguration myConfiguration;
|
||||
private final boolean myNewInspection;
|
||||
private final JTextField myNameTextField;
|
||||
private final JTextField myProblemDescriptorTextField;
|
||||
private final EditorTextField myDescriptionTextArea;
|
||||
private final JTextField mySuppressIdTextField;
|
||||
|
||||
public MetaDataDialog(Project project, @NotNull CustomRegExpInspection inspection, @NotNull RegExpInspectionConfiguration configuration, boolean newInspection) {
|
||||
super((Project)null);
|
||||
myInspection = inspection;
|
||||
|
||||
myConfiguration = configuration;
|
||||
myNewInspection = newInspection;
|
||||
myNameTextField = new JTextField(configuration.getName());
|
||||
myProblemDescriptorTextField = new JTextField(configuration.getProblemDescriptor());
|
||||
final FileType htmlFileType = FileTypeManager.getInstance().getStdFileType("HTML");
|
||||
myDescriptionTextArea = new EditorTextField(ObjectUtils.notNull(configuration.getDescription(), ""), project, htmlFileType);
|
||||
myDescriptionTextArea.setOneLineMode(false);
|
||||
myDescriptionTextArea.setFont(EditorFontType.getGlobalPlainFont());
|
||||
myDescriptionTextArea.setPreferredSize(new Dimension(375, 125));
|
||||
myDescriptionTextArea.setMinimumSize(new Dimension(200, 50));
|
||||
mySuppressIdTextField = new JTextField(configuration.getSuppressId());
|
||||
setTitle(RegExpBundle.message("dialog.title.custom.regexp.inspection"));
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myNameTextField;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull java.util.List<ValidationInfo> doValidateAll() {
|
||||
final List<ValidationInfo> warnings = new SmartList<>();
|
||||
final List<RegExpInspectionConfiguration> configurations = myInspection.getConfigurations();
|
||||
final String name = getName();
|
||||
if (StringUtil.isEmpty(name)) {
|
||||
warnings.add(new ValidationInfo(RegExpBundle.message("dialog.message.name.must.not.be.empty"), myNameTextField));
|
||||
}
|
||||
final String suppressId = getSuppressId();
|
||||
if (!StringUtil.isEmpty(suppressId)) {
|
||||
if (!mySuppressIdPattern.matcher(suppressId).matches()) {
|
||||
warnings.add(new ValidationInfo(RegExpBundle.message("dialog.message.suppress.id.must.match.regex.za.z"), mySuppressIdTextField));
|
||||
}
|
||||
else {
|
||||
final HighlightDisplayKey key = HighlightDisplayKey.findById(suppressId);
|
||||
if (key != null && key != HighlightDisplayKey.find(myConfiguration.getUuid())) {
|
||||
warnings.add(new ValidationInfo(
|
||||
RegExpBundle.message("dialog.message.suppress.id.already.in.use.by.another.inspection", suppressId), mySuppressIdTextField));
|
||||
}
|
||||
else {
|
||||
for (RegExpInspectionConfiguration configuration : configurations) {
|
||||
if (suppressId.equals(configuration.getSuppressId()) && !myConfiguration.getUuid().equals(configuration.getUuid())) {
|
||||
warnings.add(new ValidationInfo(
|
||||
RegExpBundle.message("dialog.message.suppress.id.already.in.use.by.another.inspection", suppressId), mySuppressIdTextField));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
super.doOKAction();
|
||||
if (getOKAction().isEnabled()) {
|
||||
myConfiguration.name = getName();
|
||||
myConfiguration.description = getDescription();
|
||||
myConfiguration.suppressId = getSuppressId();
|
||||
myConfiguration.problemDescriptor = getProblemDescriptor();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return new FormBuilder()
|
||||
.addLabeledComponent(RegExpBundle.message("label.inspection.name"), myNameTextField, true)
|
||||
.addLabeledComponent(RegExpBundle.message("label.problem.tool.tip"), myProblemDescriptorTextField, true)
|
||||
.addLabeledComponentFillVertically(RegExpBundle.message("label.description"), myDescriptionTextArea)
|
||||
.addLabeledComponent(RegExpBundle.message("label.suppress.id"), mySuppressIdTextField)
|
||||
.getPanel();
|
||||
}
|
||||
|
||||
public @NlsSafe String getName() {
|
||||
return myNameTextField.getText().trim();
|
||||
}
|
||||
|
||||
public @NlsSafe @Nullable String getDescription() {
|
||||
return convertEmptyToNull(myDescriptionTextArea.getText());
|
||||
}
|
||||
|
||||
public @NlsSafe @Nullable String getSuppressId() {
|
||||
return convertEmptyToNull(mySuppressIdTextField.getText());
|
||||
}
|
||||
|
||||
public @NlsSafe @Nullable String getProblemDescriptor() {
|
||||
return convertEmptyToNull(myProblemDescriptorTextField.getText());
|
||||
}
|
||||
|
||||
private static String convertEmptyToNull(String s) {
|
||||
return StringUtil.isEmpty(s.trim()) ? null : s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.intellij.lang.regexp.inspection.custom;
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class OpenRegExpDialogAction extends DumbAwareAction {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
new RegExpDialog(e.getProject(), false, null).show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.intellij.lang.regexp.inspection.custom;
|
||||
|
||||
import com.intellij.find.FindBundle
|
||||
import com.intellij.find.FindModel
|
||||
import com.intellij.find.impl.FindInProjectUtil
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.actionSystem.*
|
||||
import com.intellij.openapi.actionSystem.impl.ActionButton
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.editor.colors.EditorFontType
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import com.intellij.openapi.fileTypes.FileTypeManager
|
||||
import com.intellij.openapi.fileTypes.UnknownFileType
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.openapi.ui.popup.ListPopup
|
||||
import com.intellij.ui.EditorTextField
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.OnePixelSplitter
|
||||
import com.intellij.ui.SimpleListCellRenderer
|
||||
import com.intellij.ui.dsl.builder.IntelliJSpacingConfiguration
|
||||
import com.intellij.ui.dsl.builder.RightGap
|
||||
import com.intellij.ui.dsl.builder.Row
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.ui.dsl.gridLayout.Gaps
|
||||
import com.intellij.ui.dsl.gridLayout.HorizontalAlign
|
||||
import com.intellij.ui.dsl.gridLayout.VerticalAlign
|
||||
import com.intellij.util.ui.JBUI
|
||||
import org.intellij.lang.regexp.RegExpBundle
|
||||
import org.intellij.lang.regexp.RegExpFileType
|
||||
import org.intellij.lang.regexp.inspection.custom.RegExpInspectionConfiguration.InspectionPattern
|
||||
import java.awt.Dimension
|
||||
import java.awt.event.FocusAdapter
|
||||
import java.awt.event.FocusEvent
|
||||
import javax.swing.JButton
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JList
|
||||
import javax.swing.border.CompoundBorder
|
||||
|
||||
class RegExpDialog(val project: Project?, val editConfiguration: Boolean, defaultPattern: InspectionPattern? = null) : DialogWrapper(project, true) {
|
||||
private var searchContext: FindModel.SearchContext = FindModel.SearchContext.ANY
|
||||
private var replace: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
replaceLabel.isEnabled = value
|
||||
replaceRow.enabled(value)
|
||||
replaceButton.text = when (value) {
|
||||
true -> RegExpBundle.message("button.search.only")
|
||||
false -> RegExpBundle.message("button.enable.replace")
|
||||
}
|
||||
if (!editConfiguration) {
|
||||
setOKButtonText(when (value) {
|
||||
true -> FindBundle.message("find.replace.command")
|
||||
false -> FindBundle.message("find.dialog.find.button")
|
||||
})
|
||||
}
|
||||
}
|
||||
private var replaceEditorFocusedLast = false
|
||||
|
||||
private lateinit var fileCombo: ComboBox<FileType>
|
||||
private lateinit var filterButton: ActionButton
|
||||
private lateinit var searchEditor: EditorTextField
|
||||
private lateinit var replaceLabel: JLabel
|
||||
private lateinit var replaceButton: JButton
|
||||
private lateinit var replaceRow: Row
|
||||
private lateinit var replaceEditor: EditorTextField
|
||||
private lateinit var splitter: OnePixelSplitter
|
||||
|
||||
val pattern: InspectionPattern
|
||||
get() = InspectionPattern(
|
||||
searchEditor.text,
|
||||
fileCombo.item,
|
||||
searchContext,
|
||||
if (replace) replaceEditor.text else null
|
||||
)
|
||||
|
||||
init {
|
||||
title = RegExpBundle.message("regexp.dialog.title")
|
||||
init()
|
||||
if (!editConfiguration) {
|
||||
isModal = false
|
||||
}
|
||||
|
||||
defaultPattern?.let { pattern ->
|
||||
searchEditor.text = pattern.regExp
|
||||
searchContext = pattern.searchContext
|
||||
fileCombo.item = pattern.fileType() ?: UnknownFileType.INSTANCE
|
||||
pattern.replacement?.let { replaceEditor.text = it }
|
||||
}
|
||||
replace = defaultPattern?.replacement != null
|
||||
}
|
||||
|
||||
private fun createEditorsPanel(): JComponent = panel {
|
||||
val intelliJSpacingConfiguration = IntelliJSpacingConfiguration()
|
||||
|
||||
panel {
|
||||
row {
|
||||
label(RegExpBundle.message("regexp.dialog.search.template"))
|
||||
.resizableColumn()
|
||||
.horizontalAlign(HorizontalAlign.FILL)
|
||||
val fileTypes = mutableListOf(UnknownFileType.INSTANCE)
|
||||
fileTypes.addAll(
|
||||
FileTypeManager.getInstance().registeredFileTypes.filterNotNull()
|
||||
.sortedBy { it.displayName }
|
||||
.filter { it != UnknownFileType.INSTANCE }
|
||||
)
|
||||
fileCombo = comboBox(fileTypes, SimpleTypeRenderer())
|
||||
.label(RegExpBundle.message("regexp.dialog.language"))
|
||||
.applyToComponent {
|
||||
preferredSize.width = 150
|
||||
isSwingPopup = false
|
||||
}
|
||||
.gap(RightGap.SMALL)
|
||||
.component
|
||||
filterButton = actionButton(MyFilterAction())
|
||||
.component
|
||||
}
|
||||
}.customize(Gaps(0, intelliJSpacingConfiguration.horizontalIndent, 0, intelliJSpacingConfiguration.horizontalIndent))
|
||||
|
||||
row {
|
||||
searchEditor = cell(createEditor(true))
|
||||
.resizableColumn()
|
||||
.horizontalAlign(HorizontalAlign.FILL)
|
||||
.verticalAlign(VerticalAlign.FILL)
|
||||
.applyToComponent { addFocusListener(object : FocusAdapter() {
|
||||
override fun focusGained(e: FocusEvent?) {
|
||||
replaceEditorFocusedLast = false
|
||||
}
|
||||
}) }
|
||||
.component
|
||||
}.resizableRow()
|
||||
|
||||
panel {
|
||||
row {
|
||||
replaceLabel = label(RegExpBundle.message("regexp.dialog.replace.template"))
|
||||
.resizableColumn()
|
||||
.horizontalAlign(HorizontalAlign.FILL)
|
||||
.component
|
||||
replaceButton = button(if (replace) RegExpBundle.message("button.search.only") else RegExpBundle.message("button.enable.replace")) {
|
||||
replace = !replace
|
||||
}.component
|
||||
}
|
||||
}.customize(Gaps(10, intelliJSpacingConfiguration.horizontalIndent, 0, intelliJSpacingConfiguration.horizontalIndent))
|
||||
|
||||
replaceRow = row {
|
||||
replaceEditor = cell(createEditor(false))
|
||||
.resizableColumn()
|
||||
.horizontalAlign(HorizontalAlign.FILL)
|
||||
.verticalAlign(VerticalAlign.FILL).applyToComponent { addFocusListener(object : FocusAdapter() {
|
||||
override fun focusGained(e: FocusEvent?) {
|
||||
replaceEditorFocusedLast = true
|
||||
}
|
||||
}) }
|
||||
.component
|
||||
}.resizableRow()
|
||||
}
|
||||
|
||||
override fun createCenterPanel(): JComponent {
|
||||
splitter = OnePixelSplitter()
|
||||
splitter.firstComponent = RegExpSampleTree { insertSample(it) }.panel
|
||||
splitter.secondComponent = createEditorsPanel()
|
||||
searchEditor.grabFocus()
|
||||
return splitter
|
||||
}
|
||||
|
||||
private fun insertSample(sample: RegExpSample) {
|
||||
val editor = if (replaceEditorFocusedLast) replaceEditor else searchEditor
|
||||
val caret = editor.caretModel.allCarets.firstOrNull() ?: return
|
||||
val insertOffset = caret.offset
|
||||
val sampleOffset = if (sample.caretOffset == -1) sample.sample.length else sample.caretOffset
|
||||
when (caret.hasSelection()) {
|
||||
true -> {
|
||||
val selection = caret.selectedText!!
|
||||
val start = editor.text.dropLast(editor.text.length - caret.selectionStart)
|
||||
val end = editor.text.drop(caret.selectionEnd)
|
||||
val sampleStart = sample.sample.dropLast(sample.sample.length - sampleOffset)
|
||||
val sampleEnd = sample.sample.drop(sampleOffset)
|
||||
editor.text = start + sampleStart + selection + sampleEnd + end
|
||||
caret.moveToOffset(caret.selectionStart + sampleStart.length + selection.length)
|
||||
}
|
||||
false -> {
|
||||
val start = editor.text.dropLast(editor.text.length - insertOffset)
|
||||
val end = editor.text.drop(insertOffset)
|
||||
editor.text = start + sample.sample + end
|
||||
caret.moveToOffset(caret.offset + sampleOffset)
|
||||
}
|
||||
}
|
||||
editor.grabFocus()
|
||||
}
|
||||
|
||||
override fun getStyle(): DialogStyle = DialogStyle.COMPACT
|
||||
|
||||
fun createEditor(search: Boolean): EditorTextField {
|
||||
val document = EditorFactory.getInstance().createDocument("")
|
||||
return MyEditorTextField(document, search).apply {
|
||||
font = EditorFontType.getGlobalPlainFont()
|
||||
preferredSize = Dimension(550, 100)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class MyEditorTextField(document: Document, val search: Boolean) : EditorTextField(document, project, RegExpFileType.INSTANCE, false, false) {
|
||||
override fun createEditor(): EditorEx {
|
||||
return super.createEditor().apply {
|
||||
setHorizontalScrollbarVisible(true)
|
||||
setVerticalScrollbarVisible(true)
|
||||
val outerBorder = JBUI.Borders.customLine(JBColor.border(), 1, 0, if (search) 1 else 0, 0)
|
||||
scrollPane.border = CompoundBorder(
|
||||
outerBorder,
|
||||
JBUI.Borders.empty(6, 8, 6, 8)
|
||||
)
|
||||
isEmbeddedIntoDialogWrapper = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class SimpleTypeRenderer : SimpleListCellRenderer<FileType?>() {
|
||||
override fun customize(list: JList<out FileType?>, value: FileType?, index: Int, selected: Boolean, hasFocus: Boolean) {
|
||||
if (value == null) return
|
||||
when (value) {
|
||||
UnknownFileType.INSTANCE -> {
|
||||
text = RegExpBundle.message("label.any")
|
||||
icon = AllIcons.FileTypes.Any_type
|
||||
}
|
||||
else -> {
|
||||
text = value.displayName
|
||||
icon = value.icon ?: AllIcons.FileTypes.Text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class MyFilterAction : DumbAwareAction(FindBundle.messagePointer("find.popup.show.filter.popup"), Presentation.NULL_STRING, AllIcons.General.Filter) {
|
||||
val myGroup: ActionGroup
|
||||
var listPopup: ListPopup? = null
|
||||
init {
|
||||
ActionManager.getInstance().getKeyboardShortcut("ShowFilterPopup")?.let {
|
||||
shortcutSet = CustomShortcutSet(it)
|
||||
}
|
||||
myGroup = DefaultActionGroup().apply {
|
||||
FindModel.SearchContext.values().forEach { add(MyToggleAction(it, this@MyFilterAction)) }
|
||||
isPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
listPopup = JBPopupFactory.getInstance().createActionGroupPopup(null, myGroup, e.dataContext, false, null, 10)
|
||||
listPopup?.showUnderneathOf(filterButton)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class MyToggleAction(val context: FindModel.SearchContext, val action: MyFilterAction) : ToggleAction(FindInProjectUtil.getPresentableName(context)), DumbAware {
|
||||
override fun isSelected(e: AnActionEvent): Boolean {
|
||||
return searchContext == context
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread {
|
||||
return ActionUpdateThread.BGT
|
||||
}
|
||||
|
||||
override fun setSelected(e: AnActionEvent, state: Boolean) {
|
||||
searchContext = context
|
||||
action.listPopup?.closeOk(null)
|
||||
filterButton.repaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.intellij.lang.regexp.inspection.custom;
|
||||
|
||||
import com.intellij.find.FindModel;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.fileTypes.FileTypeManager;
|
||||
import com.intellij.openapi.fileTypes.UnknownFileType;
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.util.SmartList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public class RegExpInspectionConfiguration implements Comparable<RegExpInspectionConfiguration> {
|
||||
|
||||
public List<InspectionPattern> patterns;
|
||||
public @NotNull String name;
|
||||
public String description;
|
||||
public String uuid;
|
||||
public String suppressId;
|
||||
public String problemDescriptor;
|
||||
|
||||
public RegExpInspectionConfiguration(@NotNull String name) {
|
||||
this.name = name;
|
||||
uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8)).toString();
|
||||
patterns = new SmartList<>();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public RegExpInspectionConfiguration() {}
|
||||
|
||||
public RegExpInspectionConfiguration(RegExpInspectionConfiguration other) {
|
||||
patterns = new SmartList<>(other.patterns);
|
||||
name = other.name;
|
||||
description = other.description;
|
||||
uuid = other.uuid;
|
||||
suppressId = other.suppressId;
|
||||
problemDescriptor = other.problemDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
RegExpInspectionConfiguration that = (RegExpInspectionConfiguration)o;
|
||||
return uuid.equals(that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getUuid().hashCode();
|
||||
}
|
||||
|
||||
public RegExpInspectionConfiguration copy() {
|
||||
return new RegExpInspectionConfiguration(this);
|
||||
}
|
||||
|
||||
public List<InspectionPattern> getPatterns() {
|
||||
return patterns;
|
||||
}
|
||||
|
||||
public @NlsSafe @NotNull String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public @NlsSafe String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public @NlsSafe String getSuppressId() {
|
||||
return suppressId;
|
||||
}
|
||||
|
||||
public @NlsSafe String getProblemDescriptor() {
|
||||
return problemDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull RegExpInspectionConfiguration o) {
|
||||
int result = name.compareToIgnoreCase(o.name);
|
||||
if (result == 0) result = uuid.compareTo(o.uuid);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static final class InspectionPattern {
|
||||
public static final InspectionPattern EMPTY_REPLACE_PATTERN = new InspectionPattern("", null, FindModel.SearchContext.ANY, "");
|
||||
public @NotNull String regExp;
|
||||
private @Nullable FileType fileType;
|
||||
public @Nullable String _fileType;
|
||||
public @NotNull FindModel.SearchContext searchContext;
|
||||
public @Nullable String replacement;
|
||||
|
||||
public InspectionPattern(
|
||||
@NotNull String regExp,
|
||||
@Nullable FileType fileType,
|
||||
@NotNull FindModel.SearchContext searchContext,
|
||||
@Nullable String replacement
|
||||
) {
|
||||
this.regExp = regExp;
|
||||
this.fileType = fileType;
|
||||
if (this.fileType != null) {
|
||||
_fileType = this.fileType.getName();
|
||||
}
|
||||
this.searchContext = searchContext;
|
||||
this.replacement = replacement;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public InspectionPattern() {
|
||||
}
|
||||
|
||||
public @NlsSafe String regExp() { return regExp; }
|
||||
|
||||
public @Nullable FileType fileType() {
|
||||
if (fileType == null && _fileType != null) {
|
||||
fileType = FileTypeManager.getInstance().findFileTypeByName(_fileType);
|
||||
if (fileType == null) {
|
||||
fileType = UnknownFileType.INSTANCE;
|
||||
}
|
||||
}
|
||||
return fileType;
|
||||
}
|
||||
|
||||
public FindModel.SearchContext searchContext() { return searchContext; }
|
||||
|
||||
public @NlsSafe @Nullable String replacement() { return replacement; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || obj.getClass() != getClass()) return false;
|
||||
var that = (InspectionPattern)obj;
|
||||
return Objects.equals(regExp, that.regExp) &&
|
||||
Objects.equals(fileType, that.fileType) &&
|
||||
Objects.equals(searchContext, that.searchContext) &&
|
||||
Objects.equals(replacement, that.replacement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(regExp, fileType, searchContext, replacement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InspectionPattern[" +
|
||||
"regExp=" + regExp + ", " +
|
||||
"fileType=" + fileType + ", " +
|
||||
"searchContext=" + searchContext + ", " +
|
||||
"replacement=" + replacement + ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.intellij.lang.regexp.inspection.custom;
|
||||
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.ui.ColoredListCellRenderer;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import static com.intellij.openapi.util.text.StringUtil.shortenTextWithEllipsis;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public class RegExpInspectionConfigurationCellRenderer extends ColoredListCellRenderer<RegExpInspectionConfiguration.InspectionPattern> {
|
||||
|
||||
@Override
|
||||
protected void customizeCellRenderer(@NotNull JList<? extends RegExpInspectionConfiguration.InspectionPattern> list,
|
||||
RegExpInspectionConfiguration.InspectionPattern value,
|
||||
int index,
|
||||
boolean selected,
|
||||
boolean hasFocus) {
|
||||
final FileType fileType = value.fileType();
|
||||
setIcon((fileType == null) ? AllIcons.FileTypes.Any_type : fileType.getIcon());
|
||||
final String regExp = value.regExp();
|
||||
final String replacement = value.replacement();
|
||||
append("'", SimpleTextAttributes.GRAY_ATTRIBUTES);
|
||||
if (replacement != null) {
|
||||
append(shortenTextWithEllipsis(regExp, 49, 0, true), SimpleTextAttributes.REGULAR_ATTRIBUTES);
|
||||
append("' ⇨ '", SimpleTextAttributes.GRAY_ATTRIBUTES);
|
||||
append(shortenTextWithEllipsis(replacement, 49, 0, true), SimpleTextAttributes.REGULAR_ATTRIBUTES);
|
||||
}
|
||||
else {
|
||||
append(shortenTextWithEllipsis(regExp, 100, 0, true), SimpleTextAttributes.REGULAR_ATTRIBUTES);
|
||||
}
|
||||
append("'", SimpleTextAttributes.GRAY_ATTRIBUTES);
|
||||
setEnabled(list.isEnabled());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.intellij.lang.regexp.inspection.custom
|
||||
|
||||
import com.intellij.ide.DefaultTreeExpander
|
||||
import com.intellij.ide.ui.search.SearchUtil
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.ui.*
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
import com.intellij.ui.tree.TreePathUtil
|
||||
import com.intellij.ui.treeStructure.Tree
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import java.awt.event.MouseEvent
|
||||
import java.util.function.Function
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JTree
|
||||
import javax.swing.tree.*
|
||||
|
||||
data class RegExpSample(
|
||||
@NlsSafe val name: String,
|
||||
@NlsSafe val sample: String,
|
||||
val caretOffset: Int,
|
||||
@NlsSafe val category: String,
|
||||
val userDefined: Boolean,
|
||||
)
|
||||
|
||||
class RegExpSampleTree(val doubleClickConsumer: (RegExpSample) -> Unit) {
|
||||
val treeModel: TreeModel
|
||||
val tree: Tree
|
||||
|
||||
val panel: JComponent
|
||||
get() = JBScrollPane(tree).apply { border = JBUI.Borders.empty() }
|
||||
|
||||
init {
|
||||
val root = DefaultMutableTreeNode(null)
|
||||
treeModel = DefaultTreeModel(root)
|
||||
tree = Tree(treeModel).apply {
|
||||
isRootVisible = false
|
||||
showsRootHandles = true
|
||||
dragEnabled = false
|
||||
isEditable = false
|
||||
selectionModel.selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION
|
||||
|
||||
val speedSearch = TreeSpeedSearch(
|
||||
this,
|
||||
false,
|
||||
Function { treePath: TreePath ->
|
||||
val treeNode = treePath.lastPathComponent as DefaultMutableTreeNode
|
||||
val userObject = treeNode.userObject
|
||||
if (userObject is RegExpSample) userObject.name else userObject.toString()
|
||||
}
|
||||
)
|
||||
cellRenderer = MyTreeCellRenderer(speedSearch)
|
||||
}
|
||||
|
||||
val groups = DefaultMutableTreeNode("Groups")
|
||||
groups.add(DefaultMutableTreeNode(RegExpSample("Capturing", "()", 1, "Groups", false)))
|
||||
groups.add(DefaultMutableTreeNode(RegExpSample("Named", "(?<name>)", 8, "Groups", false)))
|
||||
groups.add(DefaultMutableTreeNode(RegExpSample("Non-capturing", "(?:)", 3, "Groups", false)))
|
||||
groups.add(DefaultMutableTreeNode(RegExpSample("Lookahead", "(?=)", 3, "Groups", false)))
|
||||
groups.add(DefaultMutableTreeNode(RegExpSample("Negative lookahead", "(?!)", 3, "Groups", false)))
|
||||
groups.add(DefaultMutableTreeNode(RegExpSample("Lookbehind", "(?<=)", 4, "Groups", false)))
|
||||
groups.add(DefaultMutableTreeNode(RegExpSample("Negative lookbehind", "(?<!)", 4, "Groups", false)))
|
||||
groups.add(DefaultMutableTreeNode(RegExpSample("Atomic", "(?>)", 3, "Groups", false)))
|
||||
groups.add(DefaultMutableTreeNode(RegExpSample("Comment", "(?#)", 3, "Groups", false)))
|
||||
root.add(groups)
|
||||
|
||||
val anchors = DefaultMutableTreeNode("Anchors")
|
||||
anchors.add(DefaultMutableTreeNode(RegExpSample("String or line start", "^", -1, "Anchors", false)))
|
||||
anchors.add(DefaultMutableTreeNode(RegExpSample("String start", "\\A", -1, "Anchors", false)))
|
||||
anchors.add(DefaultMutableTreeNode(RegExpSample("String or line end", "$", -1, "Anchors", false)))
|
||||
anchors.add(DefaultMutableTreeNode(RegExpSample("String end", "\\Z", -1, "Anchors", false)))
|
||||
anchors.add(DefaultMutableTreeNode(RegExpSample("Word boundary", "\\b", -1, "Anchors", false)))
|
||||
anchors.add(DefaultMutableTreeNode(RegExpSample("Non-word boundary", "\\B", -1, "Anchors", false)))
|
||||
anchors.add(DefaultMutableTreeNode(RegExpSample("Word start", "\\<", -1, "Anchors", false)))
|
||||
anchors.add(DefaultMutableTreeNode(RegExpSample("Word end", "\\>", -1, "Anchors", false)))
|
||||
root.add(anchors)
|
||||
|
||||
val classes = DefaultMutableTreeNode("Character Classes")
|
||||
classes.add(DefaultMutableTreeNode(RegExpSample("Control character", "\\c", -1, "Classes", false)))
|
||||
classes.add(DefaultMutableTreeNode(RegExpSample("Whitespace", "\\s", -1, "Classes", false)))
|
||||
classes.add(DefaultMutableTreeNode(RegExpSample("Non-whitespace", "\\S", -1, "Classes", false)))
|
||||
classes.add(DefaultMutableTreeNode(RegExpSample("Digit", "\\d", -1, "Classes", false)))
|
||||
classes.add(DefaultMutableTreeNode(RegExpSample("Non-digit", "\\D", -1, "Classes", false)))
|
||||
classes.add(DefaultMutableTreeNode(RegExpSample("Word", "\\w", -1, "Classes", false)))
|
||||
classes.add(DefaultMutableTreeNode(RegExpSample("Non-word", "\\W", -1, "Classes", false)))
|
||||
classes.add(DefaultMutableTreeNode(RegExpSample("Hexadecimal digit", "\\x", -1, "Classes", false)))
|
||||
classes.add(DefaultMutableTreeNode(RegExpSample("Octal digit", "\\O", -1, "Classes", false)))
|
||||
root.add(classes)
|
||||
|
||||
treeModel.reload()
|
||||
DefaultTreeExpander(tree).expandAll()
|
||||
|
||||
object: DoubleClickListener() {
|
||||
override fun onDoubleClick(event: MouseEvent): Boolean {
|
||||
val path = tree.selectionPath ?: return false
|
||||
val node = TreePathUtil.toTreeNode(path) as? DefaultMutableTreeNode
|
||||
val sample = node?.userObject as? RegExpSample ?: return false
|
||||
doubleClickConsumer(sample)
|
||||
return true
|
||||
}
|
||||
}.installOn(tree)
|
||||
}
|
||||
|
||||
private inner class MyTreeCellRenderer(private val mySpeedSearch: TreeSpeedSearch) : ColoredTreeCellRenderer() {
|
||||
override fun customizeCellRenderer(tree: JTree, value: Any, selected: Boolean, expanded: Boolean, leaf: Boolean, row: Int, hasFocus: Boolean) {
|
||||
val treeNode = value as DefaultMutableTreeNode
|
||||
val userObject = treeNode.userObject ?: return
|
||||
val background = UIUtil.getTreeBackground(selected, hasFocus)
|
||||
val foreground = UIUtil.getTreeForeground(selected, hasFocus)
|
||||
|
||||
val text: String
|
||||
val style: Int
|
||||
when (userObject) {
|
||||
is RegExpSample -> {
|
||||
text = userObject.name
|
||||
style = SimpleTextAttributes.STYLE_PLAIN
|
||||
}
|
||||
else -> {
|
||||
text = userObject.toString()
|
||||
style = SimpleTextAttributes.STYLE_BOLD
|
||||
}
|
||||
}
|
||||
SearchUtil.appendFragments(mySpeedSearch.enteredPrefix, text, style, foreground, background, this)
|
||||
if (userObject is RegExpSample) {
|
||||
append(ColoredText.singleFragment(" ${userObject.sample}", SimpleTextAttributes.GRAYED_ATTRIBUTES))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,9 @@ public final class InspectionsConfigTreeComparator {
|
||||
.thenComparing(n -> getDisplayTextToSort(n.getText()), NaturalComparator.INSTANCE);
|
||||
|
||||
public static String getDisplayTextToSort(String s) {
|
||||
if (s.equals("User Defined")) {
|
||||
return " User Defined";
|
||||
}
|
||||
if (s.isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -276,8 +276,8 @@ error.in.groovy.parser=Error in Groovy parser
|
||||
|
||||
SSRInspection.family.name=Replace Structurally
|
||||
SSRInspection.display.name=Structural search inspection
|
||||
SSRInspection.add.search.template.button=Add Search Template\u2026
|
||||
SSRInspection.add.replace.template.button=Add Replace Template\u2026
|
||||
SSRInspection.add.search.template.button=Add Structural Search Inspection\u2026
|
||||
SSRInspection.add.replace.template.button=Add Structural Replace Inspection\u2026
|
||||
overwrite.message=A template with the same name already exists. Replacing it will overwrite its current contents.
|
||||
overwrite.title="{0}" Exists, Replace?
|
||||
template.in.use.message=Template ''{0}'' is used from template ''{1}''. Are you sure you want to remove it?
|
||||
@@ -288,12 +288,13 @@ search.script.problem=Structural Search script threw an exception: {0}
|
||||
complete.match.variable.name=Complete match
|
||||
template.in.use.title=Template ''{0}'' In Use
|
||||
|
||||
user.defined.group.name=User Defined
|
||||
structural.search.group.name=Structural search
|
||||
edit.metadata.button=Edit Metadata\u2026
|
||||
add.pattern.action=Add Template
|
||||
templates.title=Templates:
|
||||
add.inspection.button=Add Structural Search \\&\\& Replace Inspection
|
||||
remove.inspection.button=Remove Structural Search \\&\\& Replace Inspection
|
||||
add.inspection.button=Add Custom Inspection
|
||||
remove.inspection.button=Remove Custom Inspection
|
||||
|
||||
meta.data.dialog.title=Structural Search Inspection
|
||||
inspection.name.label=Inspection name:
|
||||
@@ -331,4 +332,6 @@ replace.configuration.display.text={0} \u21E8 {1}
|
||||
|
||||
# SSR advertising in the empty inspection tree
|
||||
inspection.tree.create.inspection.search.template=Using a Structural Search Template\u2026
|
||||
inspection.tree.create.inspection.replace.template=Using a Structural Replace Template\u2026
|
||||
inspection.tree.create.inspection.replace.template=Using a Structural Replace Template\u2026
|
||||
action.add.regexp.replace.inspection.text=Add RegExp Replace Inspection\u2026
|
||||
action.add.regexp.search.inspection.text=Add RegExp Search Inspection\u2026
|
||||
@@ -1,13 +1,16 @@
|
||||
// Copyright 2000-2020 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.
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.structuralsearch.inspection;
|
||||
|
||||
import com.intellij.codeInspection.InspectionProfile;
|
||||
import com.intellij.codeInspection.InspectionProfileEntry;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileImpl;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileModifiableModel;
|
||||
import com.intellij.codeInspection.ex.InspectionToolWrapper;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.profile.codeInspection.ui.SingleInspectionProfilePanel;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.intellij.lang.regexp.inspection.custom.CustomRegExpInspection;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
@@ -21,9 +24,17 @@ public final class InspectionProfileUtil {
|
||||
|
||||
@NotNull
|
||||
public static SSBasedInspection getStructuralSearchInspection(@NotNull InspectionProfile profile) {
|
||||
final InspectionToolWrapper<?, ?> wrapper = profile.getInspectionTool(SSBasedInspection.SHORT_NAME, (Project)null);
|
||||
return (SSBasedInspection)getInspection(profile, SSBasedInspection.SHORT_NAME);
|
||||
}
|
||||
|
||||
public static CustomRegExpInspection getCustomRegExpInspection(@NotNull InspectionProfile profile) {
|
||||
return (CustomRegExpInspection)getInspection(profile, CustomRegExpInspection.SHORT_NAME);
|
||||
}
|
||||
|
||||
public static InspectionProfileEntry getInspection(@NotNull InspectionProfile profile, @NonNls String shortName) {
|
||||
final InspectionToolWrapper<?, ?> wrapper = profile.getInspectionTool(shortName, (Project)null);
|
||||
assert wrapper != null;
|
||||
return (SSBasedInspection)wrapper.getTool();
|
||||
return wrapper.getTool();
|
||||
}
|
||||
|
||||
public static InspectionProfileModifiableModel getInspectionProfile(@NotNull Component c) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.structuralsearch.inspection;
|
||||
|
||||
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
|
||||
import com.intellij.codeInspection.InspectionsBundle;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileModifiableModel;
|
||||
import com.intellij.ide.DataManager;
|
||||
@@ -105,6 +106,11 @@ public class StructuralSearchFakeInspection extends LocalInspectionTool {
|
||||
return SSRBundle.message("structural.search.group.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nls(capitalization = Nls.Capitalization.Sentence) String @NotNull [] getGroupPath() {
|
||||
return new String[] {InspectionsBundle.message("group.names.user.defined"), getGroupDisplayName()};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getStaticDescription() {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.structuralsearch.inspection;
|
||||
|
||||
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
|
||||
import com.intellij.codeInspection.InspectionProfileEntry;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileImpl;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileModifiableModel;
|
||||
@@ -30,6 +31,7 @@ import com.intellij.ui.EditorTextField;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.ui.FormBuilder;
|
||||
import org.intellij.lang.regexp.inspection.custom.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -51,7 +53,9 @@ public class StructuralSearchProfileActionProvider extends InspectionProfileActi
|
||||
enableSSIfDisabled(panel.getProfile(), panel.getProject());
|
||||
final DefaultActionGroup actionGroup = new DefaultActionGroup(
|
||||
new AddInspectionAction(panel, SSRBundle.message("SSRInspection.add.search.template.button"), false),
|
||||
new AddInspectionAction(panel, SSRBundle.message("SSRInspection.add.replace.template.button"), true)
|
||||
new AddInspectionAction(panel, SSRBundle.message("SSRInspection.add.replace.template.button"), true),
|
||||
new AddCustomRegExpInspectionAction(panel, SSRBundle.message("action.add.regexp.search.inspection.text"), false),
|
||||
new AddCustomRegExpInspectionAction(panel, SSRBundle.message("action.add.regexp.replace.inspection.text"), true)
|
||||
);
|
||||
actionGroup.setPopup(true);
|
||||
actionGroup.registerCustomShortcutSet(CommonShortcuts.getNew(), panel);
|
||||
@@ -86,7 +90,9 @@ public class StructuralSearchProfileActionProvider extends InspectionProfileActi
|
||||
|
||||
@Override
|
||||
public void update(@NotNull AnActionEvent e) {
|
||||
e.getPresentation().setEnabled(myPanel.getSelectedTool() instanceof StructuralSearchInspectionToolWrapper);
|
||||
final InspectionToolWrapper<?, ?> selectedTool = myPanel.getSelectedTool();
|
||||
e.getPresentation().setEnabled(selectedTool instanceof CustomRegExpInspectionToolWrapper ||
|
||||
selectedTool instanceof StructuralSearchInspectionToolWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,16 +104,57 @@ public class StructuralSearchProfileActionProvider extends InspectionProfileActi
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
final InspectionToolWrapper<?, ?> selectedTool = myPanel.getSelectedTool();
|
||||
final String shortName = selectedTool.getShortName();
|
||||
final String mainToolId = selectedTool.getMainToolId();
|
||||
myPanel.removeSelectedRow();
|
||||
final InspectionProfileModifiableModel profile = myPanel.getProfile();
|
||||
final SSBasedInspection inspection = InspectionProfileUtil.getStructuralSearchInspection(profile);
|
||||
inspection.removeConfigurationsWithUuid(shortName);
|
||||
final InspectionProfileEntry inspection = InspectionProfileUtil.getInspection(profile, mainToolId);
|
||||
if (inspection instanceof SSBasedInspection ssBasedInspection) {
|
||||
ssBasedInspection.removeConfigurationsWithUuid(shortName);
|
||||
}
|
||||
else if (inspection instanceof CustomRegExpInspection customRegExpInspection) {
|
||||
customRegExpInspection.removeConfigurationWithUuid(shortName);
|
||||
}
|
||||
profile.removeTool(selectedTool);
|
||||
profile.setModified(true);
|
||||
InspectionProfileUtil.fireProfileChanged(profile);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AddCustomRegExpInspectionAction extends DumbAwareAction {
|
||||
private final SingleInspectionProfilePanel myPanel;
|
||||
private final boolean myReplace;
|
||||
|
||||
AddCustomRegExpInspectionAction(@NotNull SingleInspectionProfilePanel panel, @NlsActions.ActionText String text, boolean replace) {
|
||||
super(text);
|
||||
myPanel = panel;
|
||||
myReplace = replace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
final RegExpDialog dialog = new RegExpDialog(e.getProject(), true, myReplace ? RegExpInspectionConfiguration.InspectionPattern.EMPTY_REPLACE_PATTERN : null);
|
||||
if (myReplace) {
|
||||
// do something?
|
||||
}
|
||||
if (!dialog.showAndGet()) return;
|
||||
|
||||
final RegExpInspectionConfiguration.InspectionPattern pattern = dialog.getPattern();
|
||||
final InspectionProfileModifiableModel profile = myPanel.getProfile();
|
||||
final CustomRegExpInspection inspection = InspectionProfileUtil.getCustomRegExpInspection(profile);
|
||||
final RegExpInspectionConfiguration configuration = new RegExpInspectionConfiguration("new inspection");
|
||||
configuration.patterns.add(pattern);
|
||||
final Project project = getEventProject(e);
|
||||
final MetaDataDialog metaDataDialog = new MetaDataDialog(project, inspection, configuration, true);
|
||||
if (!metaDataDialog.showAndGet()) return;
|
||||
|
||||
inspection.addConfiguration(configuration);
|
||||
CustomRegExpInspection.addInspectionToProfile(project, profile, configuration);
|
||||
profile.setModified(true);
|
||||
InspectionProfileUtil.fireProfileChanged(profile);
|
||||
myPanel.selectInspectionTool(configuration.getUuid());
|
||||
}
|
||||
}
|
||||
|
||||
static final class AddInspectionAction extends DumbAwareAction {
|
||||
private final SingleInspectionProfilePanel myPanel;
|
||||
private final boolean myReplace;
|
||||
@@ -210,7 +257,7 @@ public class StructuralSearchProfileActionProvider extends InspectionProfileActi
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JComponent getPreferredFocusedComponent() {
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myNameTextField;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user