diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java index 9f8d23bb4..e5d00f77a 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java @@ -59,6 +59,7 @@ import java.util.*; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -440,6 +441,13 @@ public void addCompletions(@NotNull CompletionParameters parameters, ProcessingC PlatformPatterns.psiElement(TwigTokenTypes.TAG_NAME), new IncompleteForCompletionProvider() ); + + // {% if => "if app.debug" + extend( + CompletionType.BASIC, + PlatformPatterns.psiElement(TwigTokenTypes.TAG_NAME), + new IncompleteIfCompletionProvider() + ); } private boolean isCompletionStartingMatch(@NotNull String fullText, @NotNull CompletionParameters completionParameters, int minLength) { @@ -821,7 +829,7 @@ public boolean accepts(@NotNull String s, ProcessingContext processingContext) { private class IncompleteForCompletionProvider extends CompletionProvider { @Override protected void addCompletions(@NotNull CompletionParameters completionParameters, @NotNull ProcessingContext processingContext, @NotNull CompletionResultSet resultSet) { - if(!Symfony2ProjectComponent.isEnabled(completionParameters.getPosition())) { + if (!Symfony2ProjectComponent.isEnabled(completionParameters.getPosition())) { return; } @@ -836,59 +844,99 @@ public boolean accepts(@NotNull String s, ProcessingContext processingContext) { return; } - Set> entries = TwigTypeResolveUtil.collectScopeVariables(completionParameters.getPosition()).entrySet(); + resultSet.addAllElements(processVariables( + completionParameters.getPosition(), + PhpType::isArray, + pair -> { + String var = pair.getValue().getFirst(); + String unpluralize = StringUtil.unpluralize(var); + if (unpluralize != null) { + var = unpluralize; + } - Map> arrays = new HashMap<>(); + return String.format("for %s in %s", var, pair.getKey()); + } + )); + } + } - Project project = completionParameters.getPosition().getProject(); + /** + * {% if => "if app.debug" + */ + private class IncompleteIfCompletionProvider extends CompletionProvider { + @Override + protected void addCompletions(@NotNull CompletionParameters completionParameters, @NotNull ProcessingContext processingContext, @NotNull CompletionResultSet resultSet) { + if(!Symfony2ProjectComponent.isEnabled(completionParameters.getPosition())) { + return; + } - for(Map.Entry entry: entries) { - Collection classFromPhpTypeSet = PhpElementsUtil.getClassFromPhpTypeSet(project, entry.getValue().getTypes()); - for (PhpClass phpClass : classFromPhpTypeSet) { - for(Method method: phpClass.getMethods()) { - if(!(!method.getModifier().isPublic() || method.getName().startsWith("set") || method.getName().startsWith("__"))) { - if (PhpType.isArray(PhpIndex.getInstance(project).completeType(project, method.getType(), new HashSet<>()))) { - String propertyShortcutMethodName = TwigTypeResolveUtil.getPropertyShortcutMethodName(method); - arrays.put(entry.getKey() + "." + propertyShortcutMethodName, Pair.create(propertyShortcutMethodName, new PhpTwigMethodLookupElement(method))); - } + resultSet.restartCompletionOnPrefixChange(StandardPatterns.string().longerThan(1).with(new PatternCondition<>("if startsWith") { + @Override + public boolean accepts(@NotNull String s, ProcessingContext processingContext) { + return "if".startsWith(s); + } + })); + + if (!isCompletionStartingMatch("if", completionParameters, 2)) { + return; + } + + resultSet.addAllElements(processVariables( + completionParameters.getPosition(), + PhpType::isBoolean, + entry -> String.format("if %s", entry.getKey()) + )); + } + } + + @NotNull + private Collection processVariables(@NotNull PsiElement psiElement, @NotNull Predicate filter, @NotNull Function>, String> map) { + Project project = psiElement.getProject(); + + Map> arrays = new HashMap<>(); + for(Map.Entry entry: TwigTypeResolveUtil.collectScopeVariables(psiElement).entrySet()) { + Collection classFromPhpTypeSet = PhpElementsUtil.getClassFromPhpTypeSet(project, entry.getValue().getTypes()); + for (PhpClass phpClass : classFromPhpTypeSet) { + for(Method method: phpClass.getMethods()) { + if(!(!method.getModifier().isPublic() || method.getName().startsWith("set") || method.getName().startsWith("__"))) { + if (filter.test(PhpIndex.getInstance(project).completeType(project, method.getType(), new HashSet<>()))) { + String propertyShortcutMethodName = TwigTypeResolveUtil.getPropertyShortcutMethodName(method); + arrays.put(entry.getKey() + "." + propertyShortcutMethodName, Pair.create(propertyShortcutMethodName, new PhpTwigMethodLookupElement(method))); } } + } - for(Field field: phpClass.getFields()) { - if(field.getModifier().isPublic()) { - if (PhpType.isArray(PhpIndex.getInstance(project).completeType(project, field.getType(), new HashSet<>()))) { - arrays.put(entry.getKey() + "." + field.getName(), Pair.create(field.getName(), new PhpTwigMethodLookupElement(field))); - } + for(Field field: phpClass.getFields()) { + if(field.getModifier().isPublic()) { + if (filter.test(PhpIndex.getInstance(project).completeType(project, field.getType(), new HashSet<>()))) { + arrays.put(entry.getKey() + "." + field.getName(), Pair.create(field.getName(), new PhpTwigMethodLookupElement(field))); } } } } + } - for (Map.Entry> entry : arrays.entrySet()) { - String var = entry.getValue().getFirst(); - String unpluralize = StringUtil.unpluralize(var); - if (unpluralize != null) { - var = unpluralize; - } + Collection items = new ArrayList<>(); - LookupElementPresentation lookupElementPresentation = new LookupElementPresentation(); - entry.getValue().getSecond().renderElement(lookupElementPresentation); + for (Map.Entry> entry : arrays.entrySet()) { + LookupElementPresentation lookupElementPresentation = new LookupElementPresentation(); + entry.getValue().getSecond().renderElement(lookupElementPresentation); - Set types = new HashSet<>(); - PsiElement psiElement = entry.getValue().getSecond().getPsiElement(); - if (psiElement instanceof PhpTypedElement) { - types.addAll(((PhpTypedElement) psiElement).getType().getTypes()); - } + Set types = new HashSet<>(); + PsiElement typeElement = entry.getValue().getSecond().getPsiElement(); + if (typeElement instanceof PhpTypedElement) { + types.addAll(((PhpTypedElement) typeElement).getType().getTypes()); + } - String content = String.format("for %s in %s", var, entry.getKey()); - LookupElementBuilder lookupElement = LookupElementBuilder.create(content) - .withIcon(lookupElementPresentation.getIcon()) - .withStrikeoutness(lookupElementPresentation.isStrikeout()) - .withTypeText(StringUtils.stripStart(TwigTypeResolveUtil.getTypeDisplayName(project, types), "\\")); + LookupElementBuilder lookupElement = LookupElementBuilder.create(map.apply(entry)) + .withIcon(lookupElementPresentation.getIcon()) + .withStrikeoutness(lookupElementPresentation.isStrikeout()) + .withTypeText(StringUtils.stripStart(TwigTypeResolveUtil.getTypeDisplayName(project, types), "\\")); - resultSet.addElement(lookupElement); - } + items.add(lookupElement); } + + return items; } } diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/TwigTemplateCompletionContributorTest.java b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/TwigTemplateCompletionContributorTest.java index 29a28c0e8..595017d38 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/TwigTemplateCompletionContributorTest.java +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/TwigTemplateCompletionContributorTest.java @@ -113,6 +113,22 @@ public void testThatMacroImportProvidesCompletion() { ); } + public void testThatIncompleteIfStatementIsCompletedWithVariables() { + assertCompletionContains(TwigFileType.INSTANCE, "\n" + + "{# @var \\Foo\\Template\\Foobar foobar #}\n" + + "{% if %}\n", + "if foobar.ready", "if foobar.readyStatus" + ); + } + + public void testThatIncompleteForStatementIsCompletedWithVariables() { + assertCompletionContains(TwigFileType.INSTANCE, "\n" + + "{# @var \\Foo\\Template\\Foobar foobar #}\n" + + "{% fo %}\n", + "for myfoo in foobar.myfoos", "for date in foobar.dates", "for item in foobar.items" + ); + } + private void createWorkaroundFile(@NotNull String file, @NotNull String content) { try { diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/fixtures/classes.php b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/fixtures/classes.php index a56301c5c..91b7d0a70 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/fixtures/classes.php +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/fixtures/classes.php @@ -36,4 +36,40 @@ class Foo { const FOO = 'BAR'; } -} \ No newline at end of file +} + + +namespace Foo\Template { + + class Foobar + { + /** + * @var \DateTime[] + */ + public $items; + + public bool $ready; + + public function __construct(public array $myfoos) + { + } + + public function getFoobar(): array + { + } + + /** + * @return \DateTime[] + */ + public function getDates() + { + } + + /** + * @return bool + */ + public function isReadyStatus() + { + } + } +}