Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
1ebcaf5
Add completion tests
HenokLachmann Apr 27, 2026
608c40c
Allow selection of completion argument in CliArguments
HenokLachmann Apr 27, 2026
2daba5d
Add more completion tests
HenokLachmann Apr 27, 2026
dac795f
Extract completion candidate collection into own method
HenokLachmann Apr 27, 2026
f63af46
Change CompletionCandidate test to entries list
HenokLachmann Apr 27, 2026
682226c
Adjust constructor call
HenokLachmann Apr 27, 2026
bf984c1
Pass completion status to candidate
HenokLachmann Apr 27, 2026
0830786
Fix commandlet-specific flag completion
HenokLachmann Apr 27, 2026
6e66a96
Make completeValue() abstract
HenokLachmann Apr 27, 2026
7d9135a
Add specific multi-value maven goal property for autocompletion
HenokLachmann Apr 27, 2026
6408e05
Adjust createCandidate() call
HenokLachmann Apr 27, 2026
d827ecc
Add properties to Mvn commandlet
HenokLachmann Apr 27, 2026
365c648
Comment out potentially wrong check
HenokLachmann Apr 27, 2026
5ca5927
Unapply quickfix
HenokLachmann May 13, 2026
18d84a7
Try removing initProperties to avoid assert error
HenokLachmann May 13, 2026
31c5fc3
Add abstract add() method for CompletionCandidate
HenokLachmann May 13, 2026
d08b45d
Implement add() method
HenokLachmann May 13, 2026
001b5ee
Remove superfluous lines
HenokLachmann May 13, 2026
b0197e8
Remove superfluous arguments from add() calls
HenokLachmann May 13, 2026
be38f45
Try to fix Property.apply()
HenokLachmann May 13, 2026
ecf7f18
Remove superfluous lines
HenokLachmann May 13, 2026
d011275
Fix bug to allow Mvn their own properties
HenokLachmann Apr 28, 2026
d4ae88b
Avoid double registering of completions
HenokLachmann May 13, 2026
f16bb6b
Allow completion during testing
HenokLachmann May 13, 2026
dae126e
Opt tests out of internal root dir logic
HenokLachmann May 13, 2026
72d8f5c
Add methods to set the first keyword
HenokLachmann May 19, 2026
10334bf
Prevent top-level completion of secondary commands
HenokLachmann May 19, 2026
5f91447
Dynamically add 'exit' as a shell keyword
HenokLachmann May 19, 2026
2219994
Restructure completion logic
HenokLachmann May 19, 2026
ed31941
Properly complete long and short options
HenokLachmann May 19, 2026
5dd62f7
Restructure joined short opt completion
HenokLachmann May 19, 2026
5621a66
Add German and English descriptions for new maven commands
HenokLachmann May 19, 2026
27e15f4
Adjust synopsis of mvn command
HenokLachmann May 19, 2026
d7a52e6
Fix exit completion test
HenokLachmann May 19, 2026
10599c6
Prevent completion to the identical word
HenokLachmann May 19, 2026
cf75b91
Fix more tests
HenokLachmann May 19, 2026
43b8e5c
Add doc comments
HenokLachmann May 19, 2026
7dda3a3
Don't let placeholder properties participate in completion
HenokLachmann May 19, 2026
dbee248
Add placeholder property
HenokLachmann May 19, 2026
4695542
Test fix
HenokLachmann May 19, 2026
a8981c7
Fix tests
HenokLachmann May 19, 2026
4eef828
Fix tests
HenokLachmann May 19, 2026
9ba614a
Readd init properties as no-op
HenokLachmann May 19, 2026
6b637b6
Fix tests by moving them to not break global logging state
HenokLachmann May 19, 2026
56bd7cd
Adjust test reference message
HenokLachmann May 19, 2026
d57804e
Merge branch 'main' into feature/#1392-smart-completions
HenokLachmann May 19, 2026
f34f667
Add CHANGELOG entry
HenokLachmann May 19, 2026
06c8b9e
Merge branch 'main' into feature/#1392-smart-completions
HenokLachmann May 19, 2026
290dd95
Add doc comment
HenokLachmann May 19, 2026
cf4a7f7
Uncomment assertion
HenokLachmann May 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Release with new features and bugfixes:
* https://github.com/devonfw/IDEasy/issues/800[#800]: Fix infinite recursion in Sonar start/stop on macOS
* https://github.com/devonfw/IDEasy/issues/1716[#1716]: Add commandlet for Claude Code CLI
* 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].

Expand Down
13 changes: 13 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/cli/CliArgument.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/cli/CliArguments.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public class CliArguments implements Iterator<CliArgument> {

private boolean splitShortOpts;

public CliArguments(int completionIdx, String... args) {
this(CliArgument.of(completionIdx, args));
}

/**
* The constructor.
*
Expand Down Expand Up @@ -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}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down Expand Up @@ -191,7 +213,7 @@ public boolean isIdeHomeRequired() {
*/
public boolean isIdeRootRequired() {

return true;
return !this.context.isTest();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,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);
Expand Down Expand Up @@ -236,10 +233,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<Commandlet> findCommandlet(CliArguments arguments, CompletionCandidateCollector collector) {

CliArgument current = arguments.current();
if (current.isStart()) {
arguments.next();
current = arguments.current();
}
if (current.isEnd()) {
return Collections.emptyIterator();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CompletionCandidate> {
public record CompletionCandidate(List<String> entries,
String description,
boolean complete) implements Comparable<CompletionCandidate> {

@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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
}

Expand All @@ -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;
}
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -79,5 +88,4 @@ public String toString() {

return this.candidates.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public void complete(LineReader reader, ParsedLine commandLine, List<Candidate>
List<CompletionCandidate> 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++));
}
}

Expand Down
Loading
Loading