Skip to content

Commit f11aacc

Browse files
authored
Merge pull request #1364 from Haehnchen/King2500-feature/yaml-tags
King2500 feature/yaml tags
2 parents 6a692ab + 5b80330 commit f11aacc

File tree

18 files changed

+1005
-0
lines changed

18 files changed

+1005
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package fr.adrienbrault.idea.symfony2plugin.completion.yaml;
2+
3+
import com.intellij.codeInsight.completion.CompletionContributor;
4+
import com.intellij.codeInsight.completion.CompletionType;
5+
import com.intellij.psi.PsiElement;
6+
import fr.adrienbrault.idea.symfony2plugin.config.yaml.YamlElementPatternHelper;
7+
import fr.adrienbrault.idea.symfony2plugin.util.completion.YamlKeywordsCompletionProvider;
8+
import fr.adrienbrault.idea.symfony2plugin.util.completion.YamlTagCompletionProvider;
9+
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.yaml.psi.YAMLKeyValue;
11+
import org.jetbrains.yaml.psi.YAMLSequence;
12+
import org.jetbrains.yaml.psi.YAMLSequenceItem;
13+
14+
/**
15+
* @author Thomas Schulz <mail@king2500.net>
16+
*/
17+
public class YamlCompletionContributor extends CompletionContributor {
18+
public YamlCompletionContributor() {
19+
// config:
20+
// key: !<caret>
21+
extend(
22+
CompletionType.BASIC,
23+
YamlElementPatternHelper.getSingleLineTextOrTag(),
24+
new YamlTagCompletionProvider()
25+
);
26+
27+
// config:
28+
// key: <caret>
29+
extend(
30+
CompletionType.BASIC,
31+
YamlElementPatternHelper.getSingleLineText(),
32+
new YamlKeywordsCompletionProvider()
33+
);
34+
}
35+
36+
@Override
37+
public boolean invokeAutoPopup(@NotNull PsiElement position, char typeChar) {
38+
// Only for Yaml tag places (scalar values)
39+
// key: !<caret>
40+
if (!YamlElementPatternHelper.getSingleLineTextOrTag().accepts(position)
41+
&& !(position.getPrevSibling() instanceof YAMLKeyValue)
42+
&& !(position.getParent() instanceof YAMLSequenceItem)
43+
&& !(position.getParent() instanceof YAMLSequence)
44+
) {
45+
return super.invokeAutoPopup(position, typeChar);
46+
}
47+
48+
return typeChar == '!';
49+
}
50+
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/completion/yaml/YamlGotoCompletionRegistrar.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import fr.adrienbrault.idea.symfony2plugin.config.yaml.YamlElementPatternHelper;
99
import fr.adrienbrault.idea.symfony2plugin.routing.RouteGotoCompletionProvider;
1010
import fr.adrienbrault.idea.symfony2plugin.templating.TemplateGotoCompletionRegistrar;
11+
import fr.adrienbrault.idea.symfony2plugin.util.completion.PhpConstGotoCompletionProvider;
1112
import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper;
1213
import org.jetbrains.annotations.NotNull;
1314
import org.jetbrains.annotations.Nullable;
@@ -40,6 +41,12 @@ public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
4041
YamlElementPatternHelper.getSingleLineScalarKey("decorates"),
4142
MyDecoratedServiceCompletionProvider::new
4243
);
44+
45+
// key: !php/const <caret>
46+
registrar.register(
47+
YamlElementPatternHelper.getPhpConstPattern(),
48+
PhpConstGotoCompletionProvider::new
49+
);
4350
}
4451

4552
private static class MyDecoratedServiceCompletionProvider extends DecoratedServiceCompletionProvider {

src/main/java/fr/adrienbrault/idea/symfony2plugin/config/yaml/YamlElementPatternHelper.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,37 @@ public static ElementPattern<PsiElement> getSingleLineScalarKey(String... keyNam
124124
);
125125
}
126126

127+
public static ElementPattern<PsiElement> getSingleLineText() {
128+
return
129+
PlatformPatterns
130+
.psiElement(YAMLTokenTypes.TEXT)
131+
.withParent(PlatformPatterns.psiElement(YAMLScalar.class)
132+
.withParent(PlatformPatterns.or(
133+
PlatformPatterns.psiElement(YAMLKeyValue.class),
134+
PlatformPatterns.psiElement(YAMLSequenceItem.class)
135+
)
136+
)
137+
)
138+
.withLanguage(YAMLLanguage.INSTANCE)
139+
;
140+
}
141+
142+
public static ElementPattern<PsiElement> getSingleLineTextOrTag() {
143+
return PlatformPatterns.or(
144+
PlatformPatterns
145+
.psiElement(YAMLTokenTypes.TEXT)
146+
.withParent(PlatformPatterns.psiElement(YAMLScalar.class)
147+
// .withParent(PlatformPatterns
148+
// .psiElement(YAMLKeyValue.class)
149+
// )
150+
)
151+
.withLanguage(YAMLLanguage.INSTANCE),
152+
PlatformPatterns
153+
.psiElement(YAMLTokenTypes.TAG)
154+
.withLanguage(YAMLLanguage.INSTANCE)
155+
);
156+
}
157+
127158
/**
128159
* provides auto complete on
129160
*
@@ -721,6 +752,25 @@ public static ElementPattern<PsiElement> getTaggedServicePattern() {
721752
);
722753
}
723754

755+
/**
756+
* !php/const <caret>
757+
*/
758+
public static ElementPattern<PsiElement> getPhpConstPattern() {
759+
return
760+
PlatformPatterns
761+
.psiElement()
762+
.afterLeafSkipping(
763+
PlatformPatterns.or(
764+
PlatformPatterns.psiElement(PsiWhiteSpace.class),
765+
PlatformPatterns.psiElement(YAMLTokenTypes.WHITESPACE)
766+
),
767+
PlatformPatterns.or(
768+
PlatformPatterns.psiElement().withText("!php/const"),
769+
PlatformPatterns.psiElement().withText("!php/const:")
770+
)
771+
);
772+
}
773+
724774
/**
725775
* services:
726776
* _defaults:

src/main/java/fr/adrienbrault/idea/symfony2plugin/util/PhpElementsUtil.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,10 @@ static public ArrayList<Method> getClassPublicMethod(PhpClass phpClass) {
427427
return methods;
428428
}
429429

430+
static public boolean hasClassConstantFields(@NotNull PhpClass phpClass) {
431+
return phpClass.getFields().stream().anyMatch(Field::isConstant);
432+
}
433+
430434
@Nullable
431435
static public String getArrayHashValue(ArrayCreationExpression arrayCreationExpression, String keyName) {
432436
ArrayHashElement translationArrayHashElement = PsiElementUtils.getChildrenOfType(arrayCreationExpression, PlatformPatterns.psiElement(ArrayHashElement.class)
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package fr.adrienbrault.idea.symfony2plugin.util.completion;
2+
3+
import com.intellij.codeInsight.completion.CompletionResultSet;
4+
import com.intellij.codeInsight.completion.CompletionUtil;
5+
import com.intellij.codeInsight.completion.InsertHandler;
6+
import com.intellij.codeInsight.completion.InsertionContext;
7+
import com.intellij.codeInsight.lookup.LookupElement;
8+
import com.intellij.openapi.actionSystem.ActionManager;
9+
import com.intellij.openapi.keymap.KeymapUtil;
10+
import com.intellij.psi.PsiElement;
11+
import com.jetbrains.php.PhpBundle;
12+
import com.jetbrains.php.PhpIndex;
13+
import com.jetbrains.php.completion.PhpCompletionUtil;
14+
import com.jetbrains.php.completion.PhpLookupElement;
15+
import com.jetbrains.php.completion.PhpVariantsUtil;
16+
import com.jetbrains.php.completion.insert.PhpInsertHandlerUtil;
17+
import com.jetbrains.php.lang.psi.elements.Field;
18+
import com.jetbrains.php.lang.psi.elements.PhpClass;
19+
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
20+
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
21+
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProviderLookupArguments;
22+
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
23+
import org.jetbrains.annotations.NotNull;
24+
25+
import java.util.ArrayList;
26+
import java.util.Arrays;
27+
import java.util.Collection;
28+
import java.util.List;
29+
import java.util.stream.Collectors;
30+
31+
public class PhpConstGotoCompletionProvider extends GotoCompletionProvider {
32+
33+
private static final String[] SPECIAL_STUB_CONSTANTS = new String[]{"true", "false", "null"};
34+
private static final String SCOPE_OPERATOR = "::";
35+
36+
public PhpConstGotoCompletionProvider(@NotNull PsiElement element) {
37+
super(element);
38+
}
39+
40+
@Override
41+
public void getLookupElements(@NotNull GotoCompletionProviderLookupArguments arguments) {
42+
PhpIndex phpIndex = PhpIndex.getInstance(this.getProject());
43+
CompletionResultSet resultSet = arguments.getResultSet();
44+
45+
final String prefix = getElement().getText().replace(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED, "");
46+
47+
// Class constants: !php/const Foo::<caret>
48+
if (prefix.contains(SCOPE_OPERATOR)) {
49+
String classFQN = prefix.substring(0, getElement().getText().indexOf(SCOPE_OPERATOR));
50+
PhpClass phpClass = PhpElementsUtil.getClassInterface(this.getProject(), classFQN);
51+
if (phpClass != null) {
52+
// reset the prefix matcher, starting after ::
53+
resultSet = resultSet.withPrefixMatcher(prefix.substring(prefix.indexOf(SCOPE_OPERATOR) + 2));
54+
resultSet.addAllElements(PhpVariantsUtil.getLookupItems(phpClass.getFields().stream().filter(Field::isConstant).collect(Collectors.toList()), false, null));
55+
}
56+
return;
57+
}
58+
59+
Collection<LookupElement> elements = new ArrayList<>();
60+
61+
// Global constants: !php/const BAR
62+
for (String constantName : phpIndex.getAllConstantNames(resultSet.getPrefixMatcher())) {
63+
if (Arrays.asList(SPECIAL_STUB_CONSTANTS).contains(constantName)) {
64+
continue;
65+
}
66+
elements.addAll(PhpVariantsUtil.getLookupItems(phpIndex.getConstantsByName(constantName), false, null));
67+
}
68+
69+
if (arguments.getParameters().getInvocationCount() <= 1) {
70+
String completionShortcut = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("CodeCompletion"));
71+
resultSet.addLookupAdvertisement(PhpBundle.message("completion.press.again.to.see.more.variants", completionShortcut));
72+
73+
// Classes and interfaces: !php/const Foo
74+
for (String className : phpIndex.getAllClassNames(resultSet.getPrefixMatcher())) {
75+
addAllClasses(elements, phpIndex.getClassesByName(className));
76+
addAllClasses(elements, phpIndex.getInterfacesByName(className));
77+
}
78+
} else {
79+
// Constants from all classes and interfaces: !php/const Foo::BAR
80+
for (String className : phpIndex.getAllClassNames(null)) {
81+
addAllClassConstants(elements, phpIndex.getClassesByName(className));
82+
addAllClassConstants(elements, phpIndex.getInterfacesByName(className));
83+
}
84+
}
85+
86+
arguments.addAllElements(elements);
87+
}
88+
89+
private void addAllClasses(Collection<LookupElement> elements, Collection<PhpClass> classes) {
90+
for (PhpClass phpClass : classes) {
91+
// Filter by classes only with constants (including inherited constants)
92+
if (PhpElementsUtil.hasClassConstantFields(phpClass)) {
93+
elements.add(wrapClassInsertHandler(new MyPhpLookupElement(phpClass)));
94+
}
95+
}
96+
}
97+
98+
private void addAllClassConstants(Collection<LookupElement> elements, Collection<PhpClass> classes) {
99+
for (PhpClass phpClass : classes) {
100+
// All class constants
101+
List<Field> fields = Arrays.stream(phpClass.getOwnFields()).filter(Field::isConstant).collect(Collectors.toList());
102+
for (PhpNamedElement field : fields) {
103+
// Foo::BAR
104+
String lookupString = phpClass.getName() + SCOPE_OPERATOR + field.getName();
105+
elements.add(wrapClassConstInsertHandler(new MyPhpLookupElement(field, lookupString)));
106+
}
107+
}
108+
}
109+
110+
private static MyPhpLookupElement wrapClassInsertHandler(MyPhpLookupElement lookupElement) {
111+
return lookupElement.withInsertHandler(PhpClassWithScopeOperatorInsertHandler.getInstance());
112+
}
113+
114+
private static MyPhpLookupElement wrapClassConstInsertHandler(MyPhpLookupElement lookupElement) {
115+
return lookupElement.withInsertHandler(PhpReferenceTrimBackslashInsertHandler.getInstance());
116+
}
117+
118+
private static class MyPhpLookupElement extends PhpLookupElement {
119+
120+
MyPhpLookupElement(@NotNull PhpNamedElement namedElement) {
121+
super(namedElement);
122+
}
123+
124+
MyPhpLookupElement(@NotNull PhpNamedElement namedElement, @NotNull String lookupString) {
125+
super(namedElement);
126+
this.lookupString = lookupString;
127+
}
128+
129+
MyPhpLookupElement withInsertHandler(InsertHandler insertHandler) {
130+
this.handler = insertHandler;
131+
return this;
132+
}
133+
}
134+
135+
public static class PhpClassWithScopeOperatorInsertHandler extends PhpReferenceTrimBackslashInsertHandler {
136+
private static final PhpClassWithScopeOperatorInsertHandler instance = new PhpClassWithScopeOperatorInsertHandler();
137+
138+
public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement lookupElement) {
139+
super.handleInsert(context, lookupElement);
140+
141+
if (context.getCompletionChar() == ':') {
142+
context.setAddCompletionChar(false);
143+
}
144+
145+
if (!PhpInsertHandlerUtil.isStringAtCaret(context.getEditor(), SCOPE_OPERATOR)) {
146+
PhpInsertHandlerUtil.insertStringAtCaret(context.getEditor(), SCOPE_OPERATOR);
147+
}
148+
149+
PhpCompletionUtil.showCompletion(context);
150+
}
151+
152+
public static PhpClassWithScopeOperatorInsertHandler getInstance() {
153+
return instance;
154+
}
155+
}
156+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package fr.adrienbrault.idea.symfony2plugin.util.completion;
2+
3+
import com.intellij.codeInsight.completion.CompletionParameters;
4+
import com.intellij.codeInsight.completion.CompletionProvider;
5+
import com.intellij.codeInsight.completion.CompletionResultSet;
6+
import com.intellij.codeInsight.completion.CompletionUtil;
7+
import com.intellij.codeInsight.lookup.LookupElementBuilder;
8+
import com.intellij.psi.PsiElement;
9+
import com.intellij.psi.impl.source.tree.LeafPsiElement;
10+
import com.intellij.util.ProcessingContext;
11+
import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper;
12+
import org.jetbrains.annotations.NotNull;
13+
14+
import java.util.HashMap;
15+
import java.util.Map;
16+
17+
/**
18+
* @author Thomas Schulz <mail@king2500.net>
19+
*/
20+
public class YamlKeywordsCompletionProvider extends CompletionProvider<CompletionParameters> {
21+
private final static Map<String, String> YAML_KEYWORDS = new HashMap<String, String>() {{
22+
put("~", "null");
23+
put("null", "null");
24+
put("true", "bool");
25+
put("false", "bool");
26+
put(".inf", "double");
27+
}};
28+
29+
@Override
30+
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {
31+
PsiElement psiElement = parameters.getPosition();
32+
33+
if (psiElement instanceof LeafPsiElement) {
34+
// Don't complete after tag (at end of line)
35+
// key: !my_tag <caret>\n
36+
if (YamlHelper.isElementAfterYamlTag(psiElement)) {
37+
return;
38+
}
39+
40+
// Don't complete after End Of Line:
41+
// key: foo\n
42+
// <caret>
43+
if (YamlHelper.isElementAfterEol(psiElement)) {
44+
return;
45+
}
46+
47+
String prefix = psiElement.getText();
48+
49+
if (prefix.contains(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) {
50+
prefix = prefix.substring(0, prefix.indexOf(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED));
51+
}
52+
53+
result = result.withPrefixMatcher(prefix);
54+
}
55+
56+
for (Map.Entry<String, String> entry : YAML_KEYWORDS.entrySet()) {
57+
String yamlKeyword = entry.getKey();
58+
String yamlType = entry.getValue();
59+
60+
LookupElementBuilder lookupElement = LookupElementBuilder.create(yamlKeyword)
61+
.withTypeText(yamlType);
62+
63+
result.addElement(lookupElement);
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)