package fr.adrienbrault.idea.symfony2plugin.config.yaml;
import com.intellij.patterns.*;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.ProcessingContext;
import fr.adrienbrault.idea.symfony2plugin.util.psi.ParentPathPatternCondition;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.yaml.YAMLElementTypes;
import org.jetbrains.yaml.YAMLLanguage;
import org.jetbrains.yaml.YAMLTokenTypes;
import org.jetbrains.yaml.psi.*;
import org.jetbrains.yaml.psi.impl.YAMLPlainTextImpl;
/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class YamlElementPatternHelper {
/**
* "@foo", '@foo', @foo
*/
private static final ElementTypePatternCondition SCALAR_ELEMENT_TYPES = new ElementTypePatternCondition(
YAMLTokenTypes.TEXT, YAMLTokenTypes.SCALAR_STRING, YAMLTokenTypes.SCALAR_DSTRING
);
/**
* services: ~
*/
private static PatternCondition<YAMLKeyValue> YAML_KEY_SERVICES = new YAMLKeyValuePatternCondition("services");
/**
* auto complete on
*
* keyName: refer|
*
* @param keyName
*/
public static ElementPattern<PsiElement> getOrmSingleLineScalarKey(String... keyName) {
return getKeyPattern(keyName).inFile(getOrmFilePattern()).withLanguage(YAMLLanguage.INSTANCE);
}
public static ElementPattern<PsiElement> getSingleLineScalarKey(String... keyName) {
// key: | and key: "quote" is valid here
// getKeyPattern
return PlatformPatterns.or(
PlatformPatterns
.psiElement(YAMLTokenTypes.TEXT)
.withParent(PlatformPatterns.psiElement(YAMLScalar.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(
PlatformPatterns.string().oneOf(keyName)
)
))
.withLanguage(YAMLLanguage.INSTANCE)
,
PlatformPatterns
.psiElement(YAMLTokenTypes.SCALAR_DSTRING)
.withParent(PlatformPatterns.psiElement(YAMLScalar.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(
PlatformPatterns.string().oneOf(keyName)
)
))
.withLanguage(YAMLLanguage.INSTANCE),
PlatformPatterns
.psiElement(YAMLTokenTypes.SCALAR_STRING)
.withParent(PlatformPatterns.psiElement(YAMLScalar.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(
PlatformPatterns.string().oneOf(keyName)
)
))
.withLanguage(YAMLLanguage.INSTANCE)
);
}
/**
* provides auto complete on
*
* keyName:
* refer|
* refer|: xxx
* refer|
*
* @param keyName key name
*/
public static ElementPattern<PsiElement> getOrmParentLookup(String keyName) {
return PlatformPatterns.or(
// match
//
// keyName:
// refer|: xxx
PlatformPatterns
.psiElement(YAMLTokenTypes.SCALAR_KEY)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLCompoundValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(
PlatformPatterns.string().equalTo(keyName)
)
)
)
)
.inFile(getOrmFilePattern())
.withLanguage(YAMLLanguage.INSTANCE),
// match
//
// keyName:
// xxx: xxx
// refer|
PlatformPatterns
.psiElement(YAMLPlainTextImpl.class)
.withParent(PlatformPatterns
.psiElement(YAMLCompoundValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(
PlatformPatterns.string().equalTo(keyName)
)
)
)
.inFile(getOrmFilePattern())
.withLanguage(YAMLLanguage.INSTANCE),
// match
//
// keyName:
// refer|
// xxx: xxx
getKeyPattern(keyName)
.inFile(getOrmFilePattern())
.withLanguage(YAMLLanguage.INSTANCE)
);
}
/**
* provides auto complete on
*
* keyName:
* refer|
* refer|: xxx
* refer|
*
* @param keyName key name
*/
public static ElementPattern<PsiElement> getParentKeyName(String keyName) {
return PlatformPatterns.or(
// match
//
// keyName:
// refer|: xxx
PlatformPatterns
.psiElement(YAMLTokenTypes.SCALAR_KEY)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLCompoundValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(
PlatformPatterns.string().equalTo(keyName)
)
)
)
)
.withLanguage(YAMLLanguage.INSTANCE),
// match
//
// keyName:
// xxx: xxx
// refer|
PlatformPatterns
.psiElement(YAMLPlainTextImpl.class)
.withParent(PlatformPatterns
.psiElement(YAMLCompoundValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(
PlatformPatterns.string().equalTo(keyName)
)
)
)
.withLanguage(YAMLLanguage.INSTANCE),
// match
//
// keyName:
// refer|
// xxx: xxx
getKeyPattern(keyName)
.withLanguage(YAMLLanguage.INSTANCE)
);
}
/**
* services:
* My<caret>Class: ~
*/
public static ElementPattern<PsiElement> getServicesKeyPattern() {
return PlatformPatterns
.psiElement(YAMLTokenTypes.SCALAR_KEY).withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class).withParent(
PlatformPatterns.psiElement(YAMLMapping.class).withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class).with(YAML_KEY_SERVICES)
)
)
);
}
/**
* Proxy for getWithFirstRootKey to filter with file name condition
*/
public static ElementPattern<? extends PsiElement> getOrmRoot() {
return PlatformPatterns.and(PlatformPatterns.psiElement().with(new PatternCondition<PsiElement>("Doctrine file") {
@Override
public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext processingContext) {
return getOrmFilePattern().accepts(psiElement.getContainingFile());
}
}), getWithFirstRootKey());
}
public static ElementPattern<PsiElement> getWithFirstRootKey() {
return PlatformPatterns.or(
// foo:
// <caret>
PlatformPatterns
.psiElement().with(new ParentPathPatternCondition(
YAMLScalar.class, YAMLMapping.class,
YAMLKeyValue.class, YAMLMapping.class,
YAMLDocument.class
))
.withLanguage(YAMLLanguage.INSTANCE),
// foo:
// <caret> (on incomplete)
PlatformPatterns
.psiElement().afterLeaf(
PlatformPatterns.psiElement(YAMLTokenTypes.INDENT).with(
new ParentPathPatternCondition(YAMLKeyValue.class, YAMLMapping.class, YAMLDocument.class)
)
)
.withLanguage(YAMLLanguage.INSTANCE),
// match
//
// foo:
// <caret>: bar
// <caret>:
// <caret>a:
PlatformPatterns
.psiElement().with(new ParentPathPatternCondition(
YAMLScalar.class, YAMLKeyValue.class,
YAMLMapping.class, YAMLKeyValue.class,
YAMLMapping.class, YAMLDocument.class)
)
.withLanguage(YAMLLanguage.INSTANCE),
// foo:
// fo<caret>:
PlatformPatterns.psiElement().with(new ParentPathPatternCondition(
YAMLKeyValue.class, YAMLMapping.class,
YAMLKeyValue.class, YAMLMapping.class,
YAMLDocument.class)
)
);
}
/**
* provides auto complete on
*
* tree:
* xxx:
* refer|
* refer|: xxx
* refer|
*/
public static ElementPattern<PsiElement> getFilterOnPrevParent(String... tree) {
return PlatformPatterns.or(
// match
//
// tree:
// xxx:
// refer|: xxx
PlatformPatterns
.psiElement(YAMLTokenTypes.SCALAR_KEY)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLCompoundValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLCompoundValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(PlatformPatterns
.string().oneOfIgnoreCase(tree)
)
)
)
)
)
)
.inFile(getOrmFilePattern())
.withLanguage(YAMLLanguage.INSTANCE),
// match
//
// tree:
// xxx:
// xxx: xxx
// refer|
PlatformPatterns
.psiElement(YAMLPlainTextImpl.class)
.withParent(PlatformPatterns
.psiElement(YAMLCompoundValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLCompoundValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(PlatformPatterns
.string().oneOfIgnoreCase(tree)
)
)
)
)
)
.inFile(getOrmFilePattern())
.withLanguage(YAMLLanguage.INSTANCE),
// match
//
// tree:
// xxx:
// refer|
// xxx: xxx
PlatformPatterns
.psiElement(YAMLPlainTextImpl.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLCompoundValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(PlatformPatterns
.string().oneOfIgnoreCase(tree)
)
)
)
)
.inFile(getOrmFilePattern())
.withLanguage(YAMLLanguage.INSTANCE)
);
}
/**
* services:
* foo:
* class: '</caret>'
*/
public static ElementPattern<PsiElement> getThreeLevelKeyPattern(String... tree) {
return PlatformPatterns.psiElement().withParent(
PlatformPatterns.psiElement(YAMLScalar.class).withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class).withParent(
PlatformPatterns.psiElement(YAMLMapping.class).withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class).withParent(
PlatformPatterns.psiElement(YAMLMapping.class).withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class).withName(PlatformPatterns
.string().oneOfIgnoreCase(tree)
)
)
)
)
)
)
);
}
/**
* simplified getFilterOnPrevParent :)
*
* services:
* foo.name:
* "complete": foo
*/
public static ElementPattern<PsiElement> getSuperParentArrayKey(String... tree) {
return PlatformPatterns.or(
// foo:
// <caret> (on incomplete)
PlatformPatterns.psiElement().afterLeaf(
PlatformPatterns.psiElement(YAMLTokenTypes.INDENT).withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class).withParent(
PlatformPatterns.psiElement(YAMLMapping.class).withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class).withName(PlatformPatterns
.string().oneOfIgnoreCase(tree)
)
)
)
)
),
/**
* services:
* foo:
* cla<caret>:
*/
PlatformPatterns.psiElement().withParent(
PlatformPatterns.psiElement(YAMLScalar.class).withParent(
PlatformPatterns.psiElement(YAMLMapping.class).withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class).withParent(
PlatformPatterns.psiElement(YAMLMapping.class).withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class).withName(PlatformPatterns
.string().oneOfIgnoreCase(tree)
)
)
)
)
)
),
// match
//
// tree:
// xxx:
// refer|: xxx
PlatformPatterns
.psiElement(YAMLTokenTypes.SCALAR_KEY)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLMapping.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withParent(PlatformPatterns
.psiElement(YAMLMapping.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(PlatformPatterns
.string().oneOfIgnoreCase(tree)
)
)
)
)
)
)
.withLanguage(YAMLLanguage.INSTANCE)
);
}
/**
* find common services: @foo, "@foo", '@foo'
*/
public static ElementPattern<PsiElement> getServiceDefinition() {
return PlatformPatterns
.psiElement().with(SCALAR_ELEMENT_TYPES)
.withParent(
PlatformPatterns.psiElement(YAMLScalar.class).with(new YAMLScalarValueStartsWithPatternCondition("@"))
)
.withLanguage(YAMLLanguage.INSTANCE);
}
/**
* find common service parameter: %foo%, %foo%, %foo%
*/
public static ElementPattern<PsiElement> getServiceParameterDefinition() {
return PlatformPatterns
.psiElement().with(SCALAR_ELEMENT_TYPES)
.withParent(
PlatformPatterns.psiElement(YAMLScalar.class).with(new YAMLScalarValueStartsWithPatternCondition("%"))
)
.withLanguage(YAMLLanguage.INSTANCE);
}
private static ElementPattern<? extends PsiFile> getOrmFilePattern() {
return PlatformPatterns.psiFile().withName(PlatformPatterns.string().andOr(
PlatformPatterns.string().endsWith("orm.yml"),
PlatformPatterns.string().endsWith("couchdb.yml"),
PlatformPatterns.string().endsWith("odm.yml"),
PlatformPatterns.string().endsWith("mongodb.yml"),
PlatformPatterns.string().endsWith("document.yml")
));
}
/**
* foo: <caret>
* foo: a<caret>
*/
private static PsiElementPattern.Capture<PsiElement> getKeyPattern(String... keyName) {
return PlatformPatterns
.psiElement()
.withParent(PlatformPatterns.psiElement(YAMLScalar.class).withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(
PlatformPatterns.string().oneOfIgnoreCase(keyName)
)
));
}
/**
* Possible config key completion
* In document root or key value context
*/
public static ElementPattern<PsiElement> getConfigKeyPattern() {
return PlatformPatterns.psiElement().withParent(PlatformPatterns.or(
PlatformPatterns.psiElement(YAMLDocument.class),
PlatformPatterns.psiElement(YAMLScalar.class),
PlatformPatterns.psiElement(YAMLKeyValue.class)
)).inFile(
// not should fire this in all yaml files
getConfigFileNamePattern()
);
}
/**
* config.yml, config_dev.yml,
* security.yml, security_dev.yml
*/
@NotNull
public static PsiFilePattern.Capture<PsiFile> getConfigFileNamePattern() {
return PlatformPatterns.psiFile().withName(PlatformPatterns.string().matches("(security|config).*\\.yml"));
}
/**
* Get service before comma
*
* ["@service', createNewsletterManager|]
* [@service, createNewsletterManager|]
* ['@service', createNewsletterManager|]
*/
public static ElementPattern<? extends PsiElement> getAfterCommaPattern() {
return PlatformPatterns.psiElement().withParent(
PlatformPatterns.psiElement(YAMLScalar.class).withParent(
PlatformPatterns.psiElement(YAMLSequenceItem.class).afterLeafSkipping(
PlatformPatterns.psiElement(PsiWhiteSpace.class),
PlatformPatterns.psiElement().withText(",")
)
)
);
}
/**
* Get service before comma
*
* ["@service', createNewsletterManager|]
* [@service, createNewsletterManager|]
* ['@service', createNewsletterManager|]
*/
public static ElementPattern<? extends PsiElement> getPreviousCommaSibling() {
return PlatformPatterns.or(
PlatformPatterns
.psiElement(YAMLScalar.class)
.beforeLeafSkipping(
PlatformPatterns.psiElement(PsiWhiteSpace.class),
PlatformPatterns.psiElement().withText(",")
)
);
}
/**
* parameters:
* foo.example.class: |
*
*/
static PsiElementPattern.Capture<? extends PsiElement> getParameterClassPattern() {
return PlatformPatterns
.psiElement()
.withParent(PlatformPatterns
.psiElement(YAMLScalar.class)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(
PlatformPatterns.string().endsWith(".class")
)
.withParent(PlatformPatterns
.psiElement(YAMLElementTypes.MAPPING)
.withParent(PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName("parameters")
)
)
)
);
}
public static PsiElementPattern.Capture<PsiElement> getInsideServiceKeyPattern() {
return getInsideKeyValue("services", "parameters");
}
public static PsiElementPattern.Capture<PsiElement> getInsideKeyValue(String... keys) {
return PlatformPatterns
.psiElement()
.inside(
PlatformPatterns
.psiElement(YAMLKeyValue.class)
.withName(
PlatformPatterns.string().oneOf(keys)
)
);
}
/**
* services:
* i<caret>d: []
*/
public static PsiElementPattern.Capture<PsiElement> getServiceIdKeyPattern() {
return PlatformPatterns.psiElement(YAMLTokenTypes.SCALAR_KEY)
.withParent(PlatformPatterns.psiElement(YAMLKeyValue.class)
.withParent(PlatformPatterns.psiElement(YAMLCompoundValue.class)
.withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class)
.withName(PlatformPatterns.string().oneOfIgnoreCase("services"))
)
)
);
}
/**
* services:
* i<caret>d: []
*/
public static PsiElementPattern.Capture<YAMLKeyValue> getServiceIdKeyValuePattern() {
return PlatformPatterns.psiElement(YAMLKeyValue.class)
.withParent(PlatformPatterns.psiElement(YAMLMapping.class)
.withParent(PlatformPatterns.psiElement(YAMLKeyValue.class).withName("services"))
);
}
/**
* PsiFile / Document:
* serv<caret>ices: ~
*/
public static PsiElementPattern.Capture<PsiElement> getRootConfigKeyPattern() {
return PlatformPatterns.psiElement(YAMLTokenTypes.SCALAR_KEY).withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class).withParent(
PlatformPatterns.psiElement(YAMLMapping.class).withParent(
PlatformPatterns.psiElement(YAMLDocument.class)
)
)
).inFile(getConfigFileNamePattern());
}
/**
* tags: [ foobar ]
*/
public static PsiElementPattern.Capture<PsiElement> getTagsAsSequencePattern() {
return PlatformPatterns.psiElement().withParent(
PlatformPatterns.psiElement(YAMLScalar.class).withParent(
PlatformPatterns.psiElement(YAMLSequenceItem.class).withParent(
PlatformPatterns.psiElement(YAMLSequence.class).withParent(
PlatformPatterns.psiElement(YAMLKeyValue.class).withName("tags")
)
)
)
);
}
/**
* Match elements types
*/
private static class ElementTypePatternCondition extends PatternCondition<PsiElement> {
private final IElementType[] elementType;
ElementTypePatternCondition(IElementType... elementType) {
super("IElementType matcher");
this.elementType = elementType;
}
@Override
public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext processingContext) {
return ArrayUtils.contains(elementType, psiElement.getNode().getElementType());
}
}
private static class YAMLScalarValueStartsWithPatternCondition extends PatternCondition<YAMLScalar> {
@NotNull
private final String value;
YAMLScalarValueStartsWithPatternCondition(@NotNull String value) {
super("YAMLScalar startsWith");
this.value = value;
}
@Override
public boolean accepts(@NotNull YAMLScalar yamlScalar, ProcessingContext processingContext) {
return StringUtils.startsWith(yamlScalar.getTextValue(), value);
}
}
private static class YAMLKeyValuePatternCondition extends PatternCondition<YAMLKeyValue> {
@NotNull
private final String keyText;
YAMLKeyValuePatternCondition(@NotNull String keyText) {
super("yaml " + keyText +" key");
this.keyText = keyText;
}
@Override
public boolean accepts(@NotNull YAMLKeyValue yamlKeyValue, ProcessingContext processingContext) {
return this.keyText.equals(yamlKeyValue.getKeyText());
}
}
}