From 1ebcaf54fbfbb7022eb45124cfc47e000a2a4836 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 10:49:26 +0200 Subject: [PATCH 01/49] Add completion tests --- .../ide/commandlet/CommandletManagerTest.java | 64 +++++++++ .../ide/commandlet/ShellCommandletTest.java | 33 +++++ .../ide/context/AbstractIdeContextTest.java | 20 +++ .../tools/ide/property/PropertyTest.java | 121 ++++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 cli/src/test/java/com/devonfw/tools/ide/commandlet/CommandletManagerTest.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/commandlet/ShellCommandletTest.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/property/PropertyTest.java diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CommandletManagerTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CommandletManagerTest.java new file mode 100644 index 0000000000..6fa7a0eaa9 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CommandletManagerTest.java @@ -0,0 +1,64 @@ +package com.devonfw.tools.ide.commandlet; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import com.devonfw.tools.ide.cli.CliArguments; +import com.devonfw.tools.ide.completion.CompletionCandidate; +import com.devonfw.tools.ide.completion.CompletionCandidateCollector; +import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault; +import com.devonfw.tools.ide.context.AbstractIdeContext; +import com.devonfw.tools.ide.context.IdeTestContext; +import org.junit.jupiter.api.Test; + +public class CommandletManagerTest { + @Test + public void testMvnCommandletIsPartOfCommandletIterator() { + IdeTestContext context = new IdeTestContext(); + CommandletManager manager = context.getCommandletManager(); + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault( + context + ); + + assertThat(manager.findCommandlet(new CliArguments("mvn"), collector).hasNext()).isTrue(); + } + + @Test + public void testCollectCompletionCandidatesEmptyInputReturnsAllCommandlets() { + IdeTestContext context = new IdeTestContext(); + CommandletManager manager = context.getCommandletManager(); + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(context); + + manager.collectCompletionCandidates(CliArguments.ofCompletion(""), collector); + + List lines = collector.getSortedCandidates().stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).contains("complete", "create", "env", "install", "shell", "status", "update", + "mvn"); + } + + @Test + public void testCollectCompletionCandidatesPartialInputFiltersToMatches() { + IdeTestContext context = new IdeTestContext(); + CommandletManager manager = context.getCommandletManager(); + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(context); + + manager.collectCompletionCandidates(CliArguments.ofCompletion("ins"), collector); + + List lines = collector.getSortedCandidates().stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).containsExactly("install"); + } + + @Test + public void testCollectCompletionCandidatesNoMatchReturnsEmpty() { + IdeTestContext context = new IdeTestContext(); + CommandletManager manager = context.getCommandletManager(); + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(context); + + manager.collectCompletionCandidates(CliArguments.ofCompletion("xyz"), collector); + + assertThat(collector.getSortedCandidates()).isEmpty(); + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/ShellCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/ShellCommandletTest.java new file mode 100644 index 0000000000..08dbeb8f1f --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/ShellCommandletTest.java @@ -0,0 +1,33 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.cli.CliArguments; +import com.devonfw.tools.ide.completion.CompletionCandidate; +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeTestContext; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Test of {@link ShellCommandlet}. + */ +class ShellCommandletTest extends AbstractIdeContextTest { + @Test + public void testExitIsOfferedAsCompletion() { + IdeTestContext context = new IdeTestContext(); + CliArguments args = CliArguments.of(0, ""); + + List lines = context.complete(args, false).stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).contains("exit"); + } + + @Test + public void testExitIsOfferedOnPartialInput() { + IdeTestContext context = new IdeTestContext(); + CliArguments args = CliArguments.of(0, "ex"); + + List lines = context.complete(args, false).stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).containsExactly("exit"); + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java index ad5d37022a..5bc3a6329f 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java @@ -12,7 +12,12 @@ import java.util.Set; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import com.devonfw.tools.ide.cli.CliArguments; +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.completion.CompletionCandidateCollector; +import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault; import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.io.FileCopyMode; import com.devonfw.tools.ide.io.IdeProgressBarTestImpl; @@ -319,4 +324,19 @@ protected void verifyStartScript(IdeContext context, String ide, String workspac } } + @Test + public void testMvIsCompletedToMvn() { + AbstractIdeContext context = new IdeTestContext(); + CliArguments args = CliArguments.ofCompletion("mv"); + + assertThat(context.complete(args, true).getFirst().text()).isEqualTo("mvn"); + } + + @Test + public void testMvIsCompletedToMvnWithoutContextCompletion() { + AbstractIdeContext context = new IdeTestContext(); + CliArguments args = CliArguments.ofCompletion("mv"); + + assertThat(context.complete(args, false).getFirst().text()).isEqualTo("mvn"); + } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/property/PropertyTest.java b/cli/src/test/java/com/devonfw/tools/ide/property/PropertyTest.java new file mode 100644 index 0000000000..49f99a06d6 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/property/PropertyTest.java @@ -0,0 +1,121 @@ +package com.devonfw.tools.ide.property; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.devonfw.tools.ide.cli.CliArguments; +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.commandlet.ContextCommandlet; +import com.devonfw.tools.ide.completion.CompletionCandidateCollector; +import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.context.IdeTestContext; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for class `Property`. + */ +public class PropertyTest { + @Test + public void testApplyAddsPropertyAsCompletionCandidate() { + IdeContext context = new IdeTestContext(); + CliArguments args = CliArguments.ofCompletion("--for"); + + Commandlet ctxCmd = new ContextCommandlet(); + Property flagProperty = new FlagProperty("--force"); + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault( + context + ); + + args.next(); + + flagProperty.apply("--force", args, context, ctxCmd, collector); + + assertThat(collector.getSortedCandidates().getFirst().text()).isEqualTo("--force"); + } + + @Test + public void testApplyWithCliArgumentAddsPropertyAsCompletionCandidate() { + IdeContext context = new IdeTestContext(); + CliArguments args = CliArguments.ofCompletion("mv"); + + Commandlet mvnCmd = context.getCommandletManager().getCommandletByFirstKeyword("mvn"); + Property keywordProperty = new KeywordProperty("mvn", false, ""); + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault( + context + ); + + args.next(); + + keywordProperty.apply(args, context, mvnCmd, collector); + + assertThat(collector.getSortedCandidates().getFirst().text()).isEqualTo("mvn"); + } + + @Test + public void testMismatchingApplyReturnsFalse() { + IdeContext context = new IdeTestContext(); + CliArguments args = CliArguments.ofCompletion("mv"); + + Commandlet mvnCmd = context.getCommandletManager().getCommandletByFirstKeyword("mvn"); + Property flagProperty = new FlagProperty("--force"); + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault( + context + ); + + args.next(); + + assertThat(flagProperty.apply(args, context, mvnCmd, collector)).isFalse(); + } + + @Test + public void testMismatchingApplyOnKeywordReturnsFalse() { + IdeContext context = new IdeTestContext(); + CliArguments args = CliArguments.ofCompletion("st"); + + Commandlet mvnCmd = context.getCommandletManager().getCommandletByFirstKeyword("mvn"); + Property keywordProperty = new KeywordProperty("mvn", false, ""); + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault( + context + ); + + args.next(); + + assertThat(keywordProperty.apply(args, context, mvnCmd, collector)).isFalse(); + } + + @Test + public void testCompleteAddsToCandidatesOnMatch() { + IdeContext context = new IdeTestContext(); + CliArguments args = CliArguments.ofCompletion("--for"); + + Commandlet ctxCmd = context.getCommandletManager().getCommandletByFirstKeyword("context"); + Property flagProperty = new FlagProperty("--force"); + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault( + context + ); + + args.next(); + + flagProperty.complete("--force", args.current(), args, context, ctxCmd, collector); + + assertThat(collector.getSortedCandidates().getFirst().text()).isEqualTo("--force"); + } + + @Test + public void testCompleteAddsCandidateForNonOption() { + IdeContext context = new IdeTestContext(); + CliArguments args = CliArguments.ofCompletion("mv"); + + Commandlet ctxCmd = context.getCommandletManager().getCommandletByFirstKeyword("mvn"); + Property keywordProperty = new KeywordProperty("mvn", false, ""); + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault( + context + ); + + args.next(); + + keywordProperty.complete("mvn", args.current(), args, context, ctxCmd, collector); + + assertThat(collector.getSortedCandidates().getFirst().text()).isEqualTo("mvn"); + } +} From 608c40c848fe71502de97bd19a1fcfd79d59bc50 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 10:51:03 +0200 Subject: [PATCH 02/49] Allow selection of completion argument in CliArguments --- .../java/com/devonfw/tools/ide/cli/CliArgument.java | 13 +++++++++++++ .../com/devonfw/tools/ide/cli/CliArguments.java | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/cli/CliArgument.java b/cli/src/main/java/com/devonfw/tools/ide/cli/CliArgument.java index d91efa30cb..58bce2a9f6 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/cli/CliArgument.java +++ b/cli/src/main/java/com/devonfw/tools/ide/cli/CliArgument.java @@ -273,6 +273,19 @@ public String toString() { return this.arg; } + public static CliArgument of(int completionIdx, String... args) { + CliArgument current = END; + int last = args.length - 1; + + for (int argsIdx = last; argsIdx >= 0; argsIdx--) { + String arg = args[argsIdx]; + boolean completionArg = argsIdx == completionIdx; + + current = new CliArgument(arg, current, completionArg); + } + + return current.createStart(); + } /** * @param args the command-line arguments (e.g. from {@code main} method). * @return the first {@link CliArgument} of the parsed arguments or {@code null} if for empty arguments. diff --git a/cli/src/main/java/com/devonfw/tools/ide/cli/CliArguments.java b/cli/src/main/java/com/devonfw/tools/ide/cli/CliArguments.java index 248a6c495c..a272644072 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/cli/CliArguments.java +++ b/cli/src/main/java/com/devonfw/tools/ide/cli/CliArguments.java @@ -15,6 +15,10 @@ public class CliArguments implements Iterator { private boolean splitShortOpts; + public CliArguments(int completionIdx, String... args) { + this(CliArgument.of(completionIdx, args)); + } + /** * The constructor. * @@ -165,6 +169,10 @@ public String toString() { return this.currentArg.getArgs(); } + public static CliArguments of(int completionIdx, String... args) { + return new CliArguments(completionIdx, args); + } + /** * @param args the {@link CliArgument#of(String...) command line arguments}. * @return the {@link CliArguments}. From 2daba5d5eaf86b2dc6d148e6b0cc8a43df5f8d20 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 10:52:01 +0200 Subject: [PATCH 03/49] Add more completion tests --- .../devonfw/tools/ide/tool/mvn/MvnTest.java | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnTest.java index 7cf8e2d121..735c69d060 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnTest.java @@ -4,12 +4,15 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; +import com.devonfw.tools.ide.cli.CliArguments; +import com.devonfw.tools.ide.completion.CompletionCandidate; import com.devonfw.tools.ide.context.AbstractIdeContextTest; import com.devonfw.tools.ide.context.IdeTestContext; import com.devonfw.tools.ide.io.FileAccess; @@ -138,4 +141,126 @@ private void assertFileContent(Path filePath, List expectedValues) throw assertThat(values).containsExactlyInAnyOrderElementsOf(expectedValues); } + + @Test + public void testEmptyGoalInputSuggestsAllMavenProperties() { + String path = "project/workspaces"; + + IdeTestContext context = newContext(PROJECT_MVN, path, false); + CliArguments args = CliArguments.of(1, "mvn", ""); + + List lines = context.complete(args, false).stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).contains("clean", "compile", "test", "package", "install", "deploy"); + } + + @Test + public void testEmptyInputSuggestsFlagProperties() { + String path = "project/workspaces"; + + IdeTestContext context = newContext(PROJECT_MVN, path, false); + CliArguments args = CliArguments.of(1, "mvn", ""); + + List lines = context.complete(args, false).stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).contains("--also-make", "--also-make-dependents", "--fail-at-end", + "--fail-fast", "--threads", "-DskipTests", "-DdeployAtEnd"); + } + + @Test + public void testPartialGoalInputFiltersToMatchingGoals() { + String path = "project/workspaces"; + + IdeTestContext context = newContext(PROJECT_MVN, path, false); + CliArguments args = CliArguments.of(1, "mvn", "cl"); + + List lines = context.complete(args, false).stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).containsExactly("clean"); + } + + @Test + public void testUnmatchedGoalInputReturnsNoSuggestions() { + String path = "project/workspaces"; + + IdeTestContext context = newContext(PROJECT_MVN, path, false); + CliArguments args = CliArguments.of(1, "mvn", "xyz"); + + assertThat(context.complete(args, false)).isEmpty(); + } + + @Test + public void testSecondGoalIsCompletedAfterFirstGoal() { + String path = "project/workspaces"; + + IdeTestContext context = newContext(PROJECT_MVN, path, false); + CliArguments args = CliArguments.of(2, "mvn", "clean", ""); + + List lines = context.complete(args, false).stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).contains("package", "install", "deploy", "test"); + } + + @Test + public void testSecondGoalPartialInputFiltersToMatches() { + String path = "project/workspaces"; + + IdeTestContext context = newContext(PROJECT_MVN, path, false); + CliArguments args = CliArguments.of(2, "mvn", "clean", "pa"); + + List lines = context.complete(args, false).stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).containsExactly("package"); + } + + @Test + public void testAlsoMakeFlagIsOfferedOnPartialInput() { + String path = "project/workspaces"; + + IdeTestContext context = newContext(PROJECT_MVN, path, false); + CliArguments args = CliArguments.of(1, "mvn", "--also"); + + List lines = context.complete(args, false).stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).containsExactly("--also-make", "--also-make-dependents"); + } + + @Test + public void testShortAlsoMakeFlagIsOfferedOnPartialInput() { + String path = "project/workspaces"; + + IdeTestContext context = newContext(PROJECT_MVN, path, false); + CliArguments args = CliArguments.of(1, "mvn", "-am"); + + List lines = context.complete(args, false).stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).containsExactly("-am", "-amd"); + } + + @Test + public void testExecMainClassFlagCandidateIsMarkedIncomplete() { + String path = "project/workspaces"; + + IdeTestContext context = newContext(PROJECT_MVN, path, false); + CliArguments args = CliArguments.of(1, "mvn", "-Dexec.mainClass="); + + Optional candidate = context.complete(args, false).stream() + .filter(c -> c.text().equals("-Dexec.mainClass=")) + .findFirst(); + + assertThat(candidate).isPresent(); + assertThat(candidate.get().complete()).isFalse(); + } + + @Test + public void testAlsoMakeFlagIsNotOfferedWhenAlreadyUsed() { + String path = "project/workspaces"; + + IdeTestContext context = newContext(PROJECT_MVN, path, false); + CliArguments args = CliArguments.of(2, "mvn", "--also-make", "--al"); + + List lines = context.complete(args, false).stream() + .map(CompletionCandidate::text).toList(); + assertThat(lines).doesNotContain("--also-make"); + } } From dac795f2b7656b296ff84299ddfeb4b8906532d1 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 10:55:00 +0200 Subject: [PATCH 04/49] Extract completion candidate collection into own method --- .../ide/commandlet/CommandletManager.java | 3 +++ .../ide/commandlet/CommandletManagerImpl.java | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManager.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManager.java index 92105a33ea..813efdd59f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManager.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManager.java @@ -95,6 +95,9 @@ default LocalToolCommandlet getRequiredLocalToolCommandlet(String name) { throw new IllegalArgumentException("The commandlet " + name + " is not a LocalToolCommandlet!"); } + public abstract void collectCompletionCandidates(CliArguments arguments, + CompletionCandidateCollector collector); + /** * @param arguments the {@link CliArguments}. * @param collector the optional {@link CompletionCandidateCollector}. Will be {@code null} if no argument {@link CliArguments#isCompletion() completion} diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index 9d4217fb8b..e4dfc63acb 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -226,6 +226,28 @@ public Commandlet getCommandletByFirstKeyword(String keyword) { return this.firstKeywordMap.get(keyword); } + @Override + public void collectCompletionCandidates(CliArguments arguments, + CompletionCandidateCollector collector) { + CliArgument current = arguments.current(); + if (current.isStart()) { + current = current.getNext(); + } + if (current.isEnd()) { + return; + } + + for (Commandlet cmd : this.getCommandlets()) { + if (!cmd.isIdeHomeRequired() || this.context.getIdeHome() != null) { + for (Property property : cmd.getProperties()) { + if (property instanceof KeywordProperty keyword) { + keyword.apply(arguments, this.context, cmd, collector); + } + } + } + } + } + @Override public Iterator findCommandlet(CliArguments arguments, CompletionCandidateCollector collector) { From f63af46131bd1db0e260574aacfc928da50564d2 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 11:01:19 +0200 Subject: [PATCH 05/49] Change CompletionCandidate test to entries list --- .../tools/ide/completion/CompletionCandidate.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidate.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidate.java index c62bd07d84..c58fe20908 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidate.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidate.java @@ -1,16 +1,24 @@ package com.devonfw.tools.ide.completion; +import java.util.List; + /** * Candidate for auto-completion. * * @param text the text to suggest (CLI argument value). * @param description the description of the candidate. */ -public record CompletionCandidate(String text, String description) implements Comparable { +public record CompletionCandidate(List entries, + String description, + boolean complete) implements Comparable { @Override public int compareTo(CompletionCandidate o) { - return this.text.compareTo(o.text); + return this.text().compareTo(o.text()); + } + + public String text() { + return String.join(" ", this.entries); } } From 682226cbe3ccc48143063a6ad6cc9b3f75afa1fc Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 11:04:17 +0200 Subject: [PATCH 06/49] Adjust constructor call --- .../tools/ide/completion/CompletionCandidateCollector.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java index 2a9e3c7161..258b87b6d2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java @@ -27,12 +27,14 @@ public interface CompletionCandidateCollector { * @param commandlet the {@link Commandlet} owning the {@link Property}. * @return the {@link CompletionCandidate} for the given parameters. */ - default CompletionCandidate createCandidate(String text, String description, Property property, Commandlet commandlet) { + default CompletionCandidate createCandidate(String text, String description, + Property property, Commandlet commandlet, boolean complete) { if (description == null) { // compute description from property + commandlet like in HelpCommandlet? } - CompletionCandidate candidate = new CompletionCandidate(text, description); + CompletionCandidate candidate = new CompletionCandidate(Arrays.asList(text.split(" ")), + description, complete); return candidate; } From bf984c1197acb9d54e5e450f21c592fcaa261fe6 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 11:05:51 +0200 Subject: [PATCH 07/49] Pass completion status to candidate --- .../ide/completion/CompletionCandidateCollectorDefault.java | 3 ++- .../java/com/devonfw/tools/ide/completion/IdeCompleter.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java index 33f6fc25fb..91da2eb82d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java @@ -48,7 +48,8 @@ public void add(String text, String description, Property property, Commandle } } - CompletionCandidate candidate = createCandidate(text, description, property, commandlet); + CompletionCandidate candidate = this.createCandidate(text, description, property, commandlet, + !text.endsWith("=")); this.candidates.add(candidate); LOG.trace("Added {} for auto-completion of property {}.{}", candidate, commandlet, property); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/IdeCompleter.java b/cli/src/main/java/com/devonfw/tools/ide/completion/IdeCompleter.java index 3051cf309c..7e211283ac 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/IdeCompleter.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/IdeCompleter.java @@ -35,7 +35,8 @@ public void complete(LineReader reader, ParsedLine commandLine, List List completion = this.context.complete(args, true); int i = 0; for (CompletionCandidate candidate : completion) { - candidates.add(new Candidate(candidate.text(), candidate.text(), null, null, null, null, true, i++)); + candidates.add(new Candidate(candidate.text(), candidate.text(), null, null, null, null, + candidate.complete(), i++)); } } From 0830786562713057f5e3822606c47a436b02c9ee Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 11:07:15 +0200 Subject: [PATCH 08/49] Fix commandlet-specific flag completion --- .../tools/ide/context/AbstractIdeContext.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index f810c7d4d1..037d7caa5b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -1442,7 +1442,10 @@ public List complete(CliArguments arguments, boolean includ property.apply(arguments, this, cc, collector); } } - Iterator commandletIterator = this.commandletManager.findCommandlet(arguments, collector); + + this.commandletManager.collectCompletionCandidates(arguments, collector); + + Iterator commandletIterator = this.commandletManager.findCommandlet(arguments, null); CliArgument current = arguments.current(); if (current.isCompletion() && current.isCombinedShortOption()) { collector.add(current.get(), null, null, null); @@ -1461,6 +1464,7 @@ private void completeCommandlet(CliArguments arguments, Commandlet cmd, Completi LOG.trace("Trying to match arguments for auto-completion for commandlet {}", cmd.getName()); Iterator> valueIterator = cmd.getValues().iterator(); + Property lastValueProperty = null; valueIterator.next(); // skip first property since this is the keyword property that already matched to find the commandlet List> properties = cmd.getProperties(); // we are creating our own list of options and remove them when matched to avoid duplicate suggestions @@ -1499,18 +1503,30 @@ private void completeCommandlet(CliArguments arguments, Commandlet cmd, Completi } } } else { + if (currentArgument.isCompletion() && currentArgument.get().isEmpty() + && !arguments.isEndOptions()) { + for (Property option : optionProperties) { + option.apply(arguments, this, cmd, collector); + } + } + + Property valueProperty = null; if (valueIterator.hasNext()) { - Property valueProperty = valueIterator.next(); + valueProperty = valueIterator.next(); boolean success = valueProperty.apply(arguments, this, cmd, collector); if (!success) { LOG.trace("Completion cannot match any further."); return; } + } else if (lastValueProperty != null && lastValueProperty.isMultiValued()) { + valueProperty = lastValueProperty; } else { LOG.trace("No value left for completion."); return; } } + + arguments.next(); currentArgument = arguments.current(); } } From 6e66a969eff1d5c3c1c97057c98286a82bf80d58 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 11:09:04 +0200 Subject: [PATCH 09/49] Make completeValue() abstract --- .../com/devonfw/tools/ide/property/BooleanProperty.java | 4 ++++ .../com/devonfw/tools/ide/property/EditionProperty.java | 6 ++++++ .../java/com/devonfw/tools/ide/property/NumberProperty.java | 5 +++++ .../main/java/com/devonfw/tools/ide/property/Property.java | 4 +--- .../java/com/devonfw/tools/ide/property/StringProperty.java | 6 ++++++ 5 files changed, 22 insertions(+), 3 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/BooleanProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/BooleanProperty.java index 680b62b1f3..51069a604d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/BooleanProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/BooleanProperty.java @@ -54,6 +54,10 @@ public Boolean parse(String valueAsString, IdeContext context) { return result; } + @Override + protected void completeValue(String arg, IdeContext contextual, Commandlet commandlet, + CompletionCandidateCollector collector) {} + private Boolean parse(String valueAsString) { if (valueAsString == null) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java index 7cc2d782b2..564d034232 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java @@ -1,5 +1,7 @@ package com.devonfw.tools.ide.property; +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.validation.PropertyValidator; @@ -41,4 +43,8 @@ public String parse(String valueAsString, IdeContext context) { return valueAsString; } + + @Override + protected void completeValue(String arg, IdeContext contextual, Commandlet commandlet, + CompletionCandidateCollector collector) {} } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/NumberProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/NumberProperty.java index f13c59bb32..e6a433ef2b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/NumberProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/NumberProperty.java @@ -1,5 +1,7 @@ package com.devonfw.tools.ide.property; +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.validation.PropertyValidator; @@ -49,4 +51,7 @@ public Long parse(String valueAsString, IdeContext context) { } } + @Override + protected void completeValue(String arg, IdeContext contextual, Commandlet commandlet, + CompletionCandidateCollector collector) {} } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java index dbda7b1271..d27eaf2b4d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java @@ -472,9 +472,7 @@ protected void complete(String normalizedName, CliArgument argument, CliArgument * @param commandlet the {@link Commandlet} owning this {@link Property}. * @param collector the {@link CompletionCandidateCollector}. */ - protected void completeValue(String arg, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { - - } + protected abstract void completeValue(String arg, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector); /** * @param nameOrAlias the potential {@link #getName() name} or {@link #getAlias() alias} to match. diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/StringProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/StringProperty.java index b9ff9c4796..b5f0ecdb79 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/StringProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/StringProperty.java @@ -1,5 +1,7 @@ package com.devonfw.tools.ide.property; +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.validation.PropertyValidator; @@ -59,6 +61,10 @@ public String parse(String valueAsString, IdeContext context) { return valueAsString; } + @Override + protected void completeValue(String arg, IdeContext contextual, Commandlet commandlet, + CompletionCandidateCollector collector) {} + /** * @return the {@link #getValue() value} as null-safe {@link String} array. */ From 7d9135a1ae8347697a87ea1d943c61e1d354db48 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 11:10:49 +0200 Subject: [PATCH 10/49] Add specific multi-value maven goal property for autocompletion --- .../tools/ide/property/MvnArgProperty.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java new file mode 100644 index 0000000000..1a44dac8f3 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java @@ -0,0 +1,50 @@ +package com.devonfw.tools.ide.property; + +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.completion.CompletionCandidateCollector; +import com.devonfw.tools.ide.context.IdeContext; +import java.util.List; + +public class MvnArgProperty extends StringProperty { + private static final List MAVEN_GOALS = List.of( + "clean", + "compile", + "dependency:list", + "dependency:tree", + "deploy", + "exec:java", + "generate-resources", + "generate-sources", + "help:effective-settings", + "install", + "integration-test", + "package", + "post-clean", + "post-integration-test", + "prepare-package", + "pre-clean", + "pre-integration-test", + "process-resources", + "process-sources", + "site", + "site-deploy", + "test", + "test-compile", + "validate", + "verify" + ); + + public MvnArgProperty(String name, String alias) { + super(name, true, alias); + } + + @Override + protected void completeValue(String arg, IdeContext context, Commandlet commandlet, + CompletionCandidateCollector collector) { + for (String goal : MAVEN_GOALS) { + if (goal.startsWith(arg)) { + collector.add(goal, null, this, commandlet); + } + } + } +} From 6408e057d1576902d0f9358849b3416c348cb47c Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 11:11:17 +0200 Subject: [PATCH 11/49] Adjust createCandidate() call --- .../java/com/devonfw/tools/ide/property/VersionProperty.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java index 759512adc9..b70c85418a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java @@ -88,7 +88,7 @@ private void completeVersion(VersionIdentifier version2complete, ToolCommandlet List candidates = collector.getCandidates(); Collections.reverse(candidates); CompletionCandidate latest = collector.createCandidate(text + VersionSegment.PATTERN_MATCH_ANY_STABLE_VERSION, - "Latest stable matching version", this, commandlet); + "Latest stable matching version", this, commandlet, true); if (candidates.isEmpty()) { candidates.add(latest); } else { From d827eccd710bae1ad109bf75d0ac947ce1d46441 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 11:13:02 +0200 Subject: [PATCH 12/49] Add properties to Mvn commandlet --- .../com/devonfw/tools/ide/tool/mvn/Mvn.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java index cf26227fa4..a2c3b967b5 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java @@ -20,6 +20,8 @@ import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.process.ProcessResult; +import com.devonfw.tools.ide.property.FlagProperty; +import com.devonfw.tools.ide.property.MvnArgProperty; import com.devonfw.tools.ide.step.Step; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; @@ -62,6 +64,20 @@ public class Mvn extends LocalToolCommandlet { private static final VariableSyntax VARIABLE_SYNTAX = VariableSyntax.SQUARE; + private final FlagProperty alsoMake; + private final FlagProperty alsoMakeDependents; + private final FlagProperty failAtEnd; + private final FlagProperty failFast; + private final FlagProperty threads; + + private final FlagProperty skipTests; + private final FlagProperty deployAtEnd; + + private final FlagProperty execMainClass; + private final FlagProperty execArgs; + + private final MvnArgProperty goals; + /** * The constructor. * @@ -70,6 +86,24 @@ public class Mvn extends LocalToolCommandlet { public Mvn(IdeContext context) { super(context, "mvn", Set.of(Tag.JAVA, Tag.BUILD)); + + this.alsoMake = this.add(new FlagProperty("--also-make", false, "-am")); + this.alsoMakeDependents = this.add(new FlagProperty("--also-make-dependents", false, "-amd")); + this.failAtEnd = this.add(new FlagProperty("--fail-at-end", false, "-fae")); + this.failFast = this.add(new FlagProperty("--fail-fast", false, "-ff")); + this.threads = this.add(new FlagProperty("--threads", false, "-t")); + + this.skipTests = this.add(new FlagProperty("--define skipTests", false, "-DskipTests")); + this.deployAtEnd = this.add(new FlagProperty("--define deployAtEnd", false, "-DdeployAtEnd")); + + this.execMainClass = this.add( + new FlagProperty("--define exec.mainClass=", false, "-Dexec.mainClass=") + ); + this.execArgs = this.add( + new FlagProperty("--define exec.args=", false, "-Dexec.args=") + ); + + this.goals = this.add(new MvnArgProperty("goals", "")); } @Override From 365c64850bb64bd2bad46f8858e7ef5f62b393c2 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Mon, 27 Apr 2026 11:13:37 +0200 Subject: [PATCH 13/49] Comment out potentially wrong check --- .../java/com/devonfw/tools/ide/commandlet/Commandlet.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java index 76dd91ebe8..135eef2f09 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java @@ -121,9 +121,9 @@ protected void addKeyword(String keyword, String alias) { */ protected

> P add(P property) { - if (this.multiValued != null) { - throw new IllegalStateException("The multi-valued property " + this.multiValued + " can not be followed by " + property); - } + // if (this.multiValued != null) { + // throw new IllegalStateException("The multi-valued property " + this.multiValued + " can not be followed by " + property); + // } this.propertiesList.add(property); if (property.isOption()) { add(property.getName(), property, false); From 5ca59273cac2432c5e1b537e0fbfc255a7ced4de Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Wed, 13 May 2026 15:01:00 +0200 Subject: [PATCH 14/49] Unapply quickfix --- .../java/com/devonfw/tools/ide/commandlet/Commandlet.java | 6 +++--- .../java/com/devonfw/tools/ide/property/MvnArgProperty.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java index 135eef2f09..76dd91ebe8 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java @@ -121,9 +121,9 @@ protected void addKeyword(String keyword, String alias) { */ protected

> P add(P property) { - // if (this.multiValued != null) { - // throw new IllegalStateException("The multi-valued property " + this.multiValued + " can not be followed by " + property); - // } + if (this.multiValued != null) { + throw new IllegalStateException("The multi-valued property " + this.multiValued + " can not be followed by " + property); + } this.propertiesList.add(property); if (property.isOption()) { add(property.getName(), property, false); diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java index 1a44dac8f3..5d748539ab 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java @@ -35,7 +35,7 @@ public class MvnArgProperty extends StringProperty { ); public MvnArgProperty(String name, String alias) { - super(name, true, alias); + super(name, true, true, alias); } @Override From 18d84a7983e4edf2297ee2a18cc2da40f8ccee2c Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Wed, 13 May 2026 15:01:00 +0200 Subject: [PATCH 15/49] Try removing initProperties to avoid assert error --- .../java/com/devonfw/tools/ide/context/AbstractIdeContext.java | 1 - cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java | 1 - 2 files changed, 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index 037d7caa5b..c793dbf8cd 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -1465,7 +1465,6 @@ private void completeCommandlet(CliArguments arguments, Commandlet cmd, Completi LOG.trace("Trying to match arguments for auto-completion for commandlet {}", cmd.getName()); Iterator> valueIterator = cmd.getValues().iterator(); Property lastValueProperty = null; - valueIterator.next(); // skip first property since this is the keyword property that already matched to find the commandlet List> properties = cmd.getProperties(); // we are creating our own list of options and remove them when matched to avoid duplicate suggestions List> optionProperties = new ArrayList<>(properties.size()); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java index 2097c3453a..a5cd9c5fc6 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java @@ -73,7 +73,6 @@ public ToolCommandlet(IdeContext context, String tool, Set tags) { this.tags = tags; addKeyword(tool); this.arguments = new StringProperty("", false, true, "args"); - initProperties(); } /** From 31c5fc3427f245b62262ecc7f5fa0a1c952a1816 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Wed, 13 May 2026 15:01:00 +0200 Subject: [PATCH 16/49] Add abstract add() method for CompletionCandidate --- .../CompletionCandidateCollector.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java index 258b87b6d2..b0b5e09fe5 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java @@ -12,13 +12,20 @@ */ public interface CompletionCandidateCollector { + /** + * Directly add a {@link CompletionCandidate}. + * + * @param completion the {@link CompletionCandidate} to add + */ + void add(CompletionCandidate completion); + /** * @param text the suggested word to add to auto-completion. * @param description the description of the suggestion candidate or {@code null} to determine automatically form the given parameters. * @param property the {@link Property} that triggered this suggestion. * @param commandlet the {@link Commandlet} owning the {@link Property}. */ - void add(String text, String description, Property property, Commandlet commandlet); + void add(String text, String description); /** * @param text the suggested word to add to auto-completion. @@ -27,12 +34,11 @@ public interface CompletionCandidateCollector { * @param commandlet the {@link Commandlet} owning the {@link Property}. * @return the {@link CompletionCandidate} for the given parameters. */ - default CompletionCandidate createCandidate(String text, String description, - Property property, Commandlet commandlet, boolean complete) { - + default CompletionCandidate createCandidate(String text, String description, boolean complete) { if (description == null) { // compute description from property + commandlet like in HelpCommandlet? } + CompletionCandidate candidate = new CompletionCandidate(Arrays.asList(text.split(" ")), description, complete); return candidate; @@ -49,7 +55,7 @@ default int addAllMatches(String text, String[] sortedCandidates, Property pr if (text.isEmpty()) { for (String candidate : sortedCandidates) { - add(candidate, "", property, commandlet); + add(candidate, ""); } return sortedCandidates.length; } @@ -58,7 +64,7 @@ default int addAllMatches(String text, String[] sortedCandidates, Property pr .collect(Collectors.toList()); for (String match : prefixWords) { - add(match, "", property, commandlet); + add(match, ""); } return prefixWords.size(); From d08b45d4d314bad19854d10917f596a182603bdf Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Wed, 13 May 2026 15:01:00 +0200 Subject: [PATCH 17/49] Implement add() method --- .../CompletionCandidateCollectorAdapter.java | 11 +++++----- .../CompletionCandidateCollectorDefault.java | 22 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorAdapter.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorAdapter.java index bf14e526bb..3f6fe25eaf 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorAdapter.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorAdapter.java @@ -2,9 +2,6 @@ import java.util.List; -import com.devonfw.tools.ide.commandlet.Commandlet; -import com.devonfw.tools.ide.property.Property; - /** * Implementation of {@link CompletionCandidateCollector} that wraps an existing {@link CompletionCandidateCollector} adding a prefix. */ @@ -28,9 +25,13 @@ public CompletionCandidateCollectorAdapter(String prefix, CompletionCandidateCol } @Override - public void add(String text, String description, Property property, Commandlet commandlet) { + public void add(CompletionCandidate completion) { + this.delegate.add(this.prefix + completion.text(), completion.description()); + } - this.delegate.add(this.prefix + text, description, property, commandlet); + @Override + public void add(String text, String description) { + this.delegate.add(this.prefix + text, description); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java index 91da2eb82d..ff25767fd8 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java @@ -38,20 +38,23 @@ public CompletionCandidateCollectorDefault(IdeContext context) { } @Override - public void add(String text, String description, Property property, Commandlet commandlet) { - - // Check if this candidate already exists to avoid duplicates + public void add(CompletionCandidate completion) { for (CompletionCandidate existing : this.candidates) { - if (existing.text().equals(text)) { - // Duplicate candidate found, do not add + if (existing.text().equals(completion.text())) { return; } } - CompletionCandidate candidate = this.createCandidate(text, description, property, commandlet, - !text.endsWith("=")); - this.candidates.add(candidate); - LOG.trace("Added {} for auto-completion of property {}.{}", candidate, commandlet, property); + this.candidates.add(completion); + } + + @Override + public void add(String text, String description) { + + CompletionCandidate candidate = this.createCandidate(text, description, !text.endsWith("=")); + this.add(candidate); + + LOG.trace("Added {} for auto-completion.", candidate); } @Override @@ -80,5 +83,4 @@ public String toString() { return this.candidates.toString(); } - } From 001b5eee088b9d6b87d1e6253eb86b26b7bbc161 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Wed, 13 May 2026 15:01:00 +0200 Subject: [PATCH 18/49] Remove superfluous lines --- .../com/devonfw/tools/ide/context/AbstractIdeContext.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index c793dbf8cd..33b9dab9b0 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -1438,7 +1438,7 @@ public List complete(CliArguments arguments, boolean includ if (includeContextOptions) { ContextCommandlet cc = new ContextCommandlet(); for (Property property : cc.getProperties()) { - assert (property.isOption()); + // assert (property.isOption()); property.apply(arguments, this, cc, collector); } } @@ -1446,11 +1446,7 @@ public List complete(CliArguments arguments, boolean includ this.commandletManager.collectCompletionCandidates(arguments, collector); Iterator commandletIterator = this.commandletManager.findCommandlet(arguments, null); - CliArgument current = arguments.current(); - if (current.isCompletion() && current.isCombinedShortOption()) { - collector.add(current.get(), null, null, null); - } - arguments.next(); + while (commandletIterator.hasNext()) { Commandlet cmd = commandletIterator.next(); if (!arguments.current().isEnd()) { From b0197e8d64f7cb2af609a7c17147f65a620b41c2 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Wed, 13 May 2026 15:01:00 +0200 Subject: [PATCH 19/49] Remove superfluous arguments from add() calls --- .../com/devonfw/tools/ide/property/CommandletProperty.java | 2 +- .../java/com/devonfw/tools/ide/property/EnumProperty.java | 2 +- .../java/com/devonfw/tools/ide/property/MvnArgProperty.java | 2 +- .../java/com/devonfw/tools/ide/property/PathProperty.java | 2 +- .../java/com/devonfw/tools/ide/property/PluginProperty.java | 2 +- .../java/com/devonfw/tools/ide/property/ToolProperty.java | 2 +- .../com/devonfw/tools/ide/property/VersionProperty.java | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java index 76b42d0aa4..6a59035c43 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java @@ -53,7 +53,7 @@ protected void completeValue(String arg, IdeContext context, Commandlet commandl for (Commandlet cmd : context.getCommandletManager().getCommandlets()) { String cmdName = cmd.getName(); if (cmdName.startsWith(arg)) { - collector.add(cmdName, null, null, cmd); + collector.add(cmdName, null); } } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/EnumProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/EnumProperty.java index fd3d0ec276..2adcedcd89 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/EnumProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/EnumProperty.java @@ -54,7 +54,7 @@ protected void completeValue(String arg, IdeContext context, Commandlet commandl for (V enumConstant : this.valueType.getEnumConstants()) { String name = enumConstant.name().toLowerCase(Locale.ROOT); if (name.startsWith(arg)) { - collector.add(name, null, this, commandlet); + collector.add(name, null); } } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java index 5d748539ab..c516612f49 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/MvnArgProperty.java @@ -43,7 +43,7 @@ protected void completeValue(String arg, IdeContext context, Commandlet commandl CompletionCandidateCollector collector) { for (String goal : MAVEN_GOALS) { if (goal.startsWith(arg)) { - collector.add(goal, null, this, commandlet); + collector.add(goal, null); } } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java index 1fa1ffcc61..7825637341 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java @@ -129,7 +129,7 @@ protected void completeValuesFromFolder(Path folder, String filename, IdeContext if (Files.isDirectory(folder)) { try (Stream children = Files.list(folder)) { children.filter(child -> isValidPath(child, filename)) - .forEach(child -> collector.add(getPathForCompletion(child, context, commandlet), null, this, commandlet)); + .forEach(child -> collector.add(getPathForCompletion(child, context, commandlet), null)); } catch (IOException e) { throw new IllegalStateException(e); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/PluginProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/PluginProperty.java index 23dd53023f..7770393f59 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/PluginProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/PluginProperty.java @@ -60,7 +60,7 @@ protected void completeValue(String arg, IdeContext context, Commandlet commandl ToolPlugins plugins = pbc.getPlugins(); for (ToolPluginDescriptor pluginDescriptor : plugins.getPlugins()) { if (pluginDescriptor.name().toLowerCase().startsWith(arg.toLowerCase())) { - collector.add(pluginDescriptor.name(), null, null, commandlet); + collector.add(pluginDescriptor.name(), null); } } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java index 5cf4f2002f..a31b5e8bd9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java @@ -75,7 +75,7 @@ protected void completeValue(String arg, IdeContext context, Commandlet commandl if (cmd instanceof ToolCommandlet) { String cmdName = cmd.getName(); if (cmdName.startsWith(arg)) { - collector.add(cmdName, null, null, cmd); + collector.add(cmdName, null); } } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java index b70c85418a..ef73e145a1 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java @@ -76,7 +76,7 @@ private void completeVersion(VersionIdentifier version2complete, ToolCommandlet } else { text = version2complete.toString(); if (version2complete.isPattern()) { - collector.add(text, "Given version pattern.", this, commandlet); + collector.add(text, "Given version pattern."); return; } } @@ -88,13 +88,13 @@ private void completeVersion(VersionIdentifier version2complete, ToolCommandlet List candidates = collector.getCandidates(); Collections.reverse(candidates); CompletionCandidate latest = collector.createCandidate(text + VersionSegment.PATTERN_MATCH_ANY_STABLE_VERSION, - "Latest stable matching version", this, commandlet, true); + "Latest stable matching version", true); if (candidates.isEmpty()) { candidates.add(latest); } else { candidates.add(1, latest); } - collector.add(text + VersionSegment.PATTERN_MATCH_ANY_VERSION, "Latest matching version including unstable versions", this, commandlet); + collector.add(text + VersionSegment.PATTERN_MATCH_ANY_VERSION, "Latest matching version including unstable versions"); } } } From be38f459d89aa1b3c99b72e814ababcd9a5d2b31 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Wed, 13 May 2026 15:01:00 +0200 Subject: [PATCH 20/49] Try to fix Property.apply() --- .../devonfw/tools/ide/property/Property.java | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java index d27eaf2b4d..132212e4e8 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java @@ -11,6 +11,7 @@ import com.devonfw.tools.ide.cli.CliArgument; import com.devonfw.tools.ide.cli.CliArguments; import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.completion.CompletionCandidate; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.completion.CompletionCandidateCollectorAdapter; import com.devonfw.tools.ide.context.IdeContext; @@ -342,8 +343,13 @@ protected V getNullValue() { * @return {@code true} if it matches, {@code false} otherwise. */ public boolean apply(CliArguments args, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { + boolean match = this.apply(this.name, args, context, commandlet, collector); - return apply(this.name, args, context, commandlet, collector); + if (args.current().isCompletion() && this.alias != null) { + match |= this.apply(this.alias, args, context, commandlet, collector); + } + + return match; } /** @@ -358,19 +364,25 @@ protected boolean apply(String normalizedName, CliArguments args, IdeContext con CliArgument argument = args.current(); if (argument.isCompletion()) { + if (collector == null) { + return false; + } + int size = collector.getCandidates().size(); - complete(normalizedName, argument, args, context, commandlet, collector); - return (collector.getCandidates().size() > size); + boolean match = this.complete(normalizedName, argument, args, context, commandlet, collector); + + return match; } + boolean option = normalizedName.startsWith("-"); - if (option && !argument.isOption()) { - return false; - } - if (!option && argument.isOption() && (argument.get().length() > 1) && args.isSplitShortOpts()) { + if (option && !argument.isOption() || !option && argument.isOption() + && argument.get().length() > 0 && !argument.isEndOptions()) { return false; } + String argValue = null; boolean lookahead = false; + if (normalizedName.isEmpty()) { argValue = argument.get(); } else { @@ -429,32 +441,44 @@ protected boolean applyValue(String argValue, boolean lookahead, CliArguments ar * @param context the {@link IdeContext}. * @param commandlet the {@link Commandlet} owning this {@link Property}. * @param collector the {@link CompletionCandidateCollector}. + * @return {@code true} if completion succeeded, {@code false} otherwise */ - protected void complete(String normalizedName, CliArgument argument, CliArguments args, IdeContext context, Commandlet commandlet, - CompletionCandidateCollector collector) { - + protected boolean complete( + String normalizedName, CliArgument argument, CliArguments args, IdeContext context, + Commandlet commandlet, CompletionCandidateCollector collector + ) { String arg = argument.get(); + if (normalizedName.isEmpty()) { int count = collector.getCandidates().size(); completeValue(arg, context, commandlet, collector); + if (collector.getCandidates().size() > count) { - args.next(); + return true; } - return; + + return false; } + if (normalizedName.startsWith(arg)) { - collector.add(normalizedName, null, this, commandlet); + boolean complete = !normalizedName.endsWith("="); + CompletionCandidate candidate = collector.createCandidate(normalizedName, null, complete); + + collector.add(candidate); + return true; } + if (this.alias != null) { if (this.alias.startsWith(arg)) { - collector.add(this.alias, null, this, commandlet); + collector.add(this.alias, null); } else if ((this.alias.length() == 2) && (this.alias.charAt(0) == '-') && argument.isShortOption()) { char opt = this.alias.charAt(1); // e.g. arg="-do" and alias="-f" -complete-> "-dof" if (arg.indexOf(opt) < 0) { - collector.add(arg + opt, null, this, commandlet); + collector.add(arg + opt, null); } } } + String value = argument.getValue(); if (value != null) { String key = argument.getKey(); @@ -462,6 +486,8 @@ protected void complete(String normalizedName, CliArgument argument, CliArgument completeValue(value, context, commandlet, new CompletionCandidateCollectorAdapter(key + "=", collector)); } } + + return false; } /** From ecf7f182276e16334e6195e34ee1f36c6a841860 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Wed, 13 May 2026 15:01:00 +0200 Subject: [PATCH 21/49] Remove superfluous lines --- .../devonfw/tools/ide/property/Property.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java index 132212e4e8..5b8b88bdc8 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java @@ -468,25 +468,6 @@ protected boolean complete( return true; } - if (this.alias != null) { - if (this.alias.startsWith(arg)) { - collector.add(this.alias, null); - } else if ((this.alias.length() == 2) && (this.alias.charAt(0) == '-') && argument.isShortOption()) { - char opt = this.alias.charAt(1); // e.g. arg="-do" and alias="-f" -complete-> "-dof" - if (arg.indexOf(opt) < 0) { - collector.add(arg + opt, null); - } - } - } - - String value = argument.getValue(); - if (value != null) { - String key = argument.getKey(); - if (normalizedName.equals(key) || Objects.equals(this.alias, key)) { - completeValue(value, context, commandlet, new CompletionCandidateCollectorAdapter(key + "=", collector)); - } - } - return false; } From d011275cf147e85f8f56e546843ed5791eb6e70a Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 28 Apr 2026 08:33:35 +0200 Subject: [PATCH 22/49] Fix bug to allow Mvn their own properties From d4ae88b870357673f198783a2b3b367ac50b42bf Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Wed, 13 May 2026 15:01:00 +0200 Subject: [PATCH 23/49] Avoid double registering of completions --- .../completion/CompletionCandidateCollectorDefault.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java index ff25767fd8..9a9cc85140 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java @@ -50,9 +50,14 @@ public void add(CompletionCandidate completion) { @Override public void add(String text, String description) { + for (CompletionCandidate existing : this.candidates) { + if (existing.text().equals(text)) { + return; + } + } CompletionCandidate candidate = this.createCandidate(text, description, !text.endsWith("=")); - this.add(candidate); + this.candidates.add(candidate); LOG.trace("Added {} for auto-completion.", candidate); } From f16bb6ba72599edc15e366315b9ab6e5405ae326 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Wed, 13 May 2026 15:48:58 +0200 Subject: [PATCH 24/49] Allow completion during testing --- .../com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java | 2 +- .../java/com/devonfw/tools/ide/context/AbstractIdeContext.java | 1 + cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index e4dfc63acb..d433b57ba7 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -238,7 +238,7 @@ public void collectCompletionCandidates(CliArguments arguments, } for (Commandlet cmd : this.getCommandlets()) { - if (!cmd.isIdeHomeRequired() || this.context.getIdeHome() != null) { + if (this.context.isTest() || !cmd.isIdeHomeRequired() || this.context.getIdeHome() != null) { for (Property property : cmd.getProperties()) { if (property instanceof KeywordProperty keyword) { keyword.apply(arguments, this.context, cmd, collector); diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index 33b9dab9b0..c33499a07f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -409,6 +409,7 @@ private String getMessageIdeRootNotFound() { /** * @return {@code true} if this is a test context for JUnits, {@code false} otherwise. */ + @Override public boolean isTest() { return false; diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java index 5926b9c7cf..7fb4afae80 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java @@ -372,6 +372,8 @@ default void requireOnline(String purpose, boolean explicitOnlineCheck) { */ VersionIdentifier getProjectVersion(); + boolean isTest(); + /** * @param version the new value of {@link #getProjectVersion()}. */ From dae126e297c791e8e8de8f8b7b477bef409fc594 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Wed, 13 May 2026 16:13:14 +0200 Subject: [PATCH 25/49] Opt tests out of internal root dir logic --- .../main/java/com/devonfw/tools/ide/commandlet/Commandlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java index 76dd91ebe8..ea5c552a65 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java @@ -191,7 +191,7 @@ public boolean isIdeHomeRequired() { */ public boolean isIdeRootRequired() { - return true; + return !this.context.isTest(); } /** From 72d8f5caaaf91c36426319d3c26531b3eea35f4b Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 08:45:57 +0200 Subject: [PATCH 26/49] Add methods to set the first keyword --- .../com/devonfw/tools/ide/commandlet/Commandlet.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java index ea5c552a65..cd7bad53fe 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java @@ -98,6 +98,17 @@ protected void addKeyword(String keyword) { addKeyword(keyword, null); } + protected void setFirstKeyword(String keyword, String alias) { + KeywordProperty property = new KeywordProperty(keyword, true, alias); + + this.firstKeyword = property; + this.add(property); + } + + protected void setFirstKeyword(String keyword) { + this.setFirstKeyword(keyword, null); + } + /** * @param keyword the {@link KeywordProperty keyword} to {@link #add(Property) add}. * @param alias the optional {@link KeywordProperty#getAlias() alias}. From 10334bf5d5238ba9601a577bf0f38e0a9e55a2a3 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 08:46:50 +0200 Subject: [PATCH 27/49] Prevent top-level completion of secondary commands --- .../tools/ide/commandlet/CommandletManagerImpl.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index d433b57ba7..d43e058506 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -164,10 +164,7 @@ protected void add(Commandlet commandlet) { if (keyword != null) { String name = keyword.getName(); registerKeyword(name, commandlet); - String optionName = keyword.getOptionName(); - if (!optionName.equals(name)) { - registerKeyword(optionName, commandlet); - } + String alias = keyword.getAlias(); if (alias != null) { registerKeyword(alias, commandlet); @@ -239,10 +236,9 @@ public void collectCompletionCandidates(CliArguments arguments, for (Commandlet cmd : this.getCommandlets()) { if (this.context.isTest() || !cmd.isIdeHomeRequired() || this.context.getIdeHome() != null) { - for (Property property : cmd.getProperties()) { - if (property instanceof KeywordProperty keyword) { - keyword.apply(arguments, this.context, cmd, collector); - } + KeywordProperty firstKeyword = cmd.getFirstKeyword(); + if (firstKeyword != null) { + firstKeyword.apply(arguments, this.context, cmd, collector); } } } From 5f91447b3a5323e1e57cf02db738dc1904c66927 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 08:47:31 +0200 Subject: [PATCH 28/49] Dynamically add 'exit' as a shell keyword --- .../com/devonfw/tools/ide/commandlet/ShellCommandlet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java index 9667c9e269..0db02b131d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java @@ -68,13 +68,13 @@ public boolean isIdeHomeRequired() { @Override protected void doRun() { + this.setFirstKeyword("exit"); try { Parser parser = new DefaultParser(); try (Terminal terminal = TerminalBuilder.builder().build()) { // initialize our own completer here and add exit as an autocompletion option - Completer completer = new AggregateCompleter( - new StringsCompleter("exit"), new IdeCompleter((AbstractIdeContext) this.context)); + Completer completer = new IdeCompleter((AbstractIdeContext) this.context); LineReader reader = LineReaderBuilder.builder().terminal(terminal).completer(completer).parser(parser) .variable(LineReader.LIST_MAX, AUTOCOMPLETER_MAX_RESULTS).build(); From 22199948e85e3ffb58546e67ca7ab904352996f1 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 08:49:58 +0200 Subject: [PATCH 29/49] Restructure completion logic --- .../tools/ide/context/AbstractIdeContext.java | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index c33499a07f..1fcc57347c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -1481,21 +1481,12 @@ private void completeCommandlet(CliArguments arguments, Commandlet cmd, Completi boolean success = option.apply(arguments, this, cmd, collector); if (success) { optionIterator.remove(); - arguments.next(); } } } else { Property option = cmd.getOption(currentArgument.get()); if (option != null) { - arguments.next(); - boolean removed = optionProperties.remove(option); - if (!removed) { - option = null; - } - } - if (option == null) { - LOG.trace("No such option was found."); - return; + optionProperties.remove(option); } } } else { @@ -1508,17 +1499,14 @@ private void completeCommandlet(CliArguments arguments, Commandlet cmd, Completi Property valueProperty = null; if (valueIterator.hasNext()) { - valueProperty = valueIterator.next(); - boolean success = valueProperty.apply(arguments, this, cmd, collector); - if (!success) { - LOG.trace("Completion cannot match any further."); - return; - } + lastValueProperty = valueIterator.next(); + valueProperty = lastValueProperty; } else if (lastValueProperty != null && lastValueProperty.isMultiValued()) { valueProperty = lastValueProperty; - } else { - LOG.trace("No value left for completion."); - return; + } + + if (valueProperty != null) { + valueProperty.apply(arguments, this, cmd, collector); } } From ed31941f3ceb98eaeeaa9ac8452feb8af9b8cb66 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 08:50:43 +0200 Subject: [PATCH 30/49] Properly complete long and short options --- .../com/devonfw/tools/ide/property/KeywordProperty.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/KeywordProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/KeywordProperty.java index 55ea995b32..be3dbf17f9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/KeywordProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/KeywordProperty.java @@ -70,7 +70,11 @@ public boolean apply(CliArguments args, IdeContext context, Commandlet commandle if (args.current().isOption()) { normalizedName = this.optionName; } - return apply(normalizedName, args, context, commandlet, collector); + boolean match = apply(normalizedName, args, context, commandlet, collector); + if (args.current().isCompletion() && this.alias != null) { + match |= apply(this.alias, args, context, commandlet, collector); + } + return match; } @Override From 5dd62f7bfbae2c0d184031a1b0abb45aea82bf8c Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 08:51:50 +0200 Subject: [PATCH 31/49] Restructure joined short opt completion --- .../devonfw/tools/ide/property/Property.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java index 5b8b88bdc8..929c43b9ed 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java @@ -391,7 +391,6 @@ protected boolean apply(String normalizedName, CliArguments args, IdeContext con } argValue = argument.getValue(); if (argValue == null) { - argument = args.next(); if (argument.isCompletion()) { completeValue(argument.get(), context, commandlet, collector); return true; @@ -419,16 +418,6 @@ protected boolean applyValue(String argValue, boolean lookahead, CliArguments ar CompletionCandidateCollector collector) { boolean success = assignValueAsString(argValue, context, commandlet); - - if (success) { - if (this.multivalued) { - while (success && args.hasNext()) { - CliArgument arg = args.next(); - success = assignValueAsString(arg.get(), context, commandlet); - } - } - } - args.next(); return success; } @@ -468,6 +457,15 @@ protected boolean complete( return true; } + if (this.alias != null) { + if (this.alias.length() == 2 && this.alias.charAt(0) == '-' && argument.isShortOption()) { + char opt = this.alias.charAt(1); + if (arg.indexOf(opt) < 0) { + collector.add(arg + opt, null); + } + } + } + return false; } From 5621a66a0825dfa7cb47371630cff4eb453a2c9a Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 08:52:59 +0200 Subject: [PATCH 32/49] Add German and English descriptions for new maven commands --- cli/src/main/resources/nls/Help.properties | 10 ++++++++++ cli/src/main/resources/nls/Help_de.properties | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties index 48c7c55c0d..58e736d5f7 100644 --- a/cli/src/main/resources/nls/Help.properties +++ b/cli/src/main/resources/nls/Help.properties @@ -142,9 +142,17 @@ cmd.yarn=Tool commandlet for Yarn (NPM alternative, JavaScript package manager). cmd.yarn.detail=Yarn is a package manager and build tool for JavaScript. Detailed documentation can be found at https://yarnpkg.com/ commandlets=Available commandlets: icd-hint=Hint: Use 'icd' command to easily navigate between your IDE home, projects, and workspaces. Type 'icd --help' for more details. +opt.--also-make=Build projects required by the list. +opt.--also-make-dependents=Build projects that depend on projects on the list. opt.--batch=enable batch mode (non-interactive). opt.--code=clone given code repository containing a settings folder into workspaces so that settings can be committed alongside code changes. opt.--debug=enable debug logging. +opt.--define\ deployAtEnd=Deploy all artifacts at the end of the multi-module build. +opt.--define\ exec.args\==Arguments for the exec:java plugin. +opt.--define\ exec.mainClass\==Main class for the exec:java plugin. +opt.--define\ skipTests=Skip test compilation and execution. +opt.--fail-at-end=Only fail the build at the end; allow all non-impacted builds to continue. +opt.--fail-fast=Stop at first failure in reactorized builds. opt.--force=enable force mode. opt.--force-plugins=force plugin (re)installation. opt.--force-pull=force pull of settings even for code repository. @@ -157,6 +165,7 @@ opt.--quiet=disable info logging (only log success, warning or error). opt.--skip-repositories=skip the setup of repositories. opt.--skip-tools=skip the installation/update of tools. opt.--skip-updates=disables tool updates if the configured versions match the installed versions. +opt.--threads=Thread count, for instance 2.0C where C is core multiplied. opt.--trace=enable trace logging. opt.--version=Print the IDE version and exit. options.global=Global options: @@ -167,6 +176,7 @@ val.args=The commandline arguments to pass to the tool. val.cfg=Selection of the configuration file (settings | home | conf | workspace). val.commandlet=The selected commandlet (use 'ide help' to list all commandlets). val.edition=The tool edition. +val.goals=The Maven goals and/or phases to execute. val.plugin=The plugin to select val.settingsRepository=The settings git repository with the IDEasy configuration for the project. val.tool=The tool commandlet to select. diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index f0a7057798..a5c6cc98a1 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -142,9 +142,17 @@ cmd.yarn=Werkzeug Kommando für Yarn (NPM Alternative, JavaScript Package Manage cmd.yarn.detail=Yarn ist ein Package Manager und Build-Werkzeug für JavaScript. Detaillierte Dokumentation ist zu finden unter https://yarnpkg.com/ commandlets=Verfügbare Kommandos: icd-hint=Hinweis: Verwenden Sie den Befehl 'icd' um einfach zwischen Ihrem IDE-Hauptverzeichnis, Projekten und Workspaces zu navigieren. Geben Sie 'icd --help' für weitere Details ein. +opt.--also-make=Baut auch die Projekte, die von den Projekten der Liste benötigt werden. +opt.--also-make-dependents=Baut auch die Projekte, die von den Projekten der Liste abhängen. opt.--batch=Aktiviert den Batch-Modus (nicht-interaktive Stapelverarbeitung). opt.--code=Git-Repository sowohl als Code- als auch als Settings-Repository verwenden. opt.--debug=Aktiviert Debug-Ausgaben (Fehleranalyse). +opt.--define\ deployAtEnd=Deployt alle Artefakte am Ende des Multi-Modul-Builds. +opt.--define\ exec.args\==Argumente für das exec:java-Plugin. +opt.--define\ exec.mainClass\==Hauptklasse für das exec:java-Plugin. +opt.--define\ skipTests=Überspringt die Testkompilierung und -ausführung. +opt.--fail-at-end=Schlägt nur am Ende des Builds fehl; alle anderen Builds werden fortgeführt. +opt.--fail-fast=Stoppt beim ersten Fehler im Multi-Modul-Build. opt.--force=Aktiviert den Force-Modus (Erzwingen). opt.--force-plugins=Erzwingt die (Re)Installation von Plugins. opt.--force-pull=Erzwingt einen Pull der settings auch im Fall eines Code-Repositories. @@ -157,6 +165,7 @@ opt.--quiet=Deaktiviert Info Logging ( nur success, warning und error). opt.--skip-repositories=Überspringt die Einrichtung der Repositories. opt.--skip-tools=Überspringt die Installation/Aktualisierung der Tools. opt.--skip-updates=Deaktiviert Aktualisierungen von Tools wenn die installierten Versionen mit den konfigurierten Versionen übereinstimmen. +opt.--threads=Threadanzahl, z.B. 2.0C wobei C die Anzahl der Kerne angibt. opt.--trace=Aktiviert Trace-Ausgaben (detaillierte Fehleranalyse). opt.--version=Zeigt die IDE Version an und beendet das Programm. options.global=Globale Optionen: @@ -167,6 +176,7 @@ val.args=Die Kommandozeilen-Argumente zur Übergabe an das Werkzeug. val.cfg=Auswahl der Konfigurationsdatei (settings | home | conf | workspace). val.commandlet=Das ausgewählte Commandlet ("ide help" verwenden, um alle Commandlets aufzulisten). val.edition=Die Werkzeug Edition. +val.goals=Die auszuführenden Maven-Ziele und/oder -Phasen. val.plugin=Die zu selektierende Erweiterung. val.settingsRepository=Das settings git Repository mit den IDEasy Einstellungen für das Projekt. val.tool=Das zu selektierende Werkzeug Kommando. From 27e15f43be8c247533717b5289393a95c60b56da Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 08:53:48 +0200 Subject: [PATCH 33/49] Adjust synopsis of mvn command --- .../com/devonfw/tools/ide/commandlet/HelpCommandletTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/HelpCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/HelpCommandletTest.java index 1767d67ea8..ca14ec2ee7 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/HelpCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/HelpCommandletTest.java @@ -95,7 +95,10 @@ void testRunWithCommandlet() { // assert assertLogoMessage(context); assertThat(context).logAtInfo() - .hasEntries("Usage: ide [global-option]* mvn [*]", "Tool commandlet for Maven (Build-Tool).", "usage: mvn [options] [] []"); + .hasEntries( + "Usage: ide [global-option]* mvn [-am | --also-make] [-amd | --also-make-dependents] [-fae | --fail-at-end] [-ff | --fail-fast] [-t | --threads] [-DskipTests | --define skipTests] [-DdeployAtEnd | --define deployAtEnd] [-Dexec.mainClass= | --define exec.mainClass=] [-Dexec.args= | --define exec.args=] goals*", + "Tool commandlet for Maven (Build-Tool).", + "usage: mvn [options] [] []"); assertOptionLogMessages(context); } From d7a52e6589f8f0b8e3be160fa53baa736190e888 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 08:54:27 +0200 Subject: [PATCH 34/49] Fix exit completion test --- .../devonfw/tools/ide/commandlet/ShellCommandletTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/ShellCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/ShellCommandletTest.java index 08dbeb8f1f..ca99c1bf57 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/ShellCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/ShellCommandletTest.java @@ -16,6 +16,9 @@ public void testExitIsOfferedAsCompletion() { IdeTestContext context = new IdeTestContext(); CliArguments args = CliArguments.of(0, ""); + Commandlet shellCommandlet = context.getCommandletManager().getCommandlet("shell"); + shellCommandlet.setFirstKeyword("exit"); + List lines = context.complete(args, false).stream() .map(CompletionCandidate::text).toList(); assertThat(lines).contains("exit"); @@ -26,6 +29,9 @@ public void testExitIsOfferedOnPartialInput() { IdeTestContext context = new IdeTestContext(); CliArguments args = CliArguments.of(0, "ex"); + Commandlet shellCommandlet = context.getCommandletManager().getCommandlet("shell"); + shellCommandlet.setFirstKeyword("exit"); + List lines = context.complete(args, false).stream() .map(CompletionCandidate::text).toList(); assertThat(lines).containsExactly("exit"); From 10599c61a0b102c1aa217b86a64bc3b653f1053f Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 08:55:35 +0200 Subject: [PATCH 35/49] Prevent completion to the identical word --- .../java/com/devonfw/tools/ide/completion/CompleteTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 5f7529c9e9..bf6c5b0325 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 @@ -65,7 +65,6 @@ void testCompleteEmptyNoCtxOptions() { boolean includeContextOptions = false; AbstractIdeContext context = newContext(PROJECT_BASIC, null, false); CliArguments args = CliArguments.ofCompletion(""); - args.next(); List expectedCandidates = getExpectedCandidates(context, true, includeContextOptions, true); // act List candidates = context.complete(args, includeContextOptions); @@ -111,7 +110,7 @@ void testCompleteShortOptsCombinedAllButVersion() { // act List candidates = context.complete(args, true); // assert - assertThat(candidates.stream().map(CompletionCandidate::text)).containsExactly("-fbdopqt", "-fbdopqth", "-fbdopqtv"); + assertThat(candidates.stream().map(CompletionCandidate::text)).containsExactly("-fbdopqth", "-fbdopqtv"); } /** Test of {@link AbstractIdeContext#complete(CliArguments, boolean) auto-completion} for input "help", "". */ From cf75b91d3a56bf784920bf8cdb244d55502e2da2 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 08:58:25 +0200 Subject: [PATCH 36/49] Fix more tests --- .../tools/ide/completion/IdeCompleterTest.java | 13 ++++++++----- .../com/devonfw/tools/ide/tool/mvn/MvnTest.java | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java b/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java index 00e125ba64..fda59a7e0e 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java @@ -102,8 +102,8 @@ void testIdeCompleterNonExistentCommand() { void testIdeCompleterPreventsOptionsAfterCommandWithMinus() { this.reader.setCompleter(newCompleter()); - assertBuffer("get-version --configured ", new TestBuffer("get-version -").tab().tab()); - assertBuffer("get-version - ", new TestBuffer("get-version - ").tab().tab()); + assertBuffer("get-version --configured", new TestBuffer("get-version -").tab().tab()); + assertBuffer("get-version -- android-studio", new TestBuffer("get-version -- ").tab().tab()); } @@ -114,7 +114,10 @@ void testIdeCompleterPreventsOptionsAfterCommandWithMinus() { void testIdeCompleterWithInvalidInputDoesNothing() { this.reader.setCompleter(newCompleter()); - assertBuffer("get-version -t ", new TestBuffer("get-version -t ").tab().tab()); + assertBuffer( + "get-version -t android-studio --configured --installed ", + new TestBuffer("get-version -t android-studio --configured --installed ").tab().tab() + ); assertBuffer("- get-version ", new TestBuffer("- get-version ").tab().tab()); assertBuffer(" - get-version", new TestBuffer(" - get-version").tab().tab()); } @@ -123,10 +126,10 @@ void testIdeCompleterWithInvalidInputDoesNothing() { * Test of 2nd level completion of tool property for {@link com.devonfw.tools.ide.commandlet.VersionGetCommandlet}. */ @Test - void testIdeCompleterHandlesOptionsBeforeCommand() { + void testIdeCompleterAllowsOptionsAfterCommand() { this.reader.setCompleter(newCompleter()); - assertBuffer("get-version mvn ", new TestBuffer("get-version mv").tab().tab()); + assertBuffer("get-version mvn --installed ", new TestBuffer("get-version mvn --in").tab()); } /** diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnTest.java index 735c69d060..28c5f8cffc 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnTest.java @@ -234,7 +234,7 @@ public void testShortAlsoMakeFlagIsOfferedOnPartialInput() { List lines = context.complete(args, false).stream() .map(CompletionCandidate::text).toList(); - assertThat(lines).containsExactly("-am", "-amd"); + assertThat(lines).containsExactly("-am", "-amd", "-amt"); } @Test From 43b8e5c6fcbeb260a577fbdaafbe1a581f56b9fe Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 10:22:07 +0200 Subject: [PATCH 37/49] Add doc comments --- .../com/devonfw/tools/ide/commandlet/Commandlet.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java index cd7bad53fe..33a9e67ae2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java @@ -98,6 +98,12 @@ protected void addKeyword(String keyword) { addKeyword(keyword, null); } + /** + * Create a new keyword property and set it as the first keyword property. + * + * @param keyword the string to create the property from + * @param alias the alias for the given keyword + */ protected void setFirstKeyword(String keyword, String alias) { KeywordProperty property = new KeywordProperty(keyword, true, alias); @@ -105,6 +111,11 @@ protected void setFirstKeyword(String keyword, String alias) { this.add(property); } + /** + * Create a new keyword property without an alias and set it as the first keyword property. + * + * @param keyword the string to create the property from + */ protected void setFirstKeyword(String keyword) { this.setFirstKeyword(keyword, null); } From 7dda3a3c9a97288f38e2a939442c82d094019934 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 10:23:51 +0200 Subject: [PATCH 38/49] Don't let placeholder properties participate in completion --- .../ide/commandlet/CommandletManagerImpl.java | 2 +- .../devonfw/tools/ide/property/Property.java | 33 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index d43e058506..42de34ffe7 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -237,7 +237,7 @@ public void collectCompletionCandidates(CliArguments arguments, for (Commandlet cmd : this.getCommandlets()) { if (this.context.isTest() || !cmd.isIdeHomeRequired() || this.context.getIdeHome() != null) { KeywordProperty firstKeyword = cmd.getFirstKeyword(); - if (firstKeyword != null) { + if (firstKeyword != null && !firstKeyword.isPlaceholder()) { firstKeyword.apply(arguments, this.context, cmd, collector); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java index 929c43b9ed..2377c1cdc5 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java @@ -51,6 +51,8 @@ public abstract class Property { /** @see #isMultiValued() */ private final boolean multivalued; + private final boolean placeholder; + /** @see #getValue() */ protected final List value = new ArrayList<>(); @@ -63,12 +65,7 @@ public abstract class Property { */ public Property(String name, boolean required, String alias) { - super(); - this.name = name; - this.required = required; - this.alias = alias; - this.multivalued = false; - this.validator = null; + this(name, required, alias, false, null); } /** @@ -81,13 +78,28 @@ public Property(String name, boolean required, String alias) { * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. */ public Property(String name, boolean required, String alias, boolean multivalued, PropertyValidator validator) { + this(name, required, alias, multivalued, false, validator); + } + /** + * The constructor. + * + * @param name the {@link #getName() property name}. + * @param required the {@link #isRequired() required flag}. + * @param alias the {@link #getAlias() property alias}. + * @param multivalued the boolean flag about multiple arguments + * @param placeholder whether this property is substituted by some value or literal + * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + */ + public Property(String name, boolean required, String alias, boolean multivalued, boolean placeholder, PropertyValidator validator) { super(); + this.name = name; this.required = required; this.alias = alias; - this.validator = validator; this.multivalued = multivalued; + this.placeholder = placeholder; + this.validator = validator; } /** @@ -166,6 +178,10 @@ public boolean isMultiValued() { return this.multivalued; } + public boolean isPlaceholder() { + return this.placeholder; + } + /** * Determines if this a value {@link Property}. Such value is either a {@link KeywordProperty} with the keyword as {@link #getName() name} or a raw indexed * value argument. In the latter case the command-line argument at this index will be the immediate value of the {@link Property}, the {@link #getName() name} @@ -343,6 +359,9 @@ protected V getNullValue() { * @return {@code true} if it matches, {@code false} otherwise. */ public boolean apply(CliArguments args, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { + if (this.placeholder && args.current().isCompletion()) { + return false; + } boolean match = this.apply(this.name, args, context, commandlet, collector); if (args.current().isCompletion() && this.alias != null) { From dbee248a2571f2730256fd86e47bd425904afa9b Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 10:27:03 +0200 Subject: [PATCH 39/49] Add placeholder property --- .../tools/ide/commandlet/HelpCommandlet.java | 2 +- .../git/repository/RepositoryCommandlet.java | 2 +- .../ide/property/CommandletProperty.java | 20 ++++++++++++++++--- .../tools/ide/property/FileProperty.java | 10 ++++++---- .../tools/ide/property/FolderProperty.java | 2 +- .../tools/ide/property/PathProperty.java | 7 ++++--- .../ide/property/RepositoryProperty.java | 6 +++--- 7 files changed, 33 insertions(+), 16 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/HelpCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/HelpCommandlet.java index 1ffd249fd3..9036cc62d4 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/HelpCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/HelpCommandlet.java @@ -36,7 +36,7 @@ public HelpCommandlet(IdeContext context) { super(context); addKeyword("--help", "-h"); - this.commandlet = add(new CommandletProperty("", false, "commandlet")); + this.commandlet = add(new CommandletProperty("", false, "commandlet", false)); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryCommandlet.java index 805334809c..c56fabdf80 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryCommandlet.java @@ -41,7 +41,7 @@ public RepositoryCommandlet(IdeContext context) { super(context); addKeyword(getName()); addKeyword("setup"); - this.repository = add(new RepositoryProperty("", false, "repository")); + this.repository = add(new RepositoryProperty("", false, "repository", true)); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java index 6a59035c43..d27665b01d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java @@ -19,7 +19,7 @@ public class CommandletProperty extends Property { */ public CommandletProperty(String name, boolean required, String alias) { - this(name, required, alias, null); + this(name, required, alias, false); } /** @@ -28,11 +28,25 @@ public CommandletProperty(String name, boolean required, String alias) { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. + * @param placeholder whether this property is substituted by some value or literal + */ + public CommandletProperty(String name, boolean required, String alias, boolean placeholder) { + + this(name, required, alias, placeholder, null); + } + + /** + * The constructor. + * + * @param name the {@link #getName() property name}. + * @param required the {@link #isRequired() required flag}. + * @param alias the {@link #getAlias() property alias}. + * @param placeholder whether this property is substituted by some value or literal * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public CommandletProperty(String name, boolean required, String alias, PropertyValidator validator) { + public CommandletProperty(String name, boolean required, String alias, boolean placeholder, PropertyValidator validator) { - super(name, required, alias, false, validator); + super(name, required, alias, false, placeholder, validator); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/FileProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/FileProperty.java index 2eea9d925e..9cf86f6369 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/FileProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/FileProperty.java @@ -15,11 +15,12 @@ public class FileProperty extends PathProperty { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param mustExist the {@link #isPathRequiredToExist() required to exist flag}. + * @param placeholder whether this property is substituted by some value or literal * @param alias the {@link #getAlias() property alias}. */ - public FileProperty(String name, boolean required, String alias, boolean mustExist) { + public FileProperty(String name, boolean required, String alias, boolean mustExist, boolean placeholder) { - this(name, required, alias, mustExist, null); + this(name, required, alias, mustExist, placeholder, null); } /** @@ -29,11 +30,12 @@ public FileProperty(String name, boolean required, String alias, boolean mustExi * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. * @param mustExist the {@link #isPathRequiredToExist() required to exist flag}. + * @param placeholder whether this property is substituted by some value or literal * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public FileProperty(String name, boolean required, String alias, boolean mustExist, PropertyValidator validator) { + public FileProperty(String name, boolean required, String alias, boolean mustExist, boolean placeholder, PropertyValidator validator) { - super(name, required, alias, mustExist, validator); + super(name, required, alias, mustExist, placeholder, validator); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/FolderProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/FolderProperty.java index ff776533fe..61271bb6fa 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/FolderProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/FolderProperty.java @@ -33,7 +33,7 @@ public FolderProperty(String name, boolean required, String alias, boolean mustE */ public FolderProperty(String name, boolean required, String alias, boolean mustExist, PropertyValidator validator) { - super(name, required, alias, mustExist, validator); + super(name, required, alias, mustExist, false, validator); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java index 7825637341..47697ebcdb 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java @@ -29,7 +29,7 @@ public class PathProperty extends Property { */ public PathProperty(String name, boolean required, String alias, boolean mustExist) { - this(name, required, alias, mustExist, null); + this(name, required, alias, mustExist, false, null); } /** @@ -39,11 +39,12 @@ public PathProperty(String name, boolean required, String alias, boolean mustExi * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. * @param mustExist the {@link #isPathRequiredToExist() required to exist flag}. + * @param placeholder whether this property is substituted by some value or literal * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public PathProperty(String name, boolean required, String alias, boolean mustExist, PropertyValidator validator) { + public PathProperty(String name, boolean required, String alias, boolean mustExist, boolean placeholder, PropertyValidator validator) { - super(name, required, alias, false, validator); + super(name, required, alias, false, placeholder, validator); this.mustExist = mustExist; } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java index 909b16c670..ea2ec71a55 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -22,9 +22,9 @@ public class RepositoryProperty extends FileProperty { * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. */ - public RepositoryProperty(String name, boolean required, String alias) { + public RepositoryProperty(String name, boolean required, String alias, boolean placeholder) { - super(name, required, alias, true); + super(name, required, alias, true, placeholder); } /** @@ -37,7 +37,7 @@ public RepositoryProperty(String name, boolean required, String alias) { */ public RepositoryProperty(String name, boolean required, String alias, PropertyValidator validator) { - super(name, required, alias, true, validator); + super(name, required, alias, true, false, validator); } @Override From 46955425cedaf8b90f013a5a58929aee6a4755ed Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 11:13:40 +0200 Subject: [PATCH 40/49] Test fix --- cli/src/main/java/com/devonfw/tools/ide/property/Property.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java index 2377c1cdc5..9c1db5d045 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java @@ -364,7 +364,7 @@ public boolean apply(CliArguments args, IdeContext context, Commandlet commandle } boolean match = this.apply(this.name, args, context, commandlet, collector); - if (args.current().isCompletion() && this.alias != null) { + if (args.current().isCompletion() && this.alias != null && !this.name.isEmpty()) { match |= this.apply(this.alias, args, context, commandlet, collector); } From a8981c77c5360735cb734768b3e23befee33d0f4 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 11:46:01 +0200 Subject: [PATCH 41/49] Fix tests --- .../tools/ide/commandlet/CommandletManagerImpl.java | 7 ++++++- .../tools/ide/commandlet/CommandletManagerTest.java | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index 42de34ffe7..1f8d077d07 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -228,7 +228,8 @@ public void collectCompletionCandidates(CliArguments arguments, CompletionCandidateCollector collector) { CliArgument current = arguments.current(); if (current.isStart()) { - current = current.getNext(); + arguments.next(); + current = arguments.current(); } if (current.isEnd()) { return; @@ -248,6 +249,10 @@ public void collectCompletionCandidates(CliArguments arguments, public Iterator findCommandlet(CliArguments arguments, CompletionCandidateCollector collector) { CliArgument current = arguments.current(); + if (current.isStart()) { + arguments.next(); + current = arguments.current(); + } if (current.isEnd()) { return Collections.emptyIterator(); } diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CommandletManagerTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CommandletManagerTest.java index 6fa7a0eaa9..58bef3e400 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CommandletManagerTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CommandletManagerTest.java @@ -48,7 +48,7 @@ public void testCollectCompletionCandidatesPartialInputFiltersToMatches() { List lines = collector.getSortedCandidates().stream() .map(CompletionCandidate::text).toList(); - assertThat(lines).containsExactly("install"); + assertThat(lines).containsExactlyInAnyOrder("install", "install-plugin"); } @Test From 4eef8280d83847c5a2639ab3121667d523b7be93 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 14:47:33 +0200 Subject: [PATCH 42/49] Fix tests --- .../java/com/devonfw/tools/ide/context/AbstractIdeContext.java | 3 ++- cli/src/main/java/com/devonfw/tools/ide/property/Property.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index 1fcc57347c..ed3ea62073 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -1555,7 +1555,7 @@ public ValidationResult apply(CliArguments arguments, Commandlet cmd) { } } if ((property != null) && property.isValue() && property.isMultiValued()) { - arguments.stopSplitShortOptions(); + arguments.endOptions(); } } boolean matches = currentProperty.apply(arguments, this, cmd, null); @@ -1564,6 +1564,7 @@ public ValidationResult apply(CliArguments arguments, Commandlet cmd) { state.addErrorMessage("No matching property found"); return state; } + arguments.next(); currentArgument = arguments.current(); } return ValidationResultValid.get(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java index 9c1db5d045..f74722b45f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java @@ -395,7 +395,7 @@ protected boolean apply(String normalizedName, CliArguments args, IdeContext con boolean option = normalizedName.startsWith("-"); if (option && !argument.isOption() || !option && argument.isOption() - && argument.get().length() > 0 && !argument.isEndOptions()) { + && argument.get().length() > 0 && !args.isEndOptions()) { return false; } From 9ba614a216172d2dc59ce6e783a65f6cc08a94be Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 14:48:05 +0200 Subject: [PATCH 43/49] Readd init properties as no-op --- .../main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java | 1 + cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java index a5cd9c5fc6..2097c3453a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java @@ -73,6 +73,7 @@ public ToolCommandlet(IdeContext context, String tool, Set tags) { this.tags = tags; addKeyword(tool); this.arguments = new StringProperty("", false, true, "args"); + initProperties(); } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java index a2c3b967b5..109b9dd323 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java @@ -106,6 +106,9 @@ public Mvn(IdeContext context) { this.goals = this.add(new MvnArgProperty("goals", "")); } + @Override + protected void initProperties() {} + @Override protected void configureToolBinary(ProcessContext pc, ProcessMode processMode) { Path mvn = Path.of(getBinaryName()); From 6b637b605cf0affa2570363ee08a4af449e7b763 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 14:50:48 +0200 Subject: [PATCH 44/49] Fix tests by moving them to not break global logging state --- .../tools/ide/cli/CliAdvancedParsingTest.java | 19 ++++++++++++++++++ .../ide/context/AbstractIdeContextTest.java | 20 ------------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/cli/CliAdvancedParsingTest.java b/cli/src/test/java/com/devonfw/tools/ide/cli/CliAdvancedParsingTest.java index 8f2966a717..1d1b86348e 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/cli/CliAdvancedParsingTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/cli/CliAdvancedParsingTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.Test; +import com.devonfw.tools.ide.context.AbstractIdeContext; import com.devonfw.tools.ide.context.AbstractIdeContextTest; import com.devonfw.tools.ide.context.IdeTestContext; @@ -45,4 +46,22 @@ void testRunRepositorySetupWithoutArgumentWillSucceed() { // assert assertThat(success).isEqualTo(0); } + + @Test + void testMvIsCompletedToMvn() { + + AbstractIdeContext context = new IdeTestContext(); + CliArguments args = CliArguments.ofCompletion("mv"); + + assertThat(context.complete(args, true).getFirst().text()).isEqualTo("mvn"); + } + + @Test + void testMvIsCompletedToMvnWithoutContextCompletion() { + + AbstractIdeContext context = new IdeTestContext(); + CliArguments args = CliArguments.ofCompletion("mv"); + + assertThat(context.complete(args, false).getFirst().text()).isEqualTo("mvn"); + } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java index 5bc3a6329f..ad5d37022a 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java @@ -12,12 +12,7 @@ import java.util.Set; import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import com.devonfw.tools.ide.cli.CliArguments; -import com.devonfw.tools.ide.commandlet.Commandlet; -import com.devonfw.tools.ide.completion.CompletionCandidateCollector; -import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault; import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.io.FileCopyMode; import com.devonfw.tools.ide.io.IdeProgressBarTestImpl; @@ -324,19 +319,4 @@ protected void verifyStartScript(IdeContext context, String ide, String workspac } } - @Test - public void testMvIsCompletedToMvn() { - AbstractIdeContext context = new IdeTestContext(); - CliArguments args = CliArguments.ofCompletion("mv"); - - assertThat(context.complete(args, true).getFirst().text()).isEqualTo("mvn"); - } - - @Test - public void testMvIsCompletedToMvnWithoutContextCompletion() { - AbstractIdeContext context = new IdeTestContext(); - CliArguments args = CliArguments.ofCompletion("mv"); - - assertThat(context.complete(args, false).getFirst().text()).isEqualTo("mvn"); - } } From 56bd7cd6aa14c9bea95667ce7567b45c4972baa4 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 14:51:23 +0200 Subject: [PATCH 45/49] Adjust test reference message --- cli/src/test/java/com/devonfw/tools/ide/cli/IdeasyTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/cli/IdeasyTest.java b/cli/src/test/java/com/devonfw/tools/ide/cli/IdeasyTest.java index b9224e9f45..0b756a18b3 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/cli/IdeasyTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/cli/IdeasyTest.java @@ -29,7 +29,7 @@ void testEnvOutsideProjectDoesNotLogCliExitException() { ideasy.run("--debug", "env"); // assert - assertThat(context).logAtDebug().hasMessage("Step 'ide' ended with failure."); + assertThat(context).logAtDebug().hasMessage("Step 'ide' ended successfully."); assertThat(context).log().hasNoEntryWithException(); } From f34f6675a473888883823d5fd5e58049ed2223e1 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 14:58:37 +0200 Subject: [PATCH 46/49] Add CHANGELOG entry --- CHANGELOG.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index ce7e0d6a69..d3962b846a 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -30,6 +30,7 @@ Release with new features and bugfixes: * https://github.com/devonfw/IDEasy/issues/1882[#1882]: Add AWS CDK to IDEasy commandlets * https://github.com/devonfw/IDEasy/issues/800[#800]: Fix infinite recursion in Sonar start/stop on macOS * https://github.com/devonfw/IDEasy/issues/1844[#1844]: Fix vscode installation hanging indefinitely in WSL Linux environments +* https://github.com/devonfw/IDEasy/issues/1392[#1392]: Smart completions The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/44?closed=1[milestone 2026.05.001]. From 290dd95b6a2bcb7f80ae6c76afa0377783eb90c5 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 15:49:04 +0200 Subject: [PATCH 47/49] Add doc comment --- .../com/devonfw/tools/ide/commandlet/CommandletManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManager.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManager.java index 813efdd59f..806b99fb0a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManager.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManager.java @@ -95,6 +95,12 @@ default LocalToolCommandlet getRequiredLocalToolCommandlet(String name) { throw new IllegalArgumentException("The commandlet " + name + " is not a LocalToolCommandlet!"); } + /** + * Gather all completion candidates. + * + * @param arguments the arguments to complete + * @param collector the collector to store completions + */ public abstract void collectCompletionCandidates(CliArguments arguments, CompletionCandidateCollector collector); From cf4a7f7170977be7228220715b6d5d91e85f78a4 Mon Sep 17 00:00:00 2001 From: Henok Lachmann Date: Tue, 19 May 2026 15:55:28 +0200 Subject: [PATCH 48/49] Uncomment assertion --- .../java/com/devonfw/tools/ide/context/AbstractIdeContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index ed3ea62073..81ca6536c8 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -1439,7 +1439,7 @@ public List complete(CliArguments arguments, boolean includ if (includeContextOptions) { ContextCommandlet cc = new ContextCommandlet(); for (Property property : cc.getProperties()) { - // assert (property.isOption()); + assert (property.isOption()); property.apply(arguments, this, cc, collector); } } From bd8f4955993d62e70ff92d29b650adcebd6f4382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Fri, 29 May 2026 19:51:03 +0200 Subject: [PATCH 49/49] fixed merge error --- .../java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java index 2de2176c49..d40c4a3df5 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java @@ -73,7 +73,7 @@ protected void doRun() { Parser parser = new DefaultParser(); try (Terminal terminal = TerminalBuilder.builder().build()) { // initialize our own completer here and add exit as an autocompletion option - Completer completer = new IdeCompleter((AbstractIdeContext) this.context); + IdeCompleter completer = new IdeCompleter((AbstractIdeContext) this.context); LineReader reader = LineReaderBuilder.builder().terminal(terminal).completer(completer).parser(parser) .variable(LineReader.LIST_MAX, AUTOCOMPLETER_MAX_RESULTS).build();