Skip to content

Commit 316627e

Browse files
authored
Merge pull request #1787 from Haehnchen/feature/if-incomplete
provide incomplete "if" completion for Twig
2 parents c47a2dd + 82b0455 commit 316627e

File tree

3 files changed

+139
-39
lines changed

3 files changed

+139
-39
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java

Lines changed: 86 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959

6060
import java.util.*;
6161
import java.util.function.Function;
62+
import java.util.function.Predicate;
6263
import java.util.stream.Collectors;
6364

6465
/**
@@ -440,6 +441,13 @@ public void addCompletions(@NotNull CompletionParameters parameters, ProcessingC
440441
PlatformPatterns.psiElement(TwigTokenTypes.TAG_NAME),
441442
new IncompleteForCompletionProvider()
442443
);
444+
445+
// {% if => "if app.debug"
446+
extend(
447+
CompletionType.BASIC,
448+
PlatformPatterns.psiElement(TwigTokenTypes.TAG_NAME),
449+
new IncompleteIfCompletionProvider()
450+
);
443451
}
444452

445453
private boolean isCompletionStartingMatch(@NotNull String fullText, @NotNull CompletionParameters completionParameters, int minLength) {
@@ -821,7 +829,7 @@ public boolean accepts(@NotNull String s, ProcessingContext processingContext) {
821829
private class IncompleteForCompletionProvider extends CompletionProvider<CompletionParameters> {
822830
@Override
823831
protected void addCompletions(@NotNull CompletionParameters completionParameters, @NotNull ProcessingContext processingContext, @NotNull CompletionResultSet resultSet) {
824-
if(!Symfony2ProjectComponent.isEnabled(completionParameters.getPosition())) {
832+
if (!Symfony2ProjectComponent.isEnabled(completionParameters.getPosition())) {
825833
return;
826834
}
827835

@@ -836,59 +844,99 @@ public boolean accepts(@NotNull String s, ProcessingContext processingContext) {
836844
return;
837845
}
838846

839-
Set<Map.Entry<String, PsiVariable>> entries = TwigTypeResolveUtil.collectScopeVariables(completionParameters.getPosition()).entrySet();
847+
resultSet.addAllElements(processVariables(
848+
completionParameters.getPosition(),
849+
PhpType::isArray,
850+
pair -> {
851+
String var = pair.getValue().getFirst();
852+
String unpluralize = StringUtil.unpluralize(var);
853+
if (unpluralize != null) {
854+
var = unpluralize;
855+
}
840856

841-
Map<String, Pair<String, LookupElement>> arrays = new HashMap<>();
857+
return String.format("for %s in %s", var, pair.getKey());
858+
}
859+
));
860+
}
861+
}
842862

843-
Project project = completionParameters.getPosition().getProject();
863+
/**
864+
* {% if => "if app.debug"
865+
*/
866+
private class IncompleteIfCompletionProvider extends CompletionProvider<CompletionParameters> {
867+
@Override
868+
protected void addCompletions(@NotNull CompletionParameters completionParameters, @NotNull ProcessingContext processingContext, @NotNull CompletionResultSet resultSet) {
869+
if(!Symfony2ProjectComponent.isEnabled(completionParameters.getPosition())) {
870+
return;
871+
}
844872

845-
for(Map.Entry<String, PsiVariable> entry: entries) {
846-
Collection<PhpClass> classFromPhpTypeSet = PhpElementsUtil.getClassFromPhpTypeSet(project, entry.getValue().getTypes());
847-
for (PhpClass phpClass : classFromPhpTypeSet) {
848-
for(Method method: phpClass.getMethods()) {
849-
if(!(!method.getModifier().isPublic() || method.getName().startsWith("set") || method.getName().startsWith("__"))) {
850-
if (PhpType.isArray(PhpIndex.getInstance(project).completeType(project, method.getType(), new HashSet<>()))) {
851-
String propertyShortcutMethodName = TwigTypeResolveUtil.getPropertyShortcutMethodName(method);
852-
arrays.put(entry.getKey() + "." + propertyShortcutMethodName, Pair.create(propertyShortcutMethodName, new PhpTwigMethodLookupElement(method)));
853-
}
873+
resultSet.restartCompletionOnPrefixChange(StandardPatterns.string().longerThan(1).with(new PatternCondition<>("if startsWith") {
874+
@Override
875+
public boolean accepts(@NotNull String s, ProcessingContext processingContext) {
876+
return "if".startsWith(s);
877+
}
878+
}));
879+
880+
if (!isCompletionStartingMatch("if", completionParameters, 2)) {
881+
return;
882+
}
883+
884+
resultSet.addAllElements(processVariables(
885+
completionParameters.getPosition(),
886+
PhpType::isBoolean,
887+
entry -> String.format("if %s", entry.getKey())
888+
));
889+
}
890+
}
891+
892+
@NotNull
893+
private Collection<LookupElement> processVariables(@NotNull PsiElement psiElement, @NotNull Predicate<PhpType> filter, @NotNull Function<Map.Entry<String, Pair<String, LookupElement>>, String> map) {
894+
Project project = psiElement.getProject();
895+
896+
Map<String, Pair<String, LookupElement>> arrays = new HashMap<>();
897+
for(Map.Entry<String, PsiVariable> entry: TwigTypeResolveUtil.collectScopeVariables(psiElement).entrySet()) {
898+
Collection<PhpClass> classFromPhpTypeSet = PhpElementsUtil.getClassFromPhpTypeSet(project, entry.getValue().getTypes());
899+
for (PhpClass phpClass : classFromPhpTypeSet) {
900+
for(Method method: phpClass.getMethods()) {
901+
if(!(!method.getModifier().isPublic() || method.getName().startsWith("set") || method.getName().startsWith("__"))) {
902+
if (filter.test(PhpIndex.getInstance(project).completeType(project, method.getType(), new HashSet<>()))) {
903+
String propertyShortcutMethodName = TwigTypeResolveUtil.getPropertyShortcutMethodName(method);
904+
arrays.put(entry.getKey() + "." + propertyShortcutMethodName, Pair.create(propertyShortcutMethodName, new PhpTwigMethodLookupElement(method)));
854905
}
855906
}
907+
}
856908

857-
for(Field field: phpClass.getFields()) {
858-
if(field.getModifier().isPublic()) {
859-
if (PhpType.isArray(PhpIndex.getInstance(project).completeType(project, field.getType(), new HashSet<>()))) {
860-
arrays.put(entry.getKey() + "." + field.getName(), Pair.create(field.getName(), new PhpTwigMethodLookupElement(field)));
861-
}
909+
for(Field field: phpClass.getFields()) {
910+
if(field.getModifier().isPublic()) {
911+
if (filter.test(PhpIndex.getInstance(project).completeType(project, field.getType(), new HashSet<>()))) {
912+
arrays.put(entry.getKey() + "." + field.getName(), Pair.create(field.getName(), new PhpTwigMethodLookupElement(field)));
862913
}
863914
}
864915
}
865916
}
917+
}
866918

867-
for (Map.Entry<String, Pair<String, LookupElement>> entry : arrays.entrySet()) {
868-
String var = entry.getValue().getFirst();
869-
String unpluralize = StringUtil.unpluralize(var);
870-
if (unpluralize != null) {
871-
var = unpluralize;
872-
}
919+
Collection<LookupElement> items = new ArrayList<>();
873920

874-
LookupElementPresentation lookupElementPresentation = new LookupElementPresentation();
875-
entry.getValue().getSecond().renderElement(lookupElementPresentation);
921+
for (Map.Entry<String, Pair<String, LookupElement>> entry : arrays.entrySet()) {
922+
LookupElementPresentation lookupElementPresentation = new LookupElementPresentation();
923+
entry.getValue().getSecond().renderElement(lookupElementPresentation);
876924

877-
Set<String> types = new HashSet<>();
878-
PsiElement psiElement = entry.getValue().getSecond().getPsiElement();
879-
if (psiElement instanceof PhpTypedElement) {
880-
types.addAll(((PhpTypedElement) psiElement).getType().getTypes());
881-
}
925+
Set<String> types = new HashSet<>();
926+
PsiElement typeElement = entry.getValue().getSecond().getPsiElement();
927+
if (typeElement instanceof PhpTypedElement) {
928+
types.addAll(((PhpTypedElement) typeElement).getType().getTypes());
929+
}
882930

883-
String content = String.format("for %s in %s", var, entry.getKey());
884-
LookupElementBuilder lookupElement = LookupElementBuilder.create(content)
885-
.withIcon(lookupElementPresentation.getIcon())
886-
.withStrikeoutness(lookupElementPresentation.isStrikeout())
887-
.withTypeText(StringUtils.stripStart(TwigTypeResolveUtil.getTypeDisplayName(project, types), "\\"));
931+
LookupElementBuilder lookupElement = LookupElementBuilder.create(map.apply(entry))
932+
.withIcon(lookupElementPresentation.getIcon())
933+
.withStrikeoutness(lookupElementPresentation.isStrikeout())
934+
.withTypeText(StringUtils.stripStart(TwigTypeResolveUtil.getTypeDisplayName(project, types), "\\"));
888935

889-
resultSet.addElement(lookupElement);
890-
}
936+
items.add(lookupElement);
891937
}
938+
939+
return items;
892940
}
893941
}
894942

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/TwigTemplateCompletionContributorTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,22 @@ public void testThatMacroImportProvidesCompletion() {
113113
);
114114
}
115115

116+
public void testThatIncompleteIfStatementIsCompletedWithVariables() {
117+
assertCompletionContains(TwigFileType.INSTANCE, "\n" +
118+
"{# @var \\Foo\\Template\\Foobar foobar #}\n" +
119+
"{% if<caret> %}\n",
120+
"if foobar.ready", "if foobar.readyStatus"
121+
);
122+
}
123+
124+
public void testThatIncompleteForStatementIsCompletedWithVariables() {
125+
assertCompletionContains(TwigFileType.INSTANCE, "\n" +
126+
"{# @var \\Foo\\Template\\Foobar foobar #}\n" +
127+
"{% fo<caret> %}\n",
128+
"for myfoo in foobar.myfoos", "for date in foobar.dates", "for item in foobar.items"
129+
);
130+
}
131+
116132
private void createWorkaroundFile(@NotNull String file, @NotNull String content) {
117133

118134
try {

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/fixtures/classes.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,40 @@ class Foo
3636
{
3737
const FOO = 'BAR';
3838
}
39-
}
39+
}
40+
41+
42+
namespace Foo\Template {
43+
44+
class Foobar
45+
{
46+
/**
47+
* @var \DateTime[]
48+
*/
49+
public $items;
50+
51+
public bool $ready;
52+
53+
public function __construct(public array $myfoos)
54+
{
55+
}
56+
57+
public function getFoobar(): array
58+
{
59+
}
60+
61+
/**
62+
* @return \DateTime[]
63+
*/
64+
public function getDates()
65+
{
66+
}
67+
68+
/**
69+
* @return bool
70+
*/
71+
public function isReadyStatus()
72+
{
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)