SSR: rework pattern context and move up into structural search profile

GitOrigin-RevId: 5468a2907780483d552fd90dee8f85ac6dfb5eb7
This commit is contained in:
Bas Leijdekkers
2019-06-08 19:12:26 +03:00
committed by intellij-monorepo-bot
parent cbceb3ec66
commit 86c05e25f1
18 changed files with 260 additions and 237 deletions
@@ -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);
}
@@ -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();
}
}
@@ -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<PatternContext> {
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 + ')';
}
}
@@ -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<StructuralSearchProfile> 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<PsiElement> 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<PatternContext> 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<? extends PsiElement> nodes, boolean completePattern, boolean target) {
public final boolean isApplicableConstraint(String constraintName,
List<? extends PsiElement> nodes,
boolean completePattern,
boolean target) {
if (nodes.isEmpty()) {
return isApplicableConstraint(constraintName, (PsiElement)null, completePattern, target);
}
@@ -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<PsiElement> 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) {
@@ -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<PatternContext> 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));
}
}
@@ -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 : "<QQQ>" + text + "</QQQ>";
@@ -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();
}
@@ -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;
}
@@ -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) {
@@ -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;
@@ -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();
}
}
@@ -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<FileTypeInfo> {
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<FileTypeInfo> model = (DefaultComboBoxModel<FileTypeInfo>)getModel();
for (int i = 0; i < model.getSize(); i++) {
final FileTypeInfo info = model.getElementAt(i);
@@ -53,23 +54,6 @@ public class FileTypeSelector extends ComboBox<FileTypeInfo> {
}
}
@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<FileTypeInfo> createModel() {
final List<LanguageFileType> types = new ArrayList<>();
@@ -81,30 +65,25 @@ public class FileTypeSelector extends ComboBox<FileTypeInfo> {
Collections.sort(types, (o1, o2) -> o1.getDescription().compareToIgnoreCase(o2.getDescription()));
final List<FileTypeInfo> 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<PatternContext> 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<FileTypeInfo> {
return new MyComboBoxModel(infos);
}
private static boolean isDuplicated(@NotNull LanguageFileType fileType, @NotNull List<? extends FileType> types) {
final String description = fileType.getDescription();
return types.stream().anyMatch(type -> type != fileType && description.equals(type.getDescription()));
}
private static class MyComboBoxModel extends DefaultComboBoxModel<FileTypeInfo> {
MyComboBoxModel(List<FileTypeInfo> infos) {
super(infos.toArray(FileTypeInfo.EMPTY_ARRAY));
@@ -134,14 +108,8 @@ public class FileTypeSelector extends ComboBox<FileTypeInfo> {
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
@@ -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;
@@ -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<RangeHighlighter> 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<PatternContext> 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());
@@ -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);
}
@@ -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<PatternContext> 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<PatternContext> 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;
}
@@ -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" +