diff --git a/app.json b/app.json
index 69ff15d..46c73e7 100644
--- a/app.json
+++ b/app.json
@@ -5,6 +5,8 @@
"eu.maveniverse.maven.mima.runtime:standalone-static": "2.4.15",
"eu.maveniverse.maven.mima:context": "2.4.15",
"info.picocli:picocli": "4.7.6",
+ "org.jline:jline-console-ui": "3.26.2",
+ "org.jline:jline-terminal-jni": "3.26.2",
"org.slf4j:slf4j-api": "2.0.13",
"org.slf4j:slf4j-log4j12": "2.0.13",
"org.slf4j:slf4j-simple": "2.0.13"
diff --git a/pom.xml b/pom.xml
index a1c9331..95606fc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,7 @@
2.4.15
4.7.6
2.11.0
+ 3.26.3-SNAPSHOT
2.0.13
2.43.0
1.22.0
@@ -61,6 +62,16 @@
gson
${version.gson}
+
+ org.jline
+ jline-console-ui
+ ${version.jline}
+
+
+ org.jline
+ jline-terminal-jni
+ ${version.jline}
+
org.slf4j
slf4j-api
diff --git a/src/main/java/org/codejive/jpm/Main.java b/src/main/java/org/codejive/jpm/Main.java
index 4cc8177..9cef57f 100644
--- a/src/main/java/org/codejive/jpm/Main.java
+++ b/src/main/java/org/codejive/jpm/Main.java
@@ -2,6 +2,7 @@
//DEPS eu.maveniverse.maven.mima:context:2.4.15 eu.maveniverse.maven.mima.runtime:standalone-static:2.4.15
//DEPS info.picocli:picocli:4.7.6
//DEPS com.google.code.gson:gson:2.11.0
+//DEPS org.jline:jline-console-ui:3.26.2 org.jline:jline-terminal-jni:3.26.2
//DEPS org.slf4j:slf4j-api:2.0.13 org.slf4j:slf4j-simple:2.0.13
//SOURCES Jpm.java json/AppInfo.java util/FileUtils.java util/ResolverUtils.java util/SearchUtils.java
//SOURCES util/SearchResult.java util/SyncStats.java util/Version.java
@@ -16,6 +17,13 @@
import java.util.stream.Collectors;
import org.codejive.jpm.util.SyncStats;
import org.codejive.jpm.util.Version;
+import org.jline.consoleui.prompt.ConsolePrompt;
+import org.jline.consoleui.prompt.ListResult;
+import org.jline.consoleui.prompt.PromptResultItemIF;
+import org.jline.consoleui.prompt.builder.ListPromptBuilder;
+import org.jline.consoleui.prompt.builder.PromptBuilder;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
@@ -74,6 +82,12 @@ static class Sync implements Callable {
@Mixin QuietMixin quietMixin;
@Mixin CopyMixin copyMixin;
+ @Option(
+ names = {"-i", "--interactive"},
+ description = "Interactively search and select artifacts to install",
+ defaultValue = "false")
+ private boolean interactive;
+
@Option(
names = {"-m", "--max"},
description = "Maximum number of results to return",
@@ -82,22 +96,135 @@ static class Sync implements Callable {
@Parameters(
paramLabel = "artifactPattern",
- description = "Partial or full artifact name to search for.")
+ description = "Partial or full artifact name to search for.",
+ defaultValue = "")
private String artifactPattern;
@Override
public Integer call() throws Exception {
- String[] artifactNames =
- Jpm.builder()
- .directory(copyMixin.directory)
- .noLinks(copyMixin.noLinks)
- .build()
- .search(artifactPattern, Math.min(max, 200));
- if (artifactNames.length > 0) {
- Arrays.stream(artifactNames).forEach(System.out::println);
+ if (interactive || artifactPattern == null || artifactPattern.isEmpty()) {
+ try (Terminal terminal = TerminalBuilder.builder().build()) {
+ while (true) {
+ ConsolePrompt prompt = new ConsolePrompt(terminal);
+ if (artifactPattern == null || artifactPattern.isEmpty()) {
+ artifactPattern = askString(prompt, "Search for:");
+ }
+ String[] artifactNames = search(artifactPattern);
+ PromptBuilder promptBuilder = prompt.getPromptBuilder();
+ addSelectItem(promptBuilder, "Select artifact:", artifactNames);
+ addSelectArtifactAction(promptBuilder);
+ Map result =
+ prompt.prompt(promptBuilder.build());
+ String selectedArtifact = getSelectedId(result, "item");
+ String artifactAction = getSelectedId(result, "action");
+ if ("install".equals(artifactAction)) {
+ SyncStats stats =
+ Jpm.builder()
+ .directory(copyMixin.directory)
+ .noLinks(copyMixin.noLinks)
+ .build()
+ .install(new String[] {selectedArtifact});
+ if (!quietMixin.quiet) {
+ printStats(stats);
+ }
+ } else if ("copy".equals(artifactAction)) {
+ SyncStats stats =
+ Jpm.builder()
+ .directory(copyMixin.directory)
+ .noLinks(copyMixin.noLinks)
+ .build()
+ .copy(new String[] {selectedArtifact}, false);
+ if (!quietMixin.quiet) {
+ printStats(stats);
+ }
+ } else if ("version".equals(artifactAction)) {
+ artifactPattern = selectedArtifact;
+ continue;
+ } else { // quit
+ break;
+ }
+ String finalAction = selectFinalAction(prompt);
+ if ("quit".equals(finalAction)) {
+ break;
+ }
+ artifactPattern = null;
+ }
+ }
+ } else {
+ String[] artifactNames = search(artifactPattern);
+ if (artifactNames.length > 0) {
+ Arrays.stream(artifactNames).forEach(System.out::println);
+ }
}
return 0;
}
+
+ String[] search(String artifactPattern) throws IOException {
+ return Jpm.builder()
+ .directory(copyMixin.directory)
+ .noLinks(copyMixin.noLinks)
+ .build()
+ .search(artifactPattern, Math.min(max, 200));
+ }
+
+ String askString(ConsolePrompt prompt, String message) throws IOException {
+ PromptBuilder promptBuilder = prompt.getPromptBuilder();
+ promptBuilder.createInputPrompt().name("input").message(message).addPrompt();
+ Map result = prompt.prompt(promptBuilder.build());
+ return result.get("input").getResult();
+ }
+
+ void addSelectItem(PromptBuilder promptBuilder, String message, String[] items)
+ throws IOException {
+ ListPromptBuilder artifactsList =
+ promptBuilder.createListPrompt().name("item").message(message).pageSize(10);
+ for (String artifactName : items) {
+ artifactsList.newItem(artifactName).text(artifactName).add();
+ }
+ artifactsList.addPrompt();
+ }
+
+ void addSelectArtifactAction(PromptBuilder promptBuilder) throws IOException {
+ promptBuilder
+ .createListPrompt()
+ .name("action")
+ .message("What to do:")
+ .newItem("install")
+ .text("Install artifact")
+ .add()
+ .newItem("copy")
+ .text("Copy artifact")
+ .add()
+ .newItem("version")
+ .text("Select different version")
+ .add()
+ .newItem("quit")
+ .text("Quit")
+ .add()
+ .addPrompt();
+ }
+
+ String selectFinalAction(ConsolePrompt prompt) throws IOException {
+ PromptBuilder promptBuilder = prompt.getPromptBuilder();
+ promptBuilder
+ .createListPrompt()
+ .name("action")
+ .message("What to do:")
+ .newItem("again")
+ .text("Search again")
+ .add()
+ .newItem("quit")
+ .text("Quit")
+ .add()
+ .addPrompt();
+ Map result = prompt.prompt(promptBuilder.build());
+ return getSelectedId(result, "action");
+ }
+
+ private static String getSelectedId(
+ Map result, String itemName) {
+ return ((ListResult) result.get(itemName)).getSelectedId();
+ }
}
@Command(