diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java index c0051d57be9f..0eadc89d1150 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java @@ -288,7 +288,7 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile { @NotNull PatternTreeContext context, @NotNull LanguageFileType fileType, @NotNull Language language, - String contextName, + String contextId, @NotNull Project project, boolean physical) { if (physical) { @@ -311,7 +311,7 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile { if (shouldTryExpressionPattern(result)) { try { final PsiElement[] expressionPattern = - createPatternTree(text, PatternTreeContext.Expression, fileType, language, contextName, project, false); + createPatternTree(text, PatternTreeContext.Expression, fileType, language, contextId, project, false); if (expressionPattern.length == 1) { return expressionPattern; } @@ -319,7 +319,7 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile { } else if (shouldTryClassPattern(result)) { final PsiElement[] classPattern = - createPatternTree(text, PatternTreeContext.Class, fileType, language, contextName, project, false); + createPatternTree(text, PatternTreeContext.Class, fileType, language, contextId, project, false); if (classPattern.length <= result.size()) { return classPattern; } @@ -416,7 +416,7 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile { @NotNull @Override - public PsiCodeFragment createCodeFragment(Project project, String text) { + public PsiCodeFragment createCodeFragment(Project project, String text, String contextId) { return JavaCodeFragmentFactory.getInstance(project).createCodeBlockCodeFragment(text, null, true); } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/MatchOptions.java b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchOptions.java index bbdd8a08a087..597b5de07157 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/MatchOptions.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchOptions.java @@ -31,7 +31,7 @@ public class MatchOptions implements JDOMExternalizable { @NotNull private String pattern; - private String myPatternContext; + private String myPatternContextId; @NonNls private static final String TEXT_ATTRIBUTE_NAME = "text"; @NonNls private static final String LOOSE_MATCHING_ATTRIBUTE_NAME = "loose"; @@ -65,7 +65,7 @@ public class MatchOptions implements JDOMExternalizable { scopeType = options.scopeType; scopeDescriptor = options.scopeDescriptor; pattern = options.pattern; - myPatternContext = options.myPatternContext; + myPatternContextId = options.myPatternContextId; } public MatchOptions copy() { @@ -169,11 +169,11 @@ public class MatchOptions implements JDOMExternalizable { if (myFileType != null) { element.setAttribute(FILE_TYPE_ATTR_NAME, myFileType.getName()); } - if (myDialect != null) { + if (myDialect != null && (myFileType == null || myFileType.getLanguage() != myDialect)) { element.setAttribute(DIALECT_ATTR_NAME, myDialect.getID()); } - if (myPatternContext != null) { - element.setAttribute(PATTERN_CONTEXT_ATTR_NAME, myPatternContext); + if (myPatternContextId != null) { + element.setAttribute(PATTERN_CONTEXT_ATTR_NAME, myPatternContextId); } if (scope != null) { @@ -202,7 +202,7 @@ public class MatchOptions implements JDOMExternalizable { myFileType = getFileTypeByName(element.getAttributeValue(FILE_TYPE_ATTR_NAME)); myDialect = Language.findLanguageByID(element.getAttributeValue(DIALECT_ATTR_NAME)); - myPatternContext = element.getAttributeValue(PATTERN_CONTEXT_ATTR_NAME); + myPatternContextId = element.getAttributeValue(PATTERN_CONTEXT_ATTR_NAME); final String value = element.getAttributeValue(SCOPE_TYPE); scopeType = (value == null) ? null : Scopes.Type.valueOf(value); @@ -241,12 +241,12 @@ public class MatchOptions implements JDOMExternalizable { if (!variableConstraints.equals(matchOptions.variableConstraints)) return false; if (myFileType != matchOptions.myFileType) return false; if (!Objects.equals(myDialect, matchOptions.myDialect)) return false; - if (!Objects.equals(myPatternContext, matchOptions.myPatternContext)) return false; + if (!Objects.equals(myPatternContextId, matchOptions.myPatternContextId)) return false; return true; } -public int hashCode() { + public int hashCode() { int result = (looseMatching ? 1 : 0); result = 29 * result + (recursiveSearch ? 1 : 0); result = 29 * result + (caseSensitiveMatch ? 1 : 0); @@ -256,7 +256,7 @@ public int hashCode() { result = 29 * result + scope.hashCode(); if (myFileType != null) result = 29 * result + myFileType.hashCode(); if (myDialect != null) result = 29 * result + myDialect.hashCode(); - if (myPatternContext != null) result = 29 * result + myPatternContext.hashCode(); + if (myPatternContextId != null) result = 29 * result + myPatternContextId.hashCode(); return result; } @@ -284,11 +284,12 @@ public int hashCode() { myDialect = dialect; } - public String getPatternContext() { - return myPatternContext; + public PatternContext getPatternContext() { + if (myPatternContextId == null) return null; + return StructuralSearchUtil.findPatternContextByID(myPatternContextId, getDialect()); } - public void setPatternContext(String patternContext) { - myPatternContext = patternContext; + public void setPatternContext(PatternContext patternContext) { + myPatternContextId = (patternContext == null) ? null : patternContext.getId(); } } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/PatternContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/PatternContext.java new file mode 100644 index 000000000000..0f8eafe0c1ef --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/PatternContext.java @@ -0,0 +1,52 @@ +// Copyright 2000-2019 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; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Bas Leijdekkers + */ +public final class PatternContext implements Comparable { + + public final String myID; + private final String myDisplayName; + + public PatternContext(@NotNull String ID, String displayName) { + myID = ID; + myDisplayName = displayName; + + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final PatternContext other = (PatternContext)o; + return myID.equals(other.myID) && myDisplayName.equals(other.myDisplayName); + } + + @Override + public int hashCode() { + return 31 * myID.hashCode() + myDisplayName.hashCode(); + } + + @Override + public int compareTo(@NotNull PatternContext o) { + return myDisplayName.compareTo(o.myDisplayName); + } + + @NotNull + public String getId() { + return myID; + } + + public String getDisplayName() { + return myDisplayName; + } + + @Override + public String toString() { + return myDisplayName + " (" + myID + ')'; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java index 50ddff33fc96..6d4a10208068 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java @@ -22,6 +22,7 @@ import com.intellij.structuralsearch.plugin.replace.impl.ReplacementBuilder; import com.intellij.structuralsearch.plugin.replace.impl.Replacer; import com.intellij.structuralsearch.plugin.ui.Configuration; import com.intellij.structuralsearch.plugin.ui.UIUtil; +import com.intellij.util.SmartList; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,6 +37,7 @@ import java.util.List; public abstract class StructuralSearchProfile { public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.structuralsearch.profile"); + protected static final String PATTERN_PLACEHOLDER = "$$PATTERN_PLACEHOLDER$$"; public abstract void compile(PsiElement[] elements, @NotNull GlobalCompilingVisitor globalVisitor); @@ -59,13 +61,52 @@ public abstract class StructuralSearchProfile { @NotNull PatternTreeContext context, @NotNull LanguageFileType fileType, @NotNull Language language, - @Nullable String contextName, + @Nullable String contextId, @NotNull Project project, boolean physical) { - final String name = "__dummy." + fileType.getDefaultExtension(); - final PsiFile file = PsiFileFactory.getInstance(project).createFileFromText(name, language, text, physical, true); + final String strContext = getContext(text, language, contextId); + final int offset = strContext.indexOf(PATTERN_PLACEHOLDER); - return file != null ? file.getChildren() : PsiElement.EMPTY_ARRAY; + final int patternLength = text.length(); + final String patternInContext = strContext.replace(PATTERN_PLACEHOLDER, text); + + final String name = "__dummy." + fileType.getDefaultExtension(); + final PsiFile file = PsiFileFactory.getInstance(project).createFileFromText(name, language, patternInContext, physical, true); + if (file == null) { + return PsiElement.EMPTY_ARRAY; + } + + final List result = new SmartList<>(); + + PsiElement element = file.findElementAt(offset); + if (element == null) { + return PsiElement.EMPTY_ARRAY; + } + + PsiElement topElement = element; + element = element.getParent(); + + while (element != null) { + if (element.getTextRange().getStartOffset() == offset && element.getTextLength() <= patternLength) { + topElement = element; + } + element = element.getParent(); + } + + if (topElement instanceof PsiFile) { + return topElement.getChildren(); + } + + final int endOffset = offset + patternLength; + result.add(topElement); + topElement = topElement.getNextSibling(); + + while (topElement != null && topElement.getTextRange().getEndOffset() <= endOffset) { + result.add(topElement); + topElement = topElement.getNextSibling(); + } + + return result.toArray(PsiElement.EMPTY_ARRAY); } /** @@ -89,8 +130,18 @@ public abstract class StructuralSearchProfile { return createPatternTree(text, context, (LanguageFileType)fileType, language, contextName, project, physical); } + @NotNull + public List getPatternContexts() { + return Collections.emptyList(); + } + + @NotNull + protected String getContext(@NotNull String pattern, @Nullable Language language, @Nullable String contextId) { + return PATTERN_PLACEHOLDER; + } + @Nullable - public PsiCodeFragment createCodeFragment(Project project, String text) { + public PsiCodeFragment createCodeFragment(Project project, String text, String contextId) { return null; } @@ -129,7 +180,7 @@ public abstract class StructuralSearchProfile { public String getText(PsiElement match, int start, int end) { final String matchText = match.getText(); - if (start==0 && end==-1) return matchText; + if (start == 0 && end == -1) return matchText; return matchText.substring(start, end == -1 ? matchText.length() : end); } @@ -143,7 +194,7 @@ public abstract class StructuralSearchProfile { } return element.getText(); } - + public String getMeaningfulText(PsiElement element) { return getTypedVarString(element); } @@ -192,7 +243,8 @@ public abstract class StructuralSearchProfile { if (buf.length() > 0) { if (info.isArgumentContext()) { buf.append(','); - } else { + } + else { final PsiElement sibling = currentElement.getPrevSibling(); buf.append(sibling instanceof PsiWhiteSpace ? sibling.getText() : " "); } @@ -202,7 +254,8 @@ public abstract class StructuralSearchProfile { removeSemicolon = currentElement instanceof PsiComment; } replacementString = buf.toString(); - } else { + } + else { if (info.isStatementContext()) { removeSemicolon = match.getMatch() instanceof PsiComment; } @@ -211,7 +264,7 @@ public abstract class StructuralSearchProfile { offset = Replacer.insertSubstitution(result, offset, info, replacementString); if (info.isStatementContext() && - (removeSemicolon || StringUtil.endsWithChar(replacementString, ';') || StringUtil.endsWithChar(replacementString, '}'))) { + (removeSemicolon || StringUtil.endsWithChar(replacementString, ';') || StringUtil.endsWithChar(replacementString, '}'))) { final int start = info.getStartIndex() + offset; result.delete(start, start + 1); offset--; @@ -255,10 +308,10 @@ public abstract class StructuralSearchProfile { * Override this method to influence which UI controls are shown when editing the constraints of the specified variable. * * @param constraintName the name of the constraint controls for which applicability is considered. - * See {@link UIUtil} for predefined constraint names - * @param variableNode the psi element corresponding to the current variable - * @param completePattern true, if the current variableNode encompasses the complete pattern. The variableNode can also be null in this case. - * @param target true, if the current variableNode is the target of the search + * See {@link UIUtil} for predefined constraint names + * @param variableNode the psi element corresponding to the current variable + * @param completePattern true, if the current variableNode encompasses the complete pattern. The variableNode can also be null in this case. + * @param target true, if the current variableNode is the target of the search * @return true, if the requested constraint is applicable and the corresponding UI should be shown when editing the variable; false otherwise */ public boolean isApplicableConstraint(String constraintName, @Nullable PsiElement variableNode, boolean completePattern, boolean target) { @@ -267,12 +320,16 @@ public abstract class StructuralSearchProfile { if (target) return false; case UIUtil.MAXIMUM_UNLIMITED: case UIUtil.TEXT: - case UIUtil.REFERENCE: return !completePattern; + case UIUtil.REFERENCE: + return !completePattern; } return false; } - public final boolean isApplicableConstraint(String constraintName, List nodes, boolean completePattern, boolean target) { + public final boolean isApplicableConstraint(String constraintName, + List nodes, + boolean completePattern, + boolean target) { if (nodes.isEmpty()) { return isApplicableConstraint(constraintName, (PsiElement)null, completePattern, target); } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfileBase.java b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfileBase.java index 734578292a83..10a1705fb2f3 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfileBase.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfileBase.java @@ -21,17 +21,17 @@ import com.intellij.psi.tree.TokenSet; import com.intellij.structuralsearch.impl.matcher.CompiledPattern; import com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor; import com.intellij.structuralsearch.impl.matcher.MatchContext; -import com.intellij.structuralsearch.impl.matcher.PatternTreeContext; import com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor; import com.intellij.structuralsearch.impl.matcher.handlers.*; import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator; import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy; import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; -import com.intellij.util.ArrayUtilRt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import java.util.regex.Pattern; /** @@ -39,7 +39,6 @@ import java.util.regex.Pattern; */ public abstract class StructuralSearchProfileBase extends StructuralSearchProfile { private static final String DELIMITER_CHARS = ",;.[]{}():"; - protected static final String PATTERN_PLACEHOLDER = "$$PATTERN_PLACEHOLDER$$"; @Override public void compile(PsiElement[] elements, @NotNull final GlobalCompilingVisitor globalVisitor) { @@ -179,66 +178,6 @@ public abstract class StructuralSearchProfileBase extends StructuralSearchProfil @NotNull protected abstract LanguageFileType getFileType(); - @NotNull - @Override - public PsiElement[] createPatternTree(@NotNull String text, - @NotNull PatternTreeContext context, - @NotNull LanguageFileType fileType, - @NotNull Language language, - @Nullable String contextName, - @NotNull Project project, - boolean physical) { - if (context == PatternTreeContext.Block) { - final String strContext = getContext(text, language, contextName); - if (strContext == null) { - return PsiElement.EMPTY_ARRAY; - } - final int offset = strContext.indexOf(PATTERN_PLACEHOLDER); - - final int patternLength = text.length(); - final String patternInContext = strContext.replace(PATTERN_PLACEHOLDER, text); - - final String name = "__dummy." + fileType.getDefaultExtension(); - final PsiFile file = PsiFileFactory.getInstance(project).createFileFromText(name, language, patternInContext, physical, true); - if (file == null) { - return PsiElement.EMPTY_ARRAY; - } - - final List result = new ArrayList<>(); - - PsiElement element = file.findElementAt(offset); - if (element == null) { - return PsiElement.EMPTY_ARRAY; - } - - PsiElement topElement = element; - element = element.getParent(); - - while (element != null) { - if (element.getTextRange().getStartOffset() == offset && element.getTextLength() <= patternLength) { - topElement = element; - } - element = element.getParent(); - } - - if (topElement instanceof PsiFile) { - return topElement.getChildren(); - } - - final int endOffset = offset + patternLength; - result.add(topElement); - topElement = topElement.getNextSibling(); - - while (topElement != null && topElement.getTextRange().getEndOffset() <= endOffset) { - result.add(topElement); - topElement = topElement.getNextSibling(); - } - - return result.toArray(PsiElement.EMPTY_ARRAY); - } - return super.createPatternTree(text, context, fileType, language, contextName, project, physical); - } - @Override public void checkReplacementPattern(Project project, ReplaceOptions options) {} @@ -247,16 +186,6 @@ public abstract class StructuralSearchProfileBase extends StructuralSearchProfil return new DocumentBasedReplaceHandler(project); } - @NotNull - public String[] getContextNames() { - return ArrayUtilRt.EMPTY_STRING_ARRAY; - } - - @Nullable - protected String getContext(@NotNull String pattern, @Nullable Language language, @Nullable String contextName) { - return PATTERN_PLACEHOLDER; - } - static boolean canBePatternVariable(PsiElement element) { // can be leaf element! (ex. var a = 1 <-> var $a$ = 1) if (element instanceof LeafElement) { diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java index 231698d6d490..5d8f46949313 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java @@ -225,4 +225,22 @@ public class StructuralSearchUtil { public static String normalize(@NotNull String text) { return stripAccents(normalizeWhiteSpace(text)); } + + public static PatternContext findPatternContextByID(String id, Language language) { + return findPatternContextByID(id, getProfileByLanguage(language)); + } + + public static PatternContext findPatternContextByID(String id, StructuralSearchProfile profile) { + if (profile == null) { + return null; + } + final List patternContexts = profile.getPatternContexts(); + if (patternContexts.isEmpty()) { + return null; + } + if (id == null) { + return patternContexts.get(0); + } + return patternContexts.stream().filter(context -> context.getId().equals(id)).findFirst().orElse(patternContexts.get(0)); + } } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java b/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java index f985de88b032..96f89001213d 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java @@ -85,7 +85,7 @@ public class XmlStructuralSearchProfile extends StructuralSearchProfile { @NotNull PatternTreeContext context, @NotNull LanguageFileType fileType, @NotNull Language language, - String contextName, + String contextId, @NotNull Project project, boolean physical) { text = context == PatternTreeContext.File ? text : "" + text + ""; diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java index 273c77ff6e31..8ab159d4d12a 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java @@ -86,7 +86,7 @@ public abstract class CompiledPattern { @NotNull public String getTypedVarString(PsiElement element) { final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element); - String typedVarString = (profile == null) ? element.getText() : profile.getTypedVarString(element); + final String typedVarString = (profile == null) ? element.getText() : profile.getTypedVarString(element); return typedVarString.trim(); } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java index e6472e96027e..2408c6beb04a 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java @@ -5,6 +5,7 @@ import com.intellij.lang.Language; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.PatternContext; import com.intellij.structuralsearch.StructuralSearchProfile; import com.intellij.structuralsearch.StructuralSearchUtil; @@ -33,7 +34,7 @@ public class MatcherImplUtil { PatternTreeContext context, LanguageFileType fileType, Language language, - String contextName, + PatternContext patternContext, Project project, boolean physical) { if (language == null) { @@ -41,7 +42,8 @@ public class MatcherImplUtil { } final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language); if (profile != null) { - return profile.createPatternTree(text, context, fileType, language, contextName, project, physical); + final String contextId = (patternContext == null) ? null : patternContext.getId(); + return profile.createPatternTree(text, context, fileType, language, contextId, project, physical); } return PsiElement.EMPTY_ARRAY; } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplacementPreviewDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplacementPreviewDialog.java index 9f154686ee3d..1c5b52169341 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplacementPreviewDialog.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplacementPreviewDialog.java @@ -99,7 +99,7 @@ public final class ReplacementPreviewDialog extends DialogWrapper { PsiFile file = null; final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(myFileType); if (profile != null) { - file = profile.createCodeFragment(project, ""); + file = profile.createCodeFragment(project, "", null); } if (file != null) { diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/EditVarConstraintsDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/EditVarConstraintsDialog.java index 5361763c527f..a92ace211043 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/EditVarConstraintsDialog.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/EditVarConstraintsDialog.java @@ -574,7 +574,7 @@ class EditVarConstraintsDialog extends DialogWrapper { // there is no right way to create a code fragment for generic language, so we use this hole since we need extend resolve scope for (StructuralSearchProfile profile : StructuralSearchProfile.EP_NAME.getExtensions()) { if (profile.isMyLanguage(groovy)) { - final PsiCodeFragment fragment = Objects.requireNonNull(profile.createCodeFragment(project, text)); + final PsiCodeFragment fragment = Objects.requireNonNull(profile.createCodeFragment(project, text, null)); fragment.forceResolveScope(new StructuralSearchScriptScope(myProject)); doc = PsiDocumentManager.getInstance(project).getDocument(fragment); break; diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/FileTypeInfo.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/FileTypeInfo.java index aa2d815f25f0..e5a2d758c883 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/FileTypeInfo.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/FileTypeInfo.java @@ -4,6 +4,7 @@ package com.intellij.structuralsearch.plugin.ui; import com.intellij.lang.Language; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.structuralsearch.PatternContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,20 +22,16 @@ public class FileTypeInfo { private final LanguageFileType myFileType; private final Language myDialect; - private final String myContext; - private final boolean myEnabled; + private final PatternContext myContext; + private final boolean myNested; private final String myDescription; - public FileTypeInfo(@NotNull LanguageFileType fileType, - @Nullable Language dialect, - @Nullable String context, - boolean enabled, - boolean duplicated) { + public FileTypeInfo(@NotNull LanguageFileType fileType, @NotNull Language dialect, @Nullable PatternContext context, boolean nested) { myFileType = fileType; myDialect = dialect; myContext = context; - myEnabled = enabled; - myDescription = getDescription(fileType, duplicated); + myNested = nested; + myDescription = getDescription(fileType); } @NotNull @@ -48,62 +45,42 @@ public class FileTypeInfo { } @Nullable - public String getContext() { + public PatternContext getContext() { return myContext; } @NotNull public String getText() { - if (myDialect != null) { - return myDialect.getDisplayName(); - } - if (myContext != null) { - return myContext + " Context"; - } - return myFileType.getName(); - } - - @NotNull - public String getSearchText() { - if (myDialect != null) { - return myDialect.getDisplayName(); - } - return myFileType.getName(); - } - - @NotNull - public String getFullText() { - if (myDialect != null) { - return myDescription + " - " + myDialect.getDisplayName(); - } - if (myContext != null) { - return myDescription + " - " + myContext + " Context"; + if (myNested) { + if (myDialect != null && myDialect != myFileType.getLanguage()) { + return myDialect.getDisplayName(); + } + if (myContext != null) { + return myDescription + " - " + myContext.getDisplayName(); + } } return myDescription; } + @NotNull + public String getSearchText() { + return (myDialect != null) ? myDialect.getDisplayName() : myFileType.getName(); + } + public boolean isNested() { - return myDialect != null || myContext != null; + return myNested; } - public boolean isEnabled() { - return myEnabled; - } - - public boolean isEqualTo(@NotNull LanguageFileType fileType, @Nullable Language dialect, @Nullable String context) { - return Objects.equals(myFileType, fileType) && - Objects.equals(myDialect, dialect) && - Objects.equals(myContext, context); + public boolean isEqualTo(@NotNull LanguageFileType fileType, @Nullable Language dialect, @Nullable PatternContext context) { + return (myFileType == fileType) + && (dialect == null || myDialect == dialect) + && (context == null || myContext == context); } @NotNull - private static String getDescription(@NotNull LanguageFileType fileType, boolean duplicated) { + private static String getDescription(@NotNull LanguageFileType fileType) { final String description = fileType.getDescription(); - final String trimmedDescription = StringUtil.capitalizeWords(CLEANUP.matcher(description).replaceAll(""), true); - if (!duplicated) { - return trimmedDescription; - } - return trimmedDescription + " (" + fileType.getName() + ")"; + return StringUtil.capitalizeWords(CLEANUP.matcher(description).replaceAll(""), true); } @Override @@ -111,9 +88,9 @@ public class FileTypeInfo { if (this == o) return true; if (!(o instanceof FileTypeInfo)) return false; final FileTypeInfo info = (FileTypeInfo)o; - return Objects.equals(myFileType, info.myFileType) && - Objects.equals(myDialect, info.myDialect) && - Objects.equals(myContext, info.myContext); + return myFileType == info.myFileType + && myDialect == info.myDialect + && myContext == info.myContext; } @Override @@ -123,6 +100,6 @@ public class FileTypeInfo { @Override public String toString() { - return getFullText(); + return getText(); } } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/FileTypeSelector.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/FileTypeSelector.java index 9be36429da62..11f6d44ae76d 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/FileTypeSelector.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/FileTypeSelector.java @@ -3,11 +3,10 @@ package com.intellij.structuralsearch.plugin.ui; import com.intellij.lang.Language; import com.intellij.lang.LanguageUtil; -import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.ui.ComboBox; +import com.intellij.structuralsearch.PatternContext; import com.intellij.structuralsearch.StructuralSearchProfile; -import com.intellij.structuralsearch.StructuralSearchProfileBase; import com.intellij.structuralsearch.StructuralSearchUtil; import com.intellij.ui.LayeredIcon; import com.intellij.ui.SimpleListCellRenderer; @@ -42,7 +41,9 @@ public class FileTypeSelector extends ComboBox { return info != null ? info.getFileType() : null; } - public void setSelectedItem(@NotNull LanguageFileType type, @Nullable Language dialect, @Nullable String context) { + public void setSelectedItem(@NotNull LanguageFileType type, + @Nullable Language dialect, + @Nullable PatternContext context) { final DefaultComboBoxModel model = (DefaultComboBoxModel)getModel(); for (int i = 0; i < model.getSize(); i++) { final FileTypeInfo info = model.getElementAt(i); @@ -53,23 +54,6 @@ public class FileTypeSelector extends ComboBox { } } - @Override - public void setSelectedItem(Object anObject) { - if (anObject instanceof FileTypeInfo) { - final FileTypeInfo selectedInfo = (FileTypeInfo)anObject; - if (!selectedInfo.isEnabled()) { - final MyComboBoxModel model = (MyComboBoxModel)getModel(); - final int index = model.getIndexOf(selectedInfo); - if (index >= 0 && index + 1 < model.getSize()) { - final FileTypeInfo nextInfo = model.getElementAt(index + 1); - super.setSelectedItem(nextInfo); - return; - } - } - } - super.setSelectedItem(anObject); - } - @NotNull private static DefaultComboBoxModel createModel() { final List types = new ArrayList<>(); @@ -81,30 +65,25 @@ public class FileTypeSelector extends ComboBox { Collections.sort(types, (o1, o2) -> o1.getDescription().compareToIgnoreCase(o2.getDescription())); final List infos = new ArrayList<>(); for (LanguageFileType fileType : types) { - final boolean duplicated = isDuplicated(fileType, types); - final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType); assert profile != null; - if (profile instanceof StructuralSearchProfileBase) { - final String[] contextNames = ((StructuralSearchProfileBase)profile).getContextNames(); - if (contextNames.length != 0) { - Arrays.sort(contextNames); - infos.add(new FileTypeInfo(fileType, null, null, false, duplicated)); - for (String contextName: contextNames) { - infos.add(new FileTypeInfo(fileType, null, contextName, true, duplicated)); - } - continue; // proceed with the next file type + final Language language = fileType.getLanguage(); + final List patternContexts = new ArrayList<>(profile.getPatternContexts()); + if (!patternContexts.isEmpty()) { + infos.add(new FileTypeInfo(fileType, language, patternContexts.get(0), false)); + for (int i = 1; i < patternContexts.size(); i++) { + infos.add(new FileTypeInfo(fileType, language, patternContexts.get(i), true)); } + continue; // proceed with the next file type } - infos.add(new FileTypeInfo(fileType, null, null, true, duplicated)); + infos.add(new FileTypeInfo(fileType, language, null, false)); - final Language language = fileType.getLanguage(); final Language[] languageDialects = LanguageUtil.getLanguageDialects(language); Arrays.sort(languageDialects, Comparator.comparing(Language::getDisplayName)); for (Language dialect : languageDialects) { if (profile.isMyLanguage(dialect)) { - infos.add(new FileTypeInfo(fileType, dialect, null, true, duplicated)); + infos.add(new FileTypeInfo(fileType, dialect, null, true)); } } } @@ -112,11 +91,6 @@ public class FileTypeSelector extends ComboBox { return new MyComboBoxModel(infos); } - private static boolean isDuplicated(@NotNull LanguageFileType fileType, @NotNull List types) { - final String description = fileType.getDescription(); - return types.stream().anyMatch(type -> type != fileType && description.equals(type.getDescription())); - } - private static class MyComboBoxModel extends DefaultComboBoxModel { MyComboBoxModel(List infos) { super(infos.toArray(FileTypeInfo.EMPTY_ARRAY)); @@ -134,14 +108,8 @@ public class FileTypeSelector extends ComboBox { if (value == null) { return; } - if (value.isNested() && index >= 0) { - setIcon(WIDE_EMPTY_ICON); - setText(value.getText()); - } - else { - setIcon(getFileTypeIcon(value)); - setText(value.getFullText()); - } + setIcon(value.isNested() && index >= 0 ? WIDE_EMPTY_ICON : getFileTypeIcon(value)); + setText(value.getText()); } @NotNull diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java index ee3da78c5dac..3c48b27f2857 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java @@ -79,7 +79,7 @@ public class SearchDialog extends DialogWrapper { @NonNls private LanguageFileType ourFtSearchVariant = StructuralSearchUtil.getDefaultFileType(); private static Language ourDialect = null; - private static String ourContext = null; + private static PatternContext ourContext = null; private final boolean myShowScopePanel; private final boolean myRunFindActionOnClose; diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/StructuralSearchDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/StructuralSearchDialog.java index 44db8ad30cf6..877eba860628 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/StructuralSearchDialog.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/StructuralSearchDialog.java @@ -119,7 +119,7 @@ public class StructuralSearchDialog extends DialogWrapper { Configuration myConfiguration; @NonNls LanguageFileType myFileType = StructuralSearchUtil.getDefaultFileType(); Language myDialect = null; - String myContext = null; + PatternContext myPatternContext = null; private final List myRangeHighlighters = new SmartList<>(); // ui management @@ -193,7 +193,7 @@ public class StructuralSearchDialog extends DialogWrapper { private EditorTextField createEditor() { final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(myFileType); assert profile != null; - final Document document = UIUtil.createDocument(getProject(), myFileType, myDialect, "", profile); + final Document document = UIUtil.createDocument(getProject(), myFileType, myDialect, myPatternContext, "", profile); final EditorTextField textField = new EditorTextField(document, getProject(), myFileType, false, false) { @Override @@ -494,9 +494,17 @@ public class StructuralSearchDialog extends DialogWrapper { myRecursive.setVisible(!myReplace); myMatchCase = new JCheckBox(FindBundle.message("find.popup.case.sensitive"), true); myFileType = UIUtil.detectFileType(mySearchContext); + myDialect = myFileType.getLanguage(); + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(myFileType); + if (profile != null) { + final List contexts = profile.getPatternContexts(); + if (!contexts.isEmpty()) { + myPatternContext = contexts.get(0); + } + } myFileTypesComboBox = new FileTypeSelector(); myFileTypesComboBox.setMinimumAndPreferredWidth(200); - myFileTypesComboBox.setSelectedItem(myFileType, myDialect, myContext); + myFileTypesComboBox.setSelectedItem(myFileType, myDialect, myPatternContext); myFileTypesComboBox.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { @@ -505,14 +513,14 @@ public class StructuralSearchDialog extends DialogWrapper { if (item == null) return; myFileType = item.getFileType(); myDialect = item.getDialect(); - myContext = item.getContext(); + myPatternContext = item.getContext(); final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(myFileType); assert profile != null; final Document searchDocument = - UIUtil.createDocument(getProject(), myFileType, myDialect, mySearchCriteriaEdit.getText(), profile); + UIUtil.createDocument(getProject(), myFileType, myDialect, myPatternContext, mySearchCriteriaEdit.getText(), profile); mySearchCriteriaEdit.setNewDocumentAndFileType(myFileType, searchDocument); final Document replaceDocument = - UIUtil.createDocument(getProject(), myFileType, myDialect, myReplaceCriteriaEdit.getText(), profile); + UIUtil.createDocument(getProject(), myFileType, myDialect, myPatternContext, myReplaceCriteriaEdit.getText(), profile); myReplaceCriteriaEdit.setNewDocumentAndFileType(myFileType, replaceDocument); myFilterPanel.setProfile(profile); initiateValidation(); @@ -1020,7 +1028,7 @@ public class StructuralSearchDialog extends DialogWrapper { } matchOptions.setFileType(myFileType); matchOptions.setDialect(myDialect); - matchOptions.setPatternContext(myContext); + matchOptions.setPatternContext(myPatternContext); matchOptions.setSearchPattern(getPattern(mySearchCriteriaEdit)); matchOptions.setCaseSensitiveMatch(myMatchCase.isSelected()); diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java index 6e283aa26d7d..01071214b791 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java @@ -258,10 +258,10 @@ public class UIUtil { public static LanguageFileType detectFileType(@NotNull SearchContext searchContext) { final PsiFile file = searchContext.getFile(); - PsiElement context = file; + PsiElement context = null; final Editor editor = searchContext.getEditor(); - if (editor != null && context != null) { + if (editor != null && file != null) { final int offset = editor.getCaretModel().getOffset(); context = InjectedLanguageManager.getInstance(searchContext.getProject()).findInjectedElementAt(file, offset); if (context == null) { @@ -270,6 +270,9 @@ public class UIUtil { if (context != null) { context = context.getParent(); } + if (context == null) { + context = file; + } } if (context != null) { final Language language = context.getLanguage(); @@ -283,9 +286,10 @@ public class UIUtil { } @NotNull - public static Document createDocument(@NotNull Project project, @NotNull LanguageFileType fileType, Language dialect, @NotNull String text, - @NotNull StructuralSearchProfile profile) { - PsiFile codeFragment = profile.createCodeFragment(project, text); + public static Document createDocument(@NotNull Project project, @NotNull LanguageFileType fileType, Language dialect, + PatternContext patternContext, @NotNull String text, @NotNull StructuralSearchProfile profile) { + final String contextId = (patternContext == null) ? null : patternContext.getId(); + PsiFile codeFragment = profile.createCodeFragment(project, text, contextId); if (codeFragment == null) { codeFragment = createFileFragment(project, fileType, dialect, text); } @@ -302,7 +306,7 @@ public class UIUtil { @NotNull public static Editor createEditor(@NotNull Project project, @NotNull LanguageFileType fileType, Language dialect, @NotNull String text, @NotNull StructuralSearchProfile profile) { - PsiFile codeFragment = profile.createCodeFragment(project, text); + PsiFile codeFragment = profile.createCodeFragment(project, text, null); if (codeFragment == null) { codeFragment = createFileFragment(project, fileType, dialect, text); } diff --git a/plugins/groovy/structuralsearch-groovy/src/com/intellij/structuralsearch/GroovyStructuralSearchProfile.java b/plugins/groovy/structuralsearch-groovy/src/com/intellij/structuralsearch/GroovyStructuralSearchProfile.java index 58634ba06022..ec9b5923eb7f 100644 --- a/plugins/groovy/structuralsearch-groovy/src/com/intellij/structuralsearch/GroovyStructuralSearchProfile.java +++ b/plugins/groovy/structuralsearch-groovy/src/com/intellij/structuralsearch/GroovyStructuralSearchProfile.java @@ -5,8 +5,11 @@ import com.intellij.codeInsight.template.TemplateContextType; import com.intellij.lang.Language; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.project.Project; -import com.intellij.psi.*; +import com.intellij.psi.PsiCodeFragment; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiIdentifier; import com.intellij.psi.tree.TokenSet; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.GroovyFileType; @@ -14,12 +17,15 @@ import org.jetbrains.plugins.groovy.debugger.fragments.GroovyCodeFragment; import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; import org.jetbrains.plugins.groovy.template.GroovyTemplateContextType; +import java.util.List; + /** * @author Eugene.Kudelevsky */ public class GroovyStructuralSearchProfile extends StructuralSearchProfileBase { - public static final String FILE_CONTEXT = "File"; - public static final String CLASS_CONTEXT = "Class"; + public static final PatternContext FILE_CONTEXT = new PatternContext("File", "Default"); + public static final PatternContext CLASS_CONTEXT = new PatternContext("Class", "Class Member"); + private static final List PATTERN_CONTEXTS = ContainerUtil.immutableList(FILE_CONTEXT, CLASS_CONTEXT); private static final TokenSet VARIABLE_DELIMITERS = TokenSet.create(GroovyTokenTypes.mCOMMA, GroovyTokenTypes.mSEMI); @@ -31,8 +37,8 @@ public class GroovyStructuralSearchProfile extends StructuralSearchProfileBase { @NotNull @Override - public String[] getContextNames() { - return new String[] {FILE_CONTEXT, CLASS_CONTEXT}; + public List getPatternContexts() { + return PATTERN_CONTEXTS; } @NotNull @@ -48,7 +54,7 @@ public class GroovyStructuralSearchProfile extends StructuralSearchProfileBase { } @Override - public PsiCodeFragment createCodeFragment(Project project, String text) { + public PsiCodeFragment createCodeFragment(Project project, String text, String contextId) { return new GroovyCodeFragment(project, text); } @@ -58,9 +64,10 @@ public class GroovyStructuralSearchProfile extends StructuralSearchProfileBase { return GroovyTemplateContextType.class; } + @NotNull @Override - public String getContext(@NotNull String pattern, @Nullable Language language, String contextName) { - return CLASS_CONTEXT.equals(contextName) + public String getContext(@NotNull String pattern, @Nullable Language language, String contextId) { + return CLASS_CONTEXT.getId().equals(contextId) ? "class AAAAA { " + PATTERN_PLACEHOLDER + " }" : PATTERN_PLACEHOLDER; } diff --git a/plugins/groovy/structuralsearch-groovy/testSrc/com/intellij/structuralsearch/GroovyStructuralSearchTest.java b/plugins/groovy/structuralsearch-groovy/testSrc/com/intellij/structuralsearch/GroovyStructuralSearchTest.java index 730f05dac8cd..bdcb40392cb8 100644 --- a/plugins/groovy/structuralsearch-groovy/testSrc/com/intellij/structuralsearch/GroovyStructuralSearchTest.java +++ b/plugins/groovy/structuralsearch-groovy/testSrc/com/intellij/structuralsearch/GroovyStructuralSearchTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 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-2019 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; import com.intellij.dupLocator.equivalence.EquivalenceDescriptorProvider; @@ -112,7 +112,7 @@ public class GroovyStructuralSearchTest extends StructuralSearchTestCase { doTest(s, "def $name$ = {\n" + " '_T+\n" + "}", 0, 0); - final String old = options.getPatternContext(); + final PatternContext old = options.getPatternContext(); try { options.setPatternContext(GroovyStructuralSearchProfile.CLASS_CONTEXT); doTest(s, "def $name$ = {\n" +