Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#56 implement autocompletion #112

Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
9d2d1d9
#58: initial jline3 autocompletion implementation
jan-vcapgemini Oct 11, 2023
c3950ef
Merge branch 'main' into feature/56-implement-autocompletion
jan-vcapgemini Oct 16, 2023
1da0e6f
#58: added jline3 CommandRegistry
jan-vcapgemini Oct 19, 2023
c6ff41b
#58: added jline to Ide
jan-vcapgemini Oct 23, 2023
d7db2b0
#56: added licenses
jan-vcapgemini Oct 23, 2023
9b1b9c7
Merge branch 'main' into feature/56-implement-autocompletion
jan-vcapgemini Oct 24, 2023
6c2652e
#56: implemented requested changes
jan-vcapgemini Oct 24, 2023
4c35d3a
Merge branch 'main' into feature/56-implement-autocompletion
jan-vcapgemini Oct 30, 2023
6dc807b
#56: completions up to level 3
jan-vcapgemini Oct 30, 2023
9996846
#56: added 1st tests
jan-vcapgemini Oct 31, 2023
4d92d24
#56: cleanup and help completion
jan-vcapgemini Oct 31, 2023
4f4116b
Merge branch 'main' into feature/56-implement-autocompletion
jan-vcapgemini Oct 31, 2023
fc68138
#56: fixed multiple initializations
jan-vcapgemini Nov 2, 2023
986dcc6
#56: fixed some issues
jan-vcapgemini Nov 2, 2023
22991e4
#56: implemented options completion
jan-vcapgemini Nov 7, 2023
3e99f66
#56: removed findSubCommandlet
jan-vcapgemini Nov 7, 2023
047abe8
#56: moved tests to cli
jan-vcapgemini Nov 7, 2023
67b35db
#56: added new constant
jan-vcapgemini Nov 7, 2023
51d3ae0
Merge branch 'main' into feature/56-implement-autocompletion
jan-vcapgemini Nov 7, 2023
2f95364
#56: refactored Ide class
jan-vcapgemini Nov 7, 2023
b4f30b1
Merge branch 'main' into feature/56-implement-autocompletion
jan-vcapgemini Nov 20, 2023
e97a52d
#56: added support for options
jan-vcapgemini Nov 20, 2023
0d7af44
#56: combined options and tool commands completion
jan-vcapgemini Nov 20, 2023
cc1a165
#56: fixed version test
jan-vcapgemini Nov 20, 2023
7334b5e
#56: changed visibility of addCandidates
jan-vcapgemini Nov 21, 2023
3ed6dd4
#56: refactored ReaderTestSupport
jan-vcapgemini Nov 21, 2023
31d7571
#56: removed jansi from LICENSE
jan-vcapgemini Nov 21, 2023
4bc8ce1
#56: removed CompleteCommandlet
jan-vcapgemini Nov 21, 2023
ee92a3d
Revert "#56: removed jansi from LICENSE"
jan-vcapgemini Nov 30, 2023
26ba24e
#56: re-added jansi updated jline3
jan-vcapgemini Nov 30, 2023
1a6b6f1
#56: implemented requested changes
jan-vcapgemini Dec 4, 2023
3b90ad7
#56: implemented requested changes
jan-vcapgemini Dec 4, 2023
55e3f90
#56: implemented requested changes
jan-vcapgemini Dec 4, 2023
1451913
#56: fixed options after command
jan-vcapgemini Dec 5, 2023
cf0a668
#56: fixed context not resetting
jan-vcapgemini Dec 11, 2023
7b2fd5a
#56: implemented requested changes
jan-vcapgemini Dec 11, 2023
71308fd
#56: fixed regex
jan-vcapgemini Dec 11, 2023
223e364
#56: refactored invalid pattern
jan-vcapgemini Dec 12, 2023
9c05198
Merge branch 'main' into feature/56-implement-autocompletion
jan-vcapgemini Dec 12, 2023
776689b
#56: adjusted exception handling
jan-vcapgemini Dec 14, 2023
7f7a207
#56: optimized check for invalid patterns
jan-vcapgemini Dec 14, 2023
37005ee
#56: implemented requested change
jan-vcapgemini Dec 14, 2023
e4502a1
Merge branch 'main' into feature/56-implement-autocompletion
jan-vcapgemini Dec 15, 2023
62ce9d0
Merge branch 'main' into feature/56-implement-autocompletion
jan-vcapgemini Jan 8, 2024
79fde21
#56: implemented requested change
jan-vcapgemini Jan 8, 2024
ba1ae7d
#56: implemented requested change
jan-vcapgemini Jan 8, 2024
2460b74
#56: implemented requested changes
jan-vcapgemini Jan 8, 2024
72d79d4
#56: implemented requested changes
jan-vcapgemini Jan 8, 2024
ce01b3c
#56: implemented requested changes
jan-vcapgemini Jan 8, 2024
635e7a6
#56: implemented requested change
jan-vcapgemini Jan 8, 2024
82cf24d
#56: fixed HelpCommandlet test
jan-vcapgemini Jan 8, 2024
e05a3af
Merge branch 'main' into feature/56-implement-autocompletion
hohwille Jan 8, 2024
c2d4f5b
#56: implemented requested change
jan-vcapgemini Jan 9, 2024
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
15 changes: 15 additions & 0 deletions cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@
<artifactId>progressbar</artifactId>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline</artifactId>
<version>3.23.0</version>
</dependency>
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>
<version>2.4.0</version>
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-terminal-jansi</artifactId>
<version>3.23.0</version>
</dependency>
hohwille marked this conversation as resolved.
Show resolved Hide resolved
</dependencies>

<build>
Expand Down
184 changes: 184 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/cli/CommandletRegistry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package com.devonfw.tools.ide.cli;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.jline.console.ArgDesc;
import org.jline.console.CmdDesc;
import org.jline.console.CommandRegistry;
import org.jline.reader.impl.completer.SystemCompleter;
import org.jline.utils.AttributedString;

import com.devonfw.tools.ide.commandlet.Commandlet;
import com.devonfw.tools.ide.commandlet.ContextCommandlet;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.property.CommandletProperty;
import com.devonfw.tools.ide.property.Property;

/**
* Implements the {@link CommandRegistry} for jline3.
*/
public class CommandletRegistry implements CommandRegistry {
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved

private final ContextCommandlet cmd;

private final IdeContext context;

private final Ide ide;

private final IdeCompleter ideCompleter;

private final Set<String> commandlets;

private final Map<String, String> aliasCommandlets;

public CommandletRegistry(ContextCommandlet cmd, Ide ide, IdeContext context) {

this.ideCompleter = new IdeCompleter(cmd, context);
this.cmd = cmd;
this.context = context;
this.ide = ide;

Set<String> commandlets = new HashSet<>();
Collection<Commandlet> commandletCollection = context.getCommandletManager().getCommandlets();

for (Commandlet commandlet : commandletCollection) {
commandlets.add(commandlet.getName());
}

// TODO: get correct aliases, uses command name keywords instead now
Map<String, String> aliasCommandlets = new HashMap<>();
Collection<Commandlet> aliasCommandletCollection = context.getCommandletManager().getCommandlets();

for (Commandlet commandlet : aliasCommandletCollection) {
aliasCommandlets.put(commandlet.getName(), commandlet.getKeyword());
}

this.commandlets = commandlets;
this.aliasCommandlets = aliasCommandlets;
}

@Override
public Set<String> commandNames() {

return commandlets;
}

@Override
public Map<String, String> commandAliases() {

return aliasCommandlets;
}

@Override
public List<String> commandInfo(String command) {

List<String> out = new ArrayList<>();
// TODO: take command and get help from Ide
Commandlet helpCommandlet = context.getCommandletManager().getCommandlet("help");
Property<?> property = new CommandletProperty(command, false, "");
// helpCommandlet.add(property);
helpCommandlet.run();
hohwille marked this conversation as resolved.
Show resolved Hide resolved
// TODO: add our own description for each commandlet here
String description = "placeholder description";
out.addAll(Arrays.asList(description.split("\\r?\\n")));
return out;
}

@Override
public boolean hasCommand(String command) {

return commandlets.contains(command) || aliasCommandlets.containsKey(command);
}

@Override
public SystemCompleter compileCompleters() {

SystemCompleter out = new SystemCompleter();
List<String> all = new ArrayList<>();
all.addAll(commandlets);
all.addAll(aliasCommandlets.keySet());
out.add(all, new IdeCompleter(cmd, context));
return out;
}

// For JLine >= 3.16.0
@Override
public Object invoke(CommandRegistry.CommandSession session, String command, Object[] args) throws Exception {

List<String> arguments = new ArrayList<>();
arguments.add(command);
arguments.addAll(Arrays.stream(args).map(Object::toString).collect(Collectors.toList()));
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved
// TODO: run our commandlet here
runCommand(command, arguments);
return null;
}

private void runCommand(String command, List<String> arguments) {

String[] convertedArgs = arguments.toArray(new String[0]);
CliArgument first = CliArgument.of(convertedArgs);
Commandlet firstCandidate = this.context.getCommandletManager().getCommandletByFirstKeyword(command);
ide.applyAndRun(first, firstCandidate);
}

// @Override This method was removed in JLine 3.16.0; keep it in case this component is used with an older version of
// JLine
public Object execute(CommandRegistry.CommandSession session, String command, String[] args) throws Exception {

List<String> arguments = new ArrayList<>();
arguments.add(command);
arguments.addAll(Arrays.asList(args));
// TODO: run our commandlet here
runCommand(command, arguments);
return null;
}

// @Override This method was removed in JLine 3.16.0; keep it in case this component is used with an older version of
// JLine
public CmdDesc commandDescription(String command) {

return null;
}

@Override
public CmdDesc commandDescription(List<String> list) {

Commandlet sub = ideCompleter.findSubcommandlet(list, list.size());

if (sub == null) {
return null;
}

List<AttributedString> main = new ArrayList<>();
Map<String, List<AttributedString>> options = new HashMap<>();
// String synopsis =
// AttributedString.stripAnsi(spec.usageMessage().sectionMap().get("synopsis").render(cmdhelp).toString());
// main.add(Options.HelpException.highlightSyntax(synopsis.trim(), Options.HelpException.defaultStyle()));

AttributedString attributedString = new AttributedString("test");
main.add(attributedString);
options.put("test", main);
// for (OptionSpec o : spec.options()) {
// String key = Arrays.stream(o.names()).collect(Collectors.joining(" "));
// List<AttributedString> val = new ArrayList<>();
// for (String d: o.description()) {
// val.add(new AttributedString(d));
// }
// if (o.arity().max() > 0) {
// key += "=" + o.paramLabel();
// }
// options.put(key, val);
// }
// return new CmdDesc(main, ArgDesc.doArgNames(Arrays.asList("")), options);
// TODO: implement this
return new CmdDesc(main, ArgDesc.doArgNames(Arrays.asList("description")), options);
}
}
101 changes: 100 additions & 1 deletion cli/src/main/java/com/devonfw/tools/ide/cli/Ide.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
package com.devonfw.tools.ide.cli;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.function.Supplier;

import org.fusesource.jansi.AnsiConsole;
import org.jline.console.SystemRegistry;
import org.jline.console.impl.SystemRegistryImpl;
import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.MaskingCallback;
import org.jline.reader.Parser;
import org.jline.reader.UserInterruptException;
import org.jline.reader.impl.DefaultParser;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.widget.AutosuggestionWidgets;

import com.devonfw.tools.ide.commandlet.Commandlet;
import com.devonfw.tools.ide.commandlet.ContextCommandlet;
Expand Down Expand Up @@ -94,6 +111,11 @@ public int run(String... args) {
*/
public int runOrThrow(String... args) {

if (args.length == 0) {
initializeJlineCompletion();
return 1;
hohwille marked this conversation as resolved.
Show resolved Hide resolved
}

CliArgument first = CliArgument.of(args);
CliArgument current = initContext(first);
if (current == null) {
Expand Down Expand Up @@ -124,6 +146,83 @@ public int runOrThrow(String... args) {
return 1;
}

/**
* Initializes jline3 autocompletion.
*/
private void initializeJlineCompletion() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be wrong but if I read the code correctly this is not "initializing" the "completion" but actually "running it interactively" and also executing what the user finally submitted.
Therefore the method name and the JavaDoc is misleading. I would name the method rather something like runWithInteractiveCompletion or so...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted.


AnsiConsole.systemInstall();

try {
ContextCommandlet init = new ContextCommandlet();
init.run();
this.context = init.getIdeContext();

Supplier<Path> workDir = context::getCwd;
// set up JLine built-in commands
// TODO: fix BuiltIns or remove
// Builtins builtins = new Builtins(workDir, null, null);
// builtins.rename(Builtins.Command.TTOP, "top");
// builtins.alias("zle", "widget");
// builtins.alias("bindkey", "keymap");

CommandletRegistry commandletRegistry = new CommandletRegistry(init, this, context);

Parser parser = new DefaultParser();
try (Terminal terminal = TerminalBuilder.builder().build()) {

SystemRegistry systemRegistry = new SystemRegistryImpl(parser, terminal, workDir, null);
systemRegistry.setCommandRegistries(commandletRegistry);

// systemRegistry.setCommandRegistries(builtins, commandletRegistry);
// systemRegistry.register("help", commandletRegistry);

LineReader reader = LineReaderBuilder.builder().terminal(terminal).completer(systemRegistry.completer())
.parser(parser).variable(LineReader.LIST_MAX, 50).build();

// Create autosuggestion widgets
AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(reader);
// Enable autosuggestions
autosuggestionWidgets.enable();

// TODO: implement TailTipWidgets
// TailTipWidgets widgets = new TailTipWidgets(reader, systemRegistry::commandDescription, 5,
// TailTipWidgets.TipType.COMPLETER);
// widgets.enable();
// TODO: add own KeyMap
// KeyMap<Binding> keyMap = reader.getKeyMaps().get("main");
// keyMap.bind(new Reference("tailtip-toggle"), KeyMap.alt("s"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced with TODO with link to issue.


String prompt = "prompt> ";
String rightPrompt = null;
String line;

while (true) {
try {
systemRegistry.cleanUp();
line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null);
systemRegistry.execute(line);
} catch (UserInterruptException e) {
// Ignore
context.warning("User canceled with CTRL+C", e);
} catch (EndOfFileException e) {
context.warning("User canceled with CTRL+D", e);
return;
} catch (Exception e) {
systemRegistry.trace(e);
}
}

} catch (IOException e) {
throw new RuntimeException(e);
}
} catch (Throwable e) {
throw new RuntimeException(e);
} finally {
AnsiConsole.systemUninstall();
}
}

private CliArgument initContext(CliArgument first) {

ContextCommandlet init = new ContextCommandlet();
Expand Down Expand Up @@ -161,7 +260,7 @@ private CliArgument initContext(CliArgument first) {
* @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully,
* {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
*/
private boolean applyAndRun(CliArgument current, Commandlet commandlet) {
protected boolean applyAndRun(CliArgument current, Commandlet commandlet) {

boolean matches = apply(current, commandlet);
if (matches) {
Expand Down
Loading