|
1 | 1 | package fr.adrienbrault.idea.symfony2plugin.config.yaml; |
2 | 2 |
|
3 | 3 | import com.intellij.codeInsight.completion.*; |
| 4 | +import com.intellij.codeInsight.lookup.LookupElement; |
4 | 5 | import com.intellij.codeInsight.lookup.LookupElementBuilder; |
| 6 | +import com.intellij.openapi.util.Pair; |
5 | 7 | import com.intellij.openapi.vfs.VfsUtil; |
6 | 8 | import com.intellij.openapi.vfs.VirtualFile; |
7 | 9 | import com.intellij.openapi.vfs.VirtualFileVisitor; |
|
14 | 16 | import com.intellij.util.ProcessingContext; |
15 | 17 | import com.jetbrains.php.completion.PhpLookupElement; |
16 | 18 | import com.jetbrains.php.lang.psi.elements.Method; |
| 19 | +import com.jetbrains.php.lang.psi.elements.Parameter; |
| 20 | +import com.jetbrains.php.lang.psi.elements.ParameterList; |
17 | 21 | import com.jetbrains.php.lang.psi.elements.PhpClass; |
| 22 | +import com.jetbrains.php.lang.psi.resolve.types.PhpType; |
18 | 23 | import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons; |
19 | 24 | import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; |
20 | 25 | import fr.adrienbrault.idea.symfony2plugin.config.component.ParameterLookupElement; |
21 | 26 | import fr.adrienbrault.idea.symfony2plugin.config.doctrine.DoctrineStaticTypeLookupBuilder; |
22 | 27 | import fr.adrienbrault.idea.symfony2plugin.config.yaml.completion.ConfigCompletionProvider; |
23 | 28 | import fr.adrienbrault.idea.symfony2plugin.dic.ContainerParameter; |
24 | 29 | import fr.adrienbrault.idea.symfony2plugin.dic.ServiceCompletionProvider; |
| 30 | +import fr.adrienbrault.idea.symfony2plugin.dic.container.dict.ServiceTypeHint; |
| 31 | +import fr.adrienbrault.idea.symfony2plugin.dic.container.suggestion.utils.ServiceSuggestionUtil; |
25 | 32 | import fr.adrienbrault.idea.symfony2plugin.dic.container.util.DotEnvUtil; |
26 | 33 | import fr.adrienbrault.idea.symfony2plugin.dic.container.util.ServiceContainerUtil; |
27 | 34 | import fr.adrienbrault.idea.symfony2plugin.doctrine.DoctrineYamlAnnotationLookupBuilder; |
|
33 | 40 | import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver; |
34 | 41 | import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; |
35 | 42 | import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; |
| 43 | +import fr.adrienbrault.idea.symfony2plugin.util.SimilarSuggestionUtil; |
36 | 44 | import fr.adrienbrault.idea.symfony2plugin.util.SymfonyBundleFileCompletionProvider; |
37 | 45 | import fr.adrienbrault.idea.symfony2plugin.util.completion.EventCompletionProvider; |
38 | 46 | import fr.adrienbrault.idea.symfony2plugin.util.completion.PhpClassAndParameterCompletionProvider; |
|
48 | 56 | import org.jetbrains.yaml.psi.YAMLKeyValue; |
49 | 57 | import org.jetbrains.yaml.psi.YAMLScalar; |
50 | 58 |
|
51 | | -import java.util.Collections; |
52 | | -import java.util.HashMap; |
53 | | -import java.util.HashSet; |
54 | | -import java.util.Map; |
| 59 | +import java.util.*; |
55 | 60 | import java.util.regex.Matcher; |
56 | 61 | import java.util.regex.Pattern; |
| 62 | +import java.util.stream.Collectors; |
57 | 63 |
|
58 | 64 | /** |
59 | 65 | * @author Daniel Espendiller <daniel@espendiller.net> |
@@ -408,22 +414,143 @@ private static class NamedArgumentCompletionProvider extends CompletionProvider< |
408 | 414 | protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) { |
409 | 415 | HashSet<String> uniqueParameters = new HashSet<>(); |
410 | 416 |
|
411 | | - ServiceContainerUtil.visitNamedArguments(parameters.getPosition().getContainingFile(), parameter -> { |
412 | | - String name = parameter.getName(); |
413 | | - if (uniqueParameters.contains(name)) { |
| 417 | + PsiElement position = parameters.getPosition(); |
| 418 | + boolean hasEmptyNextElement = position.getNextSibling() == null; |
| 419 | + |
| 420 | + ServiceContainerUtil.visitNamedArguments(position.getContainingFile(), pair -> { |
| 421 | + Parameter parameter = pair.getFirst(); |
| 422 | + String parameterName = parameter.getName(); |
| 423 | + if (uniqueParameters.contains(parameterName)) { |
414 | 424 | return; |
415 | 425 | } |
416 | 426 |
|
417 | | - uniqueParameters.add(name); |
| 427 | + uniqueParameters.add(parameterName); |
418 | 428 |
|
419 | 429 | // create argument for yaml: $parameter |
420 | 430 | result.addElement( |
421 | | - LookupElementBuilder.create("$" + name) |
422 | | - .withIcon(parameter.getIcon()) |
423 | | - .withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\"), true) |
| 431 | + LookupElementBuilder.create("$" + parameterName) |
| 432 | + .withIcon(parameter.getIcon()) |
| 433 | + .withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\")) |
424 | 434 | ); |
| 435 | + |
| 436 | + if (hasEmptyNextElement) { |
| 437 | + // iterable $handlers => can also provide "!tagged_iterator" |
| 438 | + if (parameter.getType().getTypes().stream().anyMatch(s -> s.equalsIgnoreCase(PhpType._ITERABLE))) { |
| 439 | + LookupElementBuilder element = LookupElementBuilder.create("$" + parameterName + ": !tagged_iterator") |
| 440 | + .withIcon(parameter.getIcon()) |
| 441 | + .withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\"), true); |
| 442 | + |
| 443 | + result.addElement(PrioritizedLookupElement.withPriority(element, -1000)); |
| 444 | + } |
| 445 | + |
| 446 | + if (!parameter.getType().getTypes().stream().allMatch(PhpType::isPrimitiveType)) { |
| 447 | + // $foobar: '@service' |
| 448 | + result.addAllElements(getServiceSuggestion(position, pair, parameterName, new ContainerCollectionResolver.LazyServiceCollector(position.getProject()))); |
| 449 | + } else { |
| 450 | + String parameterNormalized = parameterName.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", ""); |
| 451 | + if (parameterNormalized.length() > 5) { |
| 452 | + // $projectDir: '%kernel.project_dir%' |
| 453 | + result.addAllElements(getParameterSuggestion(parameter, parameterName, parameterNormalized)); |
| 454 | + |
| 455 | + // $kernelClass: '%env(KERNEL_CLASS)%' |
| 456 | + result.addAllElements(getDotEnvSuggestion(parameter, parameterName, parameterNormalized)); |
| 457 | + } |
| 458 | + } |
| 459 | + } |
425 | 460 | }); |
426 | 461 | } |
| 462 | + |
| 463 | + @NotNull |
| 464 | + private Collection<LookupElement> getServiceSuggestion(@NotNull PsiElement position, @NotNull Pair<Parameter, Integer> pair, @NotNull String parameterName, @NotNull ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector) { |
| 465 | + Parameter parameter = pair.getFirst(); |
| 466 | + |
| 467 | + PsiElement parameterList = parameter.getParent(); |
| 468 | + if (parameterList instanceof ParameterList) { |
| 469 | + PsiElement parent = parameterList.getParent(); |
| 470 | + if (parent instanceof Method) { |
| 471 | + Collection<String> suggestions = new ArrayList<>(ServiceSuggestionUtil.createSuggestions(new ServiceTypeHint( |
| 472 | + (Method) parent, |
| 473 | + pair.getSecond(), |
| 474 | + position |
| 475 | + ), lazyServiceCollector.getCollector().getServices().values())); |
| 476 | + |
| 477 | + return suggestions.stream() |
| 478 | + .limit(3) |
| 479 | + .map(service -> { |
| 480 | + LookupElementBuilder element = LookupElementBuilder.create(String.format("$%s: '@%s'", parameterName, service)) |
| 481 | + .withIcon(Symfony2Icons.SERVICE) |
| 482 | + .withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\"), true); |
| 483 | + |
| 484 | + return PrioritizedLookupElement.withPriority(element, -1000); |
| 485 | + }) |
| 486 | + .collect(Collectors.toList()); |
| 487 | + } |
| 488 | + } |
| 489 | + |
| 490 | + return Collections.emptyList(); |
| 491 | + } |
| 492 | + |
| 493 | + /** |
| 494 | + * $projectDir: '%kernel.project_dir%' |
| 495 | + */ |
| 496 | + private Collection<LookupElement> getParameterSuggestion(@NotNull Parameter parameter, @NotNull String parameterName, @NotNull String parameterNormalized) { |
| 497 | + Set<String> values = new HashSet<>(); |
| 498 | + |
| 499 | + for (String name : ContainerCollectionResolver.getParameterNames(parameter.getProject())) { |
| 500 | + String symfonyParameterNormalized = name.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", ""); |
| 501 | + |
| 502 | + if (symfonyParameterNormalized.contains(parameterNormalized)) { |
| 503 | + values.add(name); |
| 504 | + } |
| 505 | + } |
| 506 | + |
| 507 | + // weight items: append all indirect matched, after them in case there they are not similar |
| 508 | + List<String> similarString = new ArrayList<>(SimilarSuggestionUtil.findSimilarString(parameterNormalized, values)); |
| 509 | + similarString.addAll(values); |
| 510 | + |
| 511 | + return similarString.stream() |
| 512 | + .distinct() |
| 513 | + .limit(3) |
| 514 | + .map(service -> { |
| 515 | + LookupElementBuilder element = LookupElementBuilder.create("$" + parameterName + ": '%" + service + "%'") |
| 516 | + .withIcon(Symfony2Icons.PARAMETER) |
| 517 | + .withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\"), true); |
| 518 | + |
| 519 | + return PrioritizedLookupElement.withPriority(element, -1000); |
| 520 | + }) |
| 521 | + .collect(Collectors.toList()); |
| 522 | + } |
| 523 | + |
| 524 | + /** |
| 525 | + * "$kernelClass: '%env(KERNEL_CLASS)%'" |
| 526 | + */ |
| 527 | + @NotNull |
| 528 | + private Collection<LookupElement> getDotEnvSuggestion(@NotNull Parameter parameter, @NotNull String parameterName, @NotNull String parameterNormalized) { |
| 529 | + Set<String> dotEnv = new HashSet<>(); |
| 530 | + for (String name : DotEnvUtil.getEnvironmentVariables(parameter.getProject())) { |
| 531 | + String symfonyParameterNormalized = name.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", ""); |
| 532 | + |
| 533 | + if (symfonyParameterNormalized.contains(parameterNormalized)) { |
| 534 | + dotEnv.add(name); |
| 535 | + } |
| 536 | + } |
| 537 | + |
| 538 | + // weight items: append all indirect matched, after them in case there they are not similar |
| 539 | + List<String> similarString = new ArrayList<>(SimilarSuggestionUtil.findSimilarString(parameterNormalized, dotEnv)); |
| 540 | + similarString.addAll(dotEnv); |
| 541 | + |
| 542 | + return similarString.stream() |
| 543 | + .distinct() |
| 544 | + .limit(3) |
| 545 | + .map(service -> { |
| 546 | + LookupElementBuilder element = LookupElementBuilder.create("$" + parameterName + ": '%env(" + service + ")%'") |
| 547 | + .withIcon(Symfony2Icons.PARAMETER) |
| 548 | + .withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\"), true); |
| 549 | + |
| 550 | + return PrioritizedLookupElement.withPriority(element, -1000); |
| 551 | + }) |
| 552 | + .collect(Collectors.toList()); |
| 553 | + } |
427 | 554 | } |
428 | 555 |
|
429 | 556 | /** |
|
0 commit comments