From 481d81d659ab3981bcfe9a4c288e1ac88f631bff Mon Sep 17 00:00:00 2001 From: Hiepiscus Date: Thu, 27 Aug 2026 15:07:35 +0200 Subject: [PATCH 1/9] #2193: Add alternative to completion entries --- .../completion/AutoCompletionRegistry.java | 16 +++++++- .../tools/ide/completion/CompletionEntry.java | 36 +++++++++++++++++- .../tools/ide/tool/mvn/MavenCommandlet.java | 4 ++ .../tools/ide/completion/CompleteTest.java | 38 ++++++++++++++++++- 4 files changed, 89 insertions(+), 5 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java index 7948f360a1..7cca7451ef 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java @@ -39,7 +39,6 @@ public void add(String candidate, String synonym) { this.entries.add(entry); } - /** * Adds all candidates matching the given argument to the collector. * @@ -56,5 +55,20 @@ public void complete(String arg, CompletionCandidateCollector collector, } } + public void addAlternative(String candidate1, String candidate2) { + + CompletionEntry entry1 = findEntry(candidate1); + CompletionEntry entry2 = findEntry(candidate2); + + entry1.addAlternative(entry2); + } + private CompletionEntry findEntry(String candidate) { + for (CompletionEntry entry : this.entries) { + if (entry.getCandidate().contains(candidate)) { + return entry; + } + } + return null; + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java index 9df9cc7439..74eebab2f1 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java @@ -19,6 +19,8 @@ public class CompletionEntry { /** List of synonym strings for this candidate. */ private List synonyms = new ArrayList<>(); + private List alternatives = new ArrayList<>(); + /** * The constructor. * @@ -28,6 +30,10 @@ public CompletionEntry(String candidate) { this.candidate = candidate; } + public String getCandidate() { + return candidate; + } + /** * Adds a synonym for this candidate. * @@ -48,8 +54,16 @@ public void addSynonym(String synonym) { public void complete(String arg, CompletionCandidateCollector collector, Property property, Commandlet commandlet) { Set alreadyProvided = collector.getAlreadyProvided(); - if (alreadyProvided != null && (alreadyProvided.contains(this.candidate) || synonyms.stream().anyMatch(alreadyProvided::contains))) { - return; + if (alreadyProvided != null) { + if (isProvided(alreadyProvided)) { + return; + } + + for (CompletionEntry alternative : this.alternatives) { + if (alternative.isProvided(alreadyProvided)) { + return; + } + } } if (candidate.startsWith(arg)) { @@ -63,4 +77,22 @@ public void complete(String arg, CompletionCandidateCollector collector, Propert } } + public boolean isProvided(Set alreadyProvided) { + return alreadyProvided.contains(this.candidate) || this.synonyms.stream().anyMatch(alreadyProvided::contains); + } + + public void addAlternative(CompletionEntry alternative) { + + if ((alternative == null) || (alternative == this)) { + return; + } + + if (!this.alternatives.contains(alternative)) { + this.alternatives.add(alternative); + } + + if (!alternative.alternatives.contains(this)) { + alternative.alternatives.add(this); + } + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java index d843e41e1a..6fb61b66d2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java @@ -49,6 +49,7 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("-DskipTests"); registry.add("-Dmaven.test.skip=true"); registry.add("exec:java"); + registry.add("exec:exec"); registry.add("-Dexec.mainClass="); registry.add("-Dexec.args="); registry.add("-P"); @@ -75,6 +76,9 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("-Dstyle.color="); registry.add("-Duser.dir="); registry.add("-Duser.home="); + registry.addAlternative("exec:java", "exec:exec"); + //registry.addDependency("-Dexec.mainClass=", "exec:java"); + //registry.addDependency("-Dexec.mainClass=", "exec:exec"); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java index eaa0052768..4d910ba03d 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java @@ -407,8 +407,8 @@ void testSynonymFilteringWithProvidedSynonym() { } /** - * Test that completion works for a second tool argument (e.g. "ide mvn clean [tab]"), which is the real-world scenario - * that previously failed because the multivalued arguments property consumed the completion marker greedily. + * Test that completion works for a second tool argument (e.g. "ide mvn clean [tab]"), which is the real-world scenario that previously failed because the + * multivalued arguments property consumed the completion marker greedily. */ @Test void testCompleteMavenSecondToolArgument() { @@ -425,4 +425,38 @@ void testCompleteMavenSecondToolArgument() { assertThat(candidates.stream().map(CompletionCandidate::text)) .contains("dependency:list", "dependency:tree", "deploy"); } + + @Test + void testAlternativeFilteringWhenOtherAlternativeProvidedJava() { + + // arrange + AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); + String[] argsArray = { "mvn", "exec:java", "" }; + CliArguments args = CliArguments.ofCompletion(argsArray); + CompletionCandidateCollector collector = createCollector(context, argsArray); + + // act + List candidates = context.complete(args, collector, true); + + // assert + List texts = candidates.stream().map(CompletionCandidate::text).toList(); + assertThat(texts).doesNotContain("exec:exec"); + } + + @Test + void testAlternativeFilteringWhenOtherAlternativeProvidedExec() { + + // arrange + AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); + String[] argsArray = { "mvn", "exec:exec", "" }; + CliArguments args = CliArguments.ofCompletion(argsArray); + CompletionCandidateCollector collector = createCollector(context, argsArray); + + // act + List candidates = context.complete(args, collector, true); + + // assert + List texts = candidates.stream().map(CompletionCandidate::text).toList(); + assertThat(texts).doesNotContain("exec:java"); + } } From 5df3485b214b975169fb0fb50daed11b7b97356b Mon Sep 17 00:00:00 2001 From: Hiepiscus Date: Fri, 28 Aug 2026 17:27:14 +0200 Subject: [PATCH 2/9] #2193: Add dependencies to completion entries --- .../completion/AutoCompletionRegistry.java | 12 +++++ .../tools/ide/completion/CompletionEntry.java | 30 ++++++++++++ .../tools/ide/tool/mvn/MavenCommandlet.java | 9 ++-- .../tools/ide/completion/CompleteTest.java | 48 +++++++++++++++++++ 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java index 7cca7451ef..018dc53b00 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java @@ -63,6 +63,18 @@ public void addAlternative(String candidate1, String candidate2) { entry1.addAlternative(entry2); } + public void addDependency(String candidate, List depends) { + + CompletionEntry entry = findEntry(candidate); + + CompletionEntry[] dependencyEntries = new CompletionEntry[depends.size()]; + for (int i = 0; i < depends.size(); i++) { + CompletionEntry dependencyEntry = findEntry(depends.get(i)); + dependencyEntries[i] = dependencyEntry; + } + entry.addDependency(dependencyEntries); + } + private CompletionEntry findEntry(String candidate) { for (CompletionEntry entry : this.entries) { if (entry.getCandidate().contains(candidate)) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java index 74eebab2f1..873ae04b73 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java @@ -21,6 +21,8 @@ public class CompletionEntry { private List alternatives = new ArrayList<>(); + private List> dependencies = new ArrayList<>(); + /** * The constructor. * @@ -55,6 +57,10 @@ public void complete(String arg, CompletionCandidateCollector collector, Propert Set alreadyProvided = collector.getAlreadyProvided(); if (alreadyProvided != null) { + if (!isDependency(alreadyProvided)) { + return; + } + if (isProvided(alreadyProvided)) { return; } @@ -77,6 +83,23 @@ public void complete(String arg, CompletionCandidateCollector collector, Propert } } + private boolean isDependency(Set alreadyProvided) { + + for (List group : this.dependencies) { + boolean groupSatisfied = false; + for (CompletionEntry entry : group) { + if (entry.isProvided(alreadyProvided)) { + groupSatisfied = true; + break; + } + } + if (!groupSatisfied) { + return false; + } + } + return true; + } + public boolean isProvided(Set alreadyProvided) { return alreadyProvided.contains(this.candidate) || this.synonyms.stream().anyMatch(alreadyProvided::contains); } @@ -95,4 +118,11 @@ public void addAlternative(CompletionEntry alternative) { alternative.alternatives.add(this); } } + + public void addDependency(CompletionEntry[] entries) { + if ((entries == null) || (entries.length == 0)) { + return; + } + this.dependencies.add(List.of(entries)); + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java index 6fb61b66d2..df2656e653 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java @@ -1,6 +1,7 @@ package com.devonfw.tools.ide.tool.mvn; +import java.util.List; import java.util.Set; @@ -50,8 +51,6 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("-Dmaven.test.skip=true"); registry.add("exec:java"); registry.add("exec:exec"); - registry.add("-Dexec.mainClass="); - registry.add("-Dexec.args="); registry.add("-P"); registry.add("-pl"); registry.add("-am"); @@ -77,8 +76,10 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("-Duser.dir="); registry.add("-Duser.home="); registry.addAlternative("exec:java", "exec:exec"); - //registry.addDependency("-Dexec.mainClass=", "exec:java"); - //registry.addDependency("-Dexec.mainClass=", "exec:exec"); + registry.add("-Dexec.mainClass="); + registry.addDependency("-Dexec.mainClass=", List.of("exec:java")); + registry.add("-Dexec.args="); + registry.addDependency("-Dexec.args=", List.of("exec:java", "exec:exec")); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java index 4d910ba03d..ba382f0fe3 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java @@ -459,4 +459,52 @@ void testAlternativeFilteringWhenOtherAlternativeProvidedExec() { List texts = candidates.stream().map(CompletionCandidate::text).toList(); assertThat(texts).doesNotContain("exec:java"); } + + @Test + void testDependencyNotSatisfiedIsNotSuggested() { + + // arrange + AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); + String[] argsArray = { "mvn", "-Dexec.mainClass" }; + CliArguments args = CliArguments.ofCompletion(argsArray); + CompletionCandidateCollector collector = createCollector(context, argsArray); + + // act + List candidates = context.complete(args, collector, true); + + // assert + assertThat(candidates.stream().map(CompletionCandidate::text)).doesNotContain("-Dexec:mainClass="); + } + + @Test + void testDependencyNotSatisfiedIsSuggested() { + + // arrange + AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); + String[] argsArray = { "mvn", "exec:java", "-Dexec.mainClass" }; + CliArguments args = CliArguments.ofCompletion(argsArray); + CompletionCandidateCollector collector = createCollector(context, argsArray); + + // act + List candidates = context.complete(args, collector, true); + + // assert + assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec:mainClass="); + } + + @Test + void testDependencyOrGroupSatisfiedByEitherAlternative() { + + // arrange + AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); + String[] argsArray = { "mvn", "exec:exec", "-Dexec.args" }; + CliArguments args = CliArguments.ofCompletion(argsArray); + CompletionCandidateCollector collector = createCollector(context, argsArray); + + // act + List candidates = context.complete(args, collector, true); + + // assert + assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec:args="); + } } From c0002dd22e609508560f2f605362a1e4ba0bff0c Mon Sep 17 00:00:00 2001 From: Hiepiscus Date: Sun, 30 Aug 2026 21:56:43 +0200 Subject: [PATCH 3/9] #2193: fix unit test assertions, add null-checks and update Javadocs --- .../completion/AutoCompletionRegistry.java | 32 +++++++++++++++-- .../tools/ide/completion/CompletionEntry.java | 35 ++++++++++++++++--- .../tools/ide/tool/mvn/MavenCommandlet.java | 4 +-- .../tools/ide/completion/CompleteTest.java | 29 +++++++++++---- 4 files changed, 85 insertions(+), 15 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java index 018dc53b00..8519f3ee1b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java @@ -55,29 +55,57 @@ public void complete(String arg, CompletionCandidateCollector collector, } } + /** + * Registers two already-added candidates as alternatives to each other, so that once one is provided on the command line, the other is no longer + * + * @param candidate1 the text of the first candidate (must have been added via {@link #add(String)} before). + * @param candidate2 the text of the second candidate (must have been added via {@link #add(String)} before). + * @throws IllegalStateException if either candidate has not been registered via {@link #add(String)}. + */ public void addAlternative(String candidate1, String candidate2) { CompletionEntry entry1 = findEntry(candidate1); CompletionEntry entry2 = findEntry(candidate2); - + if ((entry1 == null) || (entry2 == null)) { + throw new IllegalStateException("Both candidates must be added via add(String) before calling addAlternative."); + } entry1.addAlternative(entry2); } + /** + * Registers a dependency for {@code candidate}: it is only suggested once at least one of {@code depends} has already been provided on the command line. + * + * @param candidate the text of the dependent candidate (must have been added via {@link #add(String)} before). + * @param depends the texts of the candidates of which at least one must already be provided (OR semantics). + * @throws IllegalStateException if {@code candidate} or any of {@code depends} has not been registered via {@link #add(String)}. + */ public void addDependency(String candidate, List depends) { CompletionEntry entry = findEntry(candidate); + if (entry == null) { + throw new IllegalStateException("Candidate '" + candidate + "' must be added via add(String) before calling addDependency."); + } CompletionEntry[] dependencyEntries = new CompletionEntry[depends.size()]; for (int i = 0; i < depends.size(); i++) { CompletionEntry dependencyEntry = findEntry(depends.get(i)); + + if (dependencyEntry == null) { + throw new IllegalStateException("Candidate '" + depends.get(i) + "' must be added via add(String) before calling addDependency."); + } + dependencyEntries[i] = dependencyEntry; } entry.addDependency(dependencyEntries); } + /** + * @param candidate the candidate to find. + * @return the {@link CompletionEntry} whose {@link CompletionEntry#getCandidate() candidate} matches, or {@code null} if not found. + */ private CompletionEntry findEntry(String candidate) { for (CompletionEntry entry : this.entries) { - if (entry.getCandidate().contains(candidate)) { + if (entry.getCandidate().equals(candidate)) { return entry; } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java index 873ae04b73..da5c38ee78 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java @@ -8,8 +8,8 @@ import com.devonfw.tools.ide.property.Property; /** - * A completion candidate that may have one or more synonyms. When any one of the candidate or its synonyms has already been provided on the command line, none - * of them will be suggested again. + * A completion candidate that may have synonyms, alternatives, or dependencies. An entry will not be suggested if it or its synonyms/alternatives are already + * provided, or if its dependencies are not satisfied. */ public class CompletionEntry { @@ -19,8 +19,10 @@ public class CompletionEntry { /** List of synonym strings for this candidate. */ private List synonyms = new ArrayList<>(); + /** List of alternatives (symmetric relationship). */ private List alternatives = new ArrayList<>(); + /** List of dependency groups (AND logic between groups, OR logic within a group). */ private List> dependencies = new ArrayList<>(); /** @@ -32,6 +34,9 @@ public CompletionEntry(String candidate) { this.candidate = candidate; } + /** + * @return the primary candidate string. + */ public String getCandidate() { return candidate; } @@ -57,7 +62,7 @@ public void complete(String arg, CompletionCandidateCollector collector, Propert Set alreadyProvided = collector.getAlreadyProvided(); if (alreadyProvided != null) { - if (!isDependency(alreadyProvided)) { + if (!isDependencySatisfied(alreadyProvided)) { return; } @@ -83,7 +88,13 @@ public void complete(String arg, CompletionCandidateCollector collector, Propert } } - private boolean isDependency(Set alreadyProvided) { + /** + * Checks whether all configured dependency groups are satisfied. + * + * @param alreadyProvided the set of already provided arguments. + * @return {@code true} if all dependency groups are satisfied, {@code false} otherwise. + */ + private boolean isDependencySatisfied(Set alreadyProvided) { for (List group : this.dependencies) { boolean groupSatisfied = false; @@ -100,10 +111,21 @@ private boolean isDependency(Set alreadyProvided) { return true; } + /** + * Checks if this candidate or any of its synonyms was already provided. + * + * @param alreadyProvided the set of already provided arguments. + * @return {@code true} if already provided, {@code false} otherwise. + */ public boolean isProvided(Set alreadyProvided) { return alreadyProvided.contains(this.candidate) || this.synonyms.stream().anyMatch(alreadyProvided::contains); } + /** + * Adds a symmetric alternative relation between this entry and another. + * + * @param alternative the alternative {@link CompletionEntry}. + */ public void addAlternative(CompletionEntry alternative) { if ((alternative == null) || (alternative == this)) { @@ -119,6 +141,11 @@ public void addAlternative(CompletionEntry alternative) { } } + /** + * Adds an OR-dependency group to this entry. + * + * @param entries array of {@link CompletionEntry} objects of which at least one must be provided. + */ public void addDependency(CompletionEntry[] entries) { if ((entries == null) || (entries.length == 0)) { return; diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java index df2656e653..abb8af1a61 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java @@ -49,8 +49,6 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("help:effective-settings"); registry.add("-DskipTests"); registry.add("-Dmaven.test.skip=true"); - registry.add("exec:java"); - registry.add("exec:exec"); registry.add("-P"); registry.add("-pl"); registry.add("-am"); @@ -75,6 +73,8 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("-Dstyle.color="); registry.add("-Duser.dir="); registry.add("-Duser.home="); + registry.add("exec:java"); + registry.add("exec:exec"); registry.addAlternative("exec:java", "exec:exec"); registry.add("-Dexec.mainClass="); registry.addDependency("-Dexec.mainClass=", List.of("exec:java")); diff --git a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java index ba382f0fe3..0d25e44ef9 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java @@ -426,6 +426,9 @@ void testCompleteMavenSecondToolArgument() { .contains("dependency:list", "dependency:tree", "deploy"); } + /** + * + */ @Test void testAlternativeFilteringWhenOtherAlternativeProvidedJava() { @@ -443,6 +446,9 @@ void testAlternativeFilteringWhenOtherAlternativeProvidedJava() { assertThat(texts).doesNotContain("exec:exec"); } + /** + * + */ @Test void testAlternativeFilteringWhenOtherAlternativeProvidedExec() { @@ -460,12 +466,15 @@ void testAlternativeFilteringWhenOtherAlternativeProvidedExec() { assertThat(texts).doesNotContain("exec:java"); } + /** + * Test that an entry with an unsatisfied dependency is not suggested. + */ @Test void testDependencyNotSatisfiedIsNotSuggested() { // arrange AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); - String[] argsArray = { "mvn", "-Dexec.mainClass" }; + String[] argsArray = { "mvn", "-Dexec.main" }; CliArguments args = CliArguments.ofCompletion(argsArray); CompletionCandidateCollector collector = createCollector(context, argsArray); @@ -473,15 +482,18 @@ void testDependencyNotSatisfiedIsNotSuggested() { List candidates = context.complete(args, collector, true); // assert - assertThat(candidates.stream().map(CompletionCandidate::text)).doesNotContain("-Dexec:mainClass="); + assertThat(candidates.stream().map(CompletionCandidate::text)).doesNotContain("-Dexec.mainClass="); } + /** + * Test that an entry with a satisfied dependency is suggested. + */ @Test - void testDependencyNotSatisfiedIsSuggested() { + void testDependencySatisfiedIsSuggested() { // arrange AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); - String[] argsArray = { "mvn", "exec:java", "-Dexec.mainClass" }; + String[] argsArray = { "mvn", "exec:java", "-Dexec.mainCla" }; CliArguments args = CliArguments.ofCompletion(argsArray); CompletionCandidateCollector collector = createCollector(context, argsArray); @@ -489,15 +501,18 @@ void testDependencyNotSatisfiedIsSuggested() { List candidates = context.complete(args, collector, true); // assert - assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec:mainClass="); + assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec.mainClass="); } + /** + * Test that an entry with an OR-dependency is suggested if any alternative of the group is provided. + */ @Test void testDependencyOrGroupSatisfiedByEitherAlternative() { // arrange AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); - String[] argsArray = { "mvn", "exec:exec", "-Dexec.args" }; + String[] argsArray = { "mvn", "exec:exec", "-Dexec.arg" }; CliArguments args = CliArguments.ofCompletion(argsArray); CompletionCandidateCollector collector = createCollector(context, argsArray); @@ -505,6 +520,6 @@ void testDependencyOrGroupSatisfiedByEitherAlternative() { List candidates = context.complete(args, collector, true); // assert - assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec:args="); + assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec.args="); } } From 1ec7fcbb7ade5f5ad5b01607c6212b1d8ccdb883 Mon Sep 17 00:00:00 2001 From: Hiepiscus Date: Sun, 30 Aug 2026 22:03:06 +0200 Subject: [PATCH 4/9] #2193: update Javadocs --- .../devonfw/tools/ide/completion/AutoCompletionRegistry.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java index 8519f3ee1b..368d8de349 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java @@ -56,7 +56,8 @@ public void complete(String arg, CompletionCandidateCollector collector, } /** - * Registers two already-added candidates as alternatives to each other, so that once one is provided on the command line, the other is no longer + * Registers two already-added candidates as alternatives to each other, so that once one is provided on the command line, + * the other is no longer be suggested. * * @param candidate1 the text of the first candidate (must have been added via {@link #add(String)} before). * @param candidate2 the text of the second candidate (must have been added via {@link #add(String)} before). From 508b62bea7fb9d868d33c6c42fa73864371155b2 Mon Sep 17 00:00:00 2001 From: Hiepiscus Date: Thu, 27 Aug 2026 15:07:35 +0200 Subject: [PATCH 5/9] #2193: Add alternative to completion entries --- .../completion/AutoCompletionRegistry.java | 16 +++++++- .../tools/ide/completion/CompletionEntry.java | 36 +++++++++++++++++- .../tools/ide/tool/mvn/MavenCommandlet.java | 4 ++ .../tools/ide/completion/CompleteTest.java | 38 ++++++++++++++++++- 4 files changed, 89 insertions(+), 5 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java index 7948f360a1..7cca7451ef 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java @@ -39,7 +39,6 @@ public void add(String candidate, String synonym) { this.entries.add(entry); } - /** * Adds all candidates matching the given argument to the collector. * @@ -56,5 +55,20 @@ public void complete(String arg, CompletionCandidateCollector collector, } } + public void addAlternative(String candidate1, String candidate2) { + + CompletionEntry entry1 = findEntry(candidate1); + CompletionEntry entry2 = findEntry(candidate2); + + entry1.addAlternative(entry2); + } + private CompletionEntry findEntry(String candidate) { + for (CompletionEntry entry : this.entries) { + if (entry.getCandidate().contains(candidate)) { + return entry; + } + } + return null; + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java index 9df9cc7439..74eebab2f1 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java @@ -19,6 +19,8 @@ public class CompletionEntry { /** List of synonym strings for this candidate. */ private List synonyms = new ArrayList<>(); + private List alternatives = new ArrayList<>(); + /** * The constructor. * @@ -28,6 +30,10 @@ public CompletionEntry(String candidate) { this.candidate = candidate; } + public String getCandidate() { + return candidate; + } + /** * Adds a synonym for this candidate. * @@ -48,8 +54,16 @@ public void addSynonym(String synonym) { public void complete(String arg, CompletionCandidateCollector collector, Property property, Commandlet commandlet) { Set alreadyProvided = collector.getAlreadyProvided(); - if (alreadyProvided != null && (alreadyProvided.contains(this.candidate) || synonyms.stream().anyMatch(alreadyProvided::contains))) { - return; + if (alreadyProvided != null) { + if (isProvided(alreadyProvided)) { + return; + } + + for (CompletionEntry alternative : this.alternatives) { + if (alternative.isProvided(alreadyProvided)) { + return; + } + } } if (candidate.startsWith(arg)) { @@ -63,4 +77,22 @@ public void complete(String arg, CompletionCandidateCollector collector, Propert } } + public boolean isProvided(Set alreadyProvided) { + return alreadyProvided.contains(this.candidate) || this.synonyms.stream().anyMatch(alreadyProvided::contains); + } + + public void addAlternative(CompletionEntry alternative) { + + if ((alternative == null) || (alternative == this)) { + return; + } + + if (!this.alternatives.contains(alternative)) { + this.alternatives.add(alternative); + } + + if (!alternative.alternatives.contains(this)) { + alternative.alternatives.add(this); + } + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java index d843e41e1a..6fb61b66d2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java @@ -49,6 +49,7 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("-DskipTests"); registry.add("-Dmaven.test.skip=true"); registry.add("exec:java"); + registry.add("exec:exec"); registry.add("-Dexec.mainClass="); registry.add("-Dexec.args="); registry.add("-P"); @@ -75,6 +76,9 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("-Dstyle.color="); registry.add("-Duser.dir="); registry.add("-Duser.home="); + registry.addAlternative("exec:java", "exec:exec"); + //registry.addDependency("-Dexec.mainClass=", "exec:java"); + //registry.addDependency("-Dexec.mainClass=", "exec:exec"); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java index eaa0052768..4d910ba03d 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java @@ -407,8 +407,8 @@ void testSynonymFilteringWithProvidedSynonym() { } /** - * Test that completion works for a second tool argument (e.g. "ide mvn clean [tab]"), which is the real-world scenario - * that previously failed because the multivalued arguments property consumed the completion marker greedily. + * Test that completion works for a second tool argument (e.g. "ide mvn clean [tab]"), which is the real-world scenario that previously failed because the + * multivalued arguments property consumed the completion marker greedily. */ @Test void testCompleteMavenSecondToolArgument() { @@ -425,4 +425,38 @@ void testCompleteMavenSecondToolArgument() { assertThat(candidates.stream().map(CompletionCandidate::text)) .contains("dependency:list", "dependency:tree", "deploy"); } + + @Test + void testAlternativeFilteringWhenOtherAlternativeProvidedJava() { + + // arrange + AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); + String[] argsArray = { "mvn", "exec:java", "" }; + CliArguments args = CliArguments.ofCompletion(argsArray); + CompletionCandidateCollector collector = createCollector(context, argsArray); + + // act + List candidates = context.complete(args, collector, true); + + // assert + List texts = candidates.stream().map(CompletionCandidate::text).toList(); + assertThat(texts).doesNotContain("exec:exec"); + } + + @Test + void testAlternativeFilteringWhenOtherAlternativeProvidedExec() { + + // arrange + AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); + String[] argsArray = { "mvn", "exec:exec", "" }; + CliArguments args = CliArguments.ofCompletion(argsArray); + CompletionCandidateCollector collector = createCollector(context, argsArray); + + // act + List candidates = context.complete(args, collector, true); + + // assert + List texts = candidates.stream().map(CompletionCandidate::text).toList(); + assertThat(texts).doesNotContain("exec:java"); + } } From bb0ee1b2d795966c8aa1ca86a1fb38bfeb3a1091 Mon Sep 17 00:00:00 2001 From: Hiepiscus Date: Fri, 28 Aug 2026 17:27:14 +0200 Subject: [PATCH 6/9] #2193: Add dependencies to completion entries --- .../completion/AutoCompletionRegistry.java | 12 +++++ .../tools/ide/completion/CompletionEntry.java | 30 ++++++++++++ .../tools/ide/tool/mvn/MavenCommandlet.java | 9 ++-- .../tools/ide/completion/CompleteTest.java | 48 +++++++++++++++++++ 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java index 7cca7451ef..018dc53b00 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java @@ -63,6 +63,18 @@ public void addAlternative(String candidate1, String candidate2) { entry1.addAlternative(entry2); } + public void addDependency(String candidate, List depends) { + + CompletionEntry entry = findEntry(candidate); + + CompletionEntry[] dependencyEntries = new CompletionEntry[depends.size()]; + for (int i = 0; i < depends.size(); i++) { + CompletionEntry dependencyEntry = findEntry(depends.get(i)); + dependencyEntries[i] = dependencyEntry; + } + entry.addDependency(dependencyEntries); + } + private CompletionEntry findEntry(String candidate) { for (CompletionEntry entry : this.entries) { if (entry.getCandidate().contains(candidate)) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java index 74eebab2f1..873ae04b73 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java @@ -21,6 +21,8 @@ public class CompletionEntry { private List alternatives = new ArrayList<>(); + private List> dependencies = new ArrayList<>(); + /** * The constructor. * @@ -55,6 +57,10 @@ public void complete(String arg, CompletionCandidateCollector collector, Propert Set alreadyProvided = collector.getAlreadyProvided(); if (alreadyProvided != null) { + if (!isDependency(alreadyProvided)) { + return; + } + if (isProvided(alreadyProvided)) { return; } @@ -77,6 +83,23 @@ public void complete(String arg, CompletionCandidateCollector collector, Propert } } + private boolean isDependency(Set alreadyProvided) { + + for (List group : this.dependencies) { + boolean groupSatisfied = false; + for (CompletionEntry entry : group) { + if (entry.isProvided(alreadyProvided)) { + groupSatisfied = true; + break; + } + } + if (!groupSatisfied) { + return false; + } + } + return true; + } + public boolean isProvided(Set alreadyProvided) { return alreadyProvided.contains(this.candidate) || this.synonyms.stream().anyMatch(alreadyProvided::contains); } @@ -95,4 +118,11 @@ public void addAlternative(CompletionEntry alternative) { alternative.alternatives.add(this); } } + + public void addDependency(CompletionEntry[] entries) { + if ((entries == null) || (entries.length == 0)) { + return; + } + this.dependencies.add(List.of(entries)); + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java index 6fb61b66d2..df2656e653 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java @@ -1,6 +1,7 @@ package com.devonfw.tools.ide.tool.mvn; +import java.util.List; import java.util.Set; @@ -50,8 +51,6 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("-Dmaven.test.skip=true"); registry.add("exec:java"); registry.add("exec:exec"); - registry.add("-Dexec.mainClass="); - registry.add("-Dexec.args="); registry.add("-P"); registry.add("-pl"); registry.add("-am"); @@ -77,8 +76,10 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("-Duser.dir="); registry.add("-Duser.home="); registry.addAlternative("exec:java", "exec:exec"); - //registry.addDependency("-Dexec.mainClass=", "exec:java"); - //registry.addDependency("-Dexec.mainClass=", "exec:exec"); + registry.add("-Dexec.mainClass="); + registry.addDependency("-Dexec.mainClass=", List.of("exec:java")); + registry.add("-Dexec.args="); + registry.addDependency("-Dexec.args=", List.of("exec:java", "exec:exec")); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java index 4d910ba03d..ba382f0fe3 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java @@ -459,4 +459,52 @@ void testAlternativeFilteringWhenOtherAlternativeProvidedExec() { List texts = candidates.stream().map(CompletionCandidate::text).toList(); assertThat(texts).doesNotContain("exec:java"); } + + @Test + void testDependencyNotSatisfiedIsNotSuggested() { + + // arrange + AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); + String[] argsArray = { "mvn", "-Dexec.mainClass" }; + CliArguments args = CliArguments.ofCompletion(argsArray); + CompletionCandidateCollector collector = createCollector(context, argsArray); + + // act + List candidates = context.complete(args, collector, true); + + // assert + assertThat(candidates.stream().map(CompletionCandidate::text)).doesNotContain("-Dexec:mainClass="); + } + + @Test + void testDependencyNotSatisfiedIsSuggested() { + + // arrange + AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); + String[] argsArray = { "mvn", "exec:java", "-Dexec.mainClass" }; + CliArguments args = CliArguments.ofCompletion(argsArray); + CompletionCandidateCollector collector = createCollector(context, argsArray); + + // act + List candidates = context.complete(args, collector, true); + + // assert + assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec:mainClass="); + } + + @Test + void testDependencyOrGroupSatisfiedByEitherAlternative() { + + // arrange + AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); + String[] argsArray = { "mvn", "exec:exec", "-Dexec.args" }; + CliArguments args = CliArguments.ofCompletion(argsArray); + CompletionCandidateCollector collector = createCollector(context, argsArray); + + // act + List candidates = context.complete(args, collector, true); + + // assert + assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec:args="); + } } From 556d2c239e0130f55e2c693c070434fb0e9c4606 Mon Sep 17 00:00:00 2001 From: Hiepiscus Date: Sun, 30 Aug 2026 21:56:43 +0200 Subject: [PATCH 7/9] #2193: fix unit test assertions, add null-checks and update Javadocs --- .../completion/AutoCompletionRegistry.java | 32 +++++++++++++++-- .../tools/ide/completion/CompletionEntry.java | 35 ++++++++++++++++--- .../tools/ide/tool/mvn/MavenCommandlet.java | 4 +-- .../tools/ide/completion/CompleteTest.java | 29 +++++++++++---- 4 files changed, 85 insertions(+), 15 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java index 018dc53b00..8519f3ee1b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java @@ -55,29 +55,57 @@ public void complete(String arg, CompletionCandidateCollector collector, } } + /** + * Registers two already-added candidates as alternatives to each other, so that once one is provided on the command line, the other is no longer + * + * @param candidate1 the text of the first candidate (must have been added via {@link #add(String)} before). + * @param candidate2 the text of the second candidate (must have been added via {@link #add(String)} before). + * @throws IllegalStateException if either candidate has not been registered via {@link #add(String)}. + */ public void addAlternative(String candidate1, String candidate2) { CompletionEntry entry1 = findEntry(candidate1); CompletionEntry entry2 = findEntry(candidate2); - + if ((entry1 == null) || (entry2 == null)) { + throw new IllegalStateException("Both candidates must be added via add(String) before calling addAlternative."); + } entry1.addAlternative(entry2); } + /** + * Registers a dependency for {@code candidate}: it is only suggested once at least one of {@code depends} has already been provided on the command line. + * + * @param candidate the text of the dependent candidate (must have been added via {@link #add(String)} before). + * @param depends the texts of the candidates of which at least one must already be provided (OR semantics). + * @throws IllegalStateException if {@code candidate} or any of {@code depends} has not been registered via {@link #add(String)}. + */ public void addDependency(String candidate, List depends) { CompletionEntry entry = findEntry(candidate); + if (entry == null) { + throw new IllegalStateException("Candidate '" + candidate + "' must be added via add(String) before calling addDependency."); + } CompletionEntry[] dependencyEntries = new CompletionEntry[depends.size()]; for (int i = 0; i < depends.size(); i++) { CompletionEntry dependencyEntry = findEntry(depends.get(i)); + + if (dependencyEntry == null) { + throw new IllegalStateException("Candidate '" + depends.get(i) + "' must be added via add(String) before calling addDependency."); + } + dependencyEntries[i] = dependencyEntry; } entry.addDependency(dependencyEntries); } + /** + * @param candidate the candidate to find. + * @return the {@link CompletionEntry} whose {@link CompletionEntry#getCandidate() candidate} matches, or {@code null} if not found. + */ private CompletionEntry findEntry(String candidate) { for (CompletionEntry entry : this.entries) { - if (entry.getCandidate().contains(candidate)) { + if (entry.getCandidate().equals(candidate)) { return entry; } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java index 873ae04b73..da5c38ee78 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionEntry.java @@ -8,8 +8,8 @@ import com.devonfw.tools.ide.property.Property; /** - * A completion candidate that may have one or more synonyms. When any one of the candidate or its synonyms has already been provided on the command line, none - * of them will be suggested again. + * A completion candidate that may have synonyms, alternatives, or dependencies. An entry will not be suggested if it or its synonyms/alternatives are already + * provided, or if its dependencies are not satisfied. */ public class CompletionEntry { @@ -19,8 +19,10 @@ public class CompletionEntry { /** List of synonym strings for this candidate. */ private List synonyms = new ArrayList<>(); + /** List of alternatives (symmetric relationship). */ private List alternatives = new ArrayList<>(); + /** List of dependency groups (AND logic between groups, OR logic within a group). */ private List> dependencies = new ArrayList<>(); /** @@ -32,6 +34,9 @@ public CompletionEntry(String candidate) { this.candidate = candidate; } + /** + * @return the primary candidate string. + */ public String getCandidate() { return candidate; } @@ -57,7 +62,7 @@ public void complete(String arg, CompletionCandidateCollector collector, Propert Set alreadyProvided = collector.getAlreadyProvided(); if (alreadyProvided != null) { - if (!isDependency(alreadyProvided)) { + if (!isDependencySatisfied(alreadyProvided)) { return; } @@ -83,7 +88,13 @@ public void complete(String arg, CompletionCandidateCollector collector, Propert } } - private boolean isDependency(Set alreadyProvided) { + /** + * Checks whether all configured dependency groups are satisfied. + * + * @param alreadyProvided the set of already provided arguments. + * @return {@code true} if all dependency groups are satisfied, {@code false} otherwise. + */ + private boolean isDependencySatisfied(Set alreadyProvided) { for (List group : this.dependencies) { boolean groupSatisfied = false; @@ -100,10 +111,21 @@ private boolean isDependency(Set alreadyProvided) { return true; } + /** + * Checks if this candidate or any of its synonyms was already provided. + * + * @param alreadyProvided the set of already provided arguments. + * @return {@code true} if already provided, {@code false} otherwise. + */ public boolean isProvided(Set alreadyProvided) { return alreadyProvided.contains(this.candidate) || this.synonyms.stream().anyMatch(alreadyProvided::contains); } + /** + * Adds a symmetric alternative relation between this entry and another. + * + * @param alternative the alternative {@link CompletionEntry}. + */ public void addAlternative(CompletionEntry alternative) { if ((alternative == null) || (alternative == this)) { @@ -119,6 +141,11 @@ public void addAlternative(CompletionEntry alternative) { } } + /** + * Adds an OR-dependency group to this entry. + * + * @param entries array of {@link CompletionEntry} objects of which at least one must be provided. + */ public void addDependency(CompletionEntry[] entries) { if ((entries == null) || (entries.length == 0)) { return; diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java index df2656e653..abb8af1a61 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java @@ -49,8 +49,6 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("help:effective-settings"); registry.add("-DskipTests"); registry.add("-Dmaven.test.skip=true"); - registry.add("exec:java"); - registry.add("exec:exec"); registry.add("-P"); registry.add("-pl"); registry.add("-am"); @@ -75,6 +73,8 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.add("-Dstyle.color="); registry.add("-Duser.dir="); registry.add("-Duser.home="); + registry.add("exec:java"); + registry.add("exec:exec"); registry.addAlternative("exec:java", "exec:exec"); registry.add("-Dexec.mainClass="); registry.addDependency("-Dexec.mainClass=", List.of("exec:java")); diff --git a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java index ba382f0fe3..0d25e44ef9 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/completion/CompleteTest.java @@ -426,6 +426,9 @@ void testCompleteMavenSecondToolArgument() { .contains("dependency:list", "dependency:tree", "deploy"); } + /** + * + */ @Test void testAlternativeFilteringWhenOtherAlternativeProvidedJava() { @@ -443,6 +446,9 @@ void testAlternativeFilteringWhenOtherAlternativeProvidedJava() { assertThat(texts).doesNotContain("exec:exec"); } + /** + * + */ @Test void testAlternativeFilteringWhenOtherAlternativeProvidedExec() { @@ -460,12 +466,15 @@ void testAlternativeFilteringWhenOtherAlternativeProvidedExec() { assertThat(texts).doesNotContain("exec:java"); } + /** + * Test that an entry with an unsatisfied dependency is not suggested. + */ @Test void testDependencyNotSatisfiedIsNotSuggested() { // arrange AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); - String[] argsArray = { "mvn", "-Dexec.mainClass" }; + String[] argsArray = { "mvn", "-Dexec.main" }; CliArguments args = CliArguments.ofCompletion(argsArray); CompletionCandidateCollector collector = createCollector(context, argsArray); @@ -473,15 +482,18 @@ void testDependencyNotSatisfiedIsNotSuggested() { List candidates = context.complete(args, collector, true); // assert - assertThat(candidates.stream().map(CompletionCandidate::text)).doesNotContain("-Dexec:mainClass="); + assertThat(candidates.stream().map(CompletionCandidate::text)).doesNotContain("-Dexec.mainClass="); } + /** + * Test that an entry with a satisfied dependency is suggested. + */ @Test - void testDependencyNotSatisfiedIsSuggested() { + void testDependencySatisfiedIsSuggested() { // arrange AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); - String[] argsArray = { "mvn", "exec:java", "-Dexec.mainClass" }; + String[] argsArray = { "mvn", "exec:java", "-Dexec.mainCla" }; CliArguments args = CliArguments.ofCompletion(argsArray); CompletionCandidateCollector collector = createCollector(context, argsArray); @@ -489,15 +501,18 @@ void testDependencyNotSatisfiedIsSuggested() { List candidates = context.complete(args, collector, true); // assert - assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec:mainClass="); + assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec.mainClass="); } + /** + * Test that an entry with an OR-dependency is suggested if any alternative of the group is provided. + */ @Test void testDependencyOrGroupSatisfiedByEitherAlternative() { // arrange AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); - String[] argsArray = { "mvn", "exec:exec", "-Dexec.args" }; + String[] argsArray = { "mvn", "exec:exec", "-Dexec.arg" }; CliArguments args = CliArguments.ofCompletion(argsArray); CompletionCandidateCollector collector = createCollector(context, argsArray); @@ -505,6 +520,6 @@ void testDependencyOrGroupSatisfiedByEitherAlternative() { List candidates = context.complete(args, collector, true); // assert - assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec:args="); + assertThat(candidates.stream().map(CompletionCandidate::text)).contains("-Dexec.args="); } } From 57c0f206ef0d386888291a0c74815bce998bde37 Mon Sep 17 00:00:00 2001 From: Hiepiscus Date: Sun, 30 Aug 2026 22:03:06 +0200 Subject: [PATCH 8/9] #2193: update Javadocs --- .../devonfw/tools/ide/completion/AutoCompletionRegistry.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java index 8519f3ee1b..368d8de349 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/AutoCompletionRegistry.java @@ -56,7 +56,8 @@ public void complete(String arg, CompletionCandidateCollector collector, } /** - * Registers two already-added candidates as alternatives to each other, so that once one is provided on the command line, the other is no longer + * Registers two already-added candidates as alternatives to each other, so that once one is provided on the command line, + * the other is no longer be suggested. * * @param candidate1 the text of the first candidate (must have been added via {@link #add(String)} before). * @param candidate2 the text of the second candidate (must have been added via {@link #add(String)} before). From 81f5d8fe729f57d40904b1e61448c50c34819b5e Mon Sep 17 00:00:00 2001 From: Hiepiscus Date: Mon, 31 Aug 2026 10:13:30 +0200 Subject: [PATCH 9/9] #2193: Applied spotless and changed CHANGELOG.adoc --- CHANGELOG.adoc | 1 + .../java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 74e0e5bb3b..e7e497a6d5 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -6,6 +6,7 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE Release with new features and bugfixes: +* https://github.com/devonfw/IDEasy/issues/2193[#2193]: Added support for conditional auto-completion * https://github.com/devonfw/IDEasy/issues/1525[#1525]: Document known issue and workaround for lombok plugin in Eclipse * https://github.com/devonfw/IDEasy/issues/1031[#1031]: Added OpenRewrite commandlet * https://github.com/devonfw/IDEasy/issues/2361[#2361]: Improve dotnet installation by setting DOTNET_ROOT diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java index abb8af1a61..5d8184db16 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MavenCommandlet.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Set; - import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.completion.AutoCompletionRegistry; import com.devonfw.tools.ide.context.IdeContext; @@ -82,5 +81,3 @@ protected void initAutoCompletionRegistry(AutoCompletionRegistry registry) { registry.addDependency("-Dexec.args=", List.of("exec:java", "exec:exec")); } } - -