Skip to content

Commit

Permalink
devonfw#58: added jline to Ide
Browse files Browse the repository at this point in the history
refactored CommandletRegistry
added jline3 autocompletion to Ide (if Ide was run without arguments)
fixed multiple initializations warning
moved IdeCompleter to new class file
added simple completion examples
  • Loading branch information
jan-vcapgemini committed Oct 23, 2023
1 parent 1da0e6f commit c6ff41b
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.devonfw.tools.ide.commandlet;
package com.devonfw.tools.ide.cli;

import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -13,57 +13,38 @@
import org.jline.console.ArgDesc;
import org.jline.console.CmdDesc;
import org.jline.console.CommandRegistry;
import org.jline.reader.Candidate;
import org.jline.reader.Completer;
import org.jline.reader.LineReader;
import org.jline.reader.ParsedLine;
import org.jline.reader.impl.completer.ArgumentCompleter;
import org.jline.reader.impl.completer.NullCompleter;
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 {

private final IdeContext context;

private final Set<String> commandlets;

private final Map<String, String> aliasCommandlets;

private class IdeCompleter extends ArgumentCompleter implements Completer {
private final ContextCommandlet cmd;

public IdeCompleter() {
private final IdeContext context;

super(NullCompleter.INSTANCE);
}
private final Ide ide;

@Override
public void complete(LineReader reader, ParsedLine commandLine, List<Candidate> candidates) {
assert commandLine != null;
assert candidates != null;
String word = commandLine.word();
List<String> words = commandLine.words();
// TODO: implement rest of this
}
private final IdeCompleter ideCompleter;

private void addCandidates(List<Candidate> candidates, Iterable<String> cands) {
addCandidates(candidates, cands, "", "", true);
}
private final Set<String> commandlets;

private void addCandidates(List<Candidate> candidates, Iterable<String> cands, String preFix, String postFix, boolean complete) {
for (String s : cands) {
candidates.add(new Candidate(AttributedString.stripAnsi(preFix + s + postFix), s, null, null, null, null, complete));
}
}
}
private final Map<String, String> aliasCommandlets;

public CommandletRegistry(IdeContext context) {
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();
Expand Down Expand Up @@ -103,10 +84,11 @@ public List<String> commandInfo(String command) {
// TODO: take command and get help from Ide
Commandlet helpCommandlet = context.getCommandletManager().getCommandlet("help");
Property<?> property = new CommandletProperty(command, false, "");
helpCommandlet.add(property);
// helpCommandlet.add(property);
helpCommandlet.run();
String description = "description";
// out.addAll(Arrays.asList(description.split("\\r?\\n")));
// TODO: add our own description for each commandlet here
String description = "placeholder description";
out.addAll(Arrays.asList(description.split("\\r?\\n")));
return out;
}

Expand All @@ -123,7 +105,7 @@ public SystemCompleter compileCompleters() {
List<String> all = new ArrayList<>();
all.addAll(commandlets);
all.addAll(aliasCommandlets.keySet());
out.add(all, new IdeCompleter());
out.add(all, new IdeCompleter(cmd, context));
return out;
}

Expand All @@ -135,32 +117,68 @@ public Object invoke(CommandRegistry.CommandSession session, String command, Obj
arguments.add(command);
arguments.addAll(Arrays.stream(args).map(Object::toString).collect(Collectors.toList()));
// TODO: run our commandlet here

context.getCommandletManager().getCommandlet(command).run();
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
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
context.getCommandletManager().getCommandlet(command).run();
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
// @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("")), options);
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;
}

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() {

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"));

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

0 comments on commit c6ff41b

Please sign in to comment.