diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index bcbe54ca34..b19eb7f4a0 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -8,6 +8,7 @@ Release with new features and bugfixes: * https://github.com/devonfw/IDEasy/issues/1964[#1964]: Fixed gui not launching with older project java versions * https://github.com/devonfw/IDEasy/issues/1849[#1849]: Add VSCodium support +* 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/45?closed=1[milestone 2026.06.001]. 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}. 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..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,28 @@ 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); + + this.firstKeyword = property; + 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); + } + /** * @param keyword the {@link KeywordProperty keyword} to {@link #add(Property) add}. * @param alias the optional {@link KeywordProperty#getAlias() alias}. @@ -191,7 +213,7 @@ public boolean isIdeHomeRequired() { */ public boolean isIdeRootRequired() { - return true; + return !this.context.isTest(); } /** 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..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,15 @@ 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); + /** * @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 537196461e..c9a0a8be91 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 @@ -178,10 +178,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); @@ -240,10 +237,36 @@ 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()) { + arguments.next(); + current = arguments.current(); + } + if (current.isEnd()) { + return; + } + + for (Commandlet cmd : this.getCommandlets()) { + if (this.context.isTest() || !cmd.isIdeHomeRequired() || this.context.getIdeHome() != null) { + KeywordProperty firstKeyword = cmd.getFirstKeyword(); + if (firstKeyword != null && !firstKeyword.isPlaceholder()) { + firstKeyword.apply(arguments, this.context, cmd, collector); + } + } + } + } + @Override 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/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/commandlet/ShellCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java index 48991a73c0..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 @@ -67,10 +67,12 @@ 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 IdeCompleter completer = new IdeCompleter((AbstractIdeContext) this.context); LineReader reader = LineReaderBuilder.builder().terminal(terminal).completer(completer).parser(parser) 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); } } 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..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,13 @@ 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, 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; } @@ -47,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; } @@ -56,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(); 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 33f6fc25fb..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 @@ -38,19 +38,28 @@ public CompletionCandidateCollectorDefault(IdeContext context) { } @Override - public void add(String text, String description, Property property, Commandlet commandlet) { + public void add(CompletionCandidate completion) { + for (CompletionCandidate existing : this.candidates) { + if (existing.text().equals(completion.text())) { + return; + } + } - // Check if this candidate already exists to avoid duplicates + this.candidates.add(completion); + } + + @Override + public void add(String text, String description) { for (CompletionCandidate existing : this.candidates) { if (existing.text().equals(text)) { - // Duplicate candidate found, do not add return; } } - CompletionCandidate candidate = createCandidate(text, description, property, commandlet); + CompletionCandidate candidate = this.createCandidate(text, description, !text.endsWith("=")); this.candidates.add(candidate); - LOG.trace("Added {} for auto-completion of property {}.{}", candidate, commandlet, property); + + LOG.trace("Added {} for auto-completion.", candidate); } @Override @@ -79,5 +88,4 @@ public String toString() { return this.candidates.toString(); } - } 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 ade9d6866d..99dbcbf1bd 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 @@ -45,7 +45,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++)); } } 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 1d06087894..7739309afc 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; @@ -1450,12 +1451,11 @@ public List complete(CliArguments arguments, boolean includ property.apply(arguments, this, cc, collector); } } - Iterator commandletIterator = this.commandletManager.findCommandlet(arguments, collector); - CliArgument current = arguments.current(); - if (current.isCompletion() && current.isCombinedShortOption()) { - collector.add(current.get(), null, null, null); - } - arguments.next(); + + this.commandletManager.collectCompletionCandidates(arguments, collector); + + Iterator commandletIterator = this.commandletManager.findCommandlet(arguments, null); + while (commandletIterator.hasNext()) { Commandlet cmd = commandletIterator.next(); if (!arguments.current().isEnd()) { @@ -1469,7 +1469,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(); - valueIterator.next(); // skip first property since this is the keyword property that already matched to find the commandlet + Property lastValueProperty = null; 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()); @@ -1489,36 +1489,36 @@ 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 { - if (valueIterator.hasNext()) { - Property valueProperty = valueIterator.next(); - boolean success = valueProperty.apply(arguments, this, cmd, collector); - if (!success) { - LOG.trace("Completion cannot match any further."); - return; + if (currentArgument.isCompletion() && currentArgument.get().isEmpty() + && !arguments.isEndOptions()) { + for (Property option : optionProperties) { + option.apply(arguments, this, cmd, collector); } - } else { - LOG.trace("No value left for completion."); - return; + } + + Property valueProperty = null; + if (valueIterator.hasNext()) { + lastValueProperty = valueIterator.next(); + valueProperty = lastValueProperty; + } else if (lastValueProperty != null && lastValueProperty.isMultiValued()) { + valueProperty = lastValueProperty; + } + + if (valueProperty != null) { + valueProperty.apply(arguments, this, cmd, collector); } } + + arguments.next(); currentArgument = arguments.current(); } } @@ -1563,7 +1563,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); @@ -1572,6 +1572,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/context/IdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java index 83030135d0..ba72e64a09 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()}. */ 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/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/CommandletProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java index 76b42d0aa4..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 @@ -53,7 +67,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/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/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/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/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 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..c516612f49 --- /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, 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); + } + } + } +} 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/PathProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java index 1fa1ffcc61..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; } @@ -129,7 +130,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/Property.java b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java index dbda7b1271..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 @@ -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; @@ -50,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<>(); @@ -62,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); } /** @@ -80,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; } /** @@ -165,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} @@ -342,8 +359,16 @@ 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 && !this.name.isEmpty()) { + match |= this.apply(this.alias, args, context, commandlet, collector); + } - return apply(this.name, args, context, commandlet, collector); + return match; } /** @@ -358,19 +383,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 && !args.isEndOptions()) { return false; } + String argValue = null; boolean lookahead = false; + if (normalizedName.isEmpty()) { argValue = argument.get(); } else { @@ -379,7 +410,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; @@ -407,16 +437,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; } @@ -429,39 +449,43 @@ 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); - } 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 (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, this, commandlet); + 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; } /** @@ -472,9 +496,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/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 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. */ 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 759512adc9..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); + "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"); } } } 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..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 @@ -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,8 +86,29 @@ 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 + protected void initProperties() {} + @Override protected void configureToolBinary(ProcessContext pc, ProcessMode processMode) { Path mvn = Path.of(getBinaryName()); diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties index 24f6eef61d..65040f3adc 100644 --- a/cli/src/main/resources/nls/Help.properties +++ b/cli/src/main/resources/nls/Help.properties @@ -156,9 +156,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-plugin-reinstall=resets installed plugins to the project configuration opt.--force-plugins=force plugin (re)installation. @@ -174,6 +182,7 @@ 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.--symbolic=Create a symbolic link instead of a hard link (default). +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: @@ -184,6 +193,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.link=The path where the link is created. val.plugin=The plugin to select val.settingsRepository=The settings git repository with the IDEasy configuration for the project. diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index 4181f99043..dced59c077 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -156,9 +156,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-plugin-reinstall=Setzt installierte Plugins zurück auf die Projektkonfiguration. opt.--force-plugins=Erzwingt die (Re)Installation von Plugins. @@ -174,6 +182,7 @@ 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.--symbolic=Erstellt einen symbolischen Link anstelle eines Hardlinks (Standard). +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: @@ -184,6 +193,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.link=Pfad des zu erstellenden Links. val.plugin=Die zu selektierende Erweiterung. val.settingsRepository=Das settings git Repository mit den IDEasy Einstellungen für das Projekt. 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/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(); } 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..58bef3e400 --- /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).containsExactlyInAnyOrder("install", "install-plugin"); + } + + @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/HelpCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/HelpCommandletTest.java index b2672ee171..1c8b1b0e87 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); } 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..ca99c1bf57 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/ShellCommandletTest.java @@ -0,0 +1,39 @@ +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, ""); + + Commandlet shellCommandlet = context.getCommandletManager().getCommandlet("shell"); + shellCommandlet.setFirstKeyword("exit"); + + 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"); + + Commandlet shellCommandlet = context.getCommandletManager().getCommandlet("shell"); + shellCommandlet.setFirstKeyword("exit"); + + 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/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", "". */ 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/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"); + } +} 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..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 @@ -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", "-amt"); + } + + @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"); + } }