From c46b053fa21f9c642eb0b1fcc1cfbd84ec5e9ab7 Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Thu, 12 Dec 2024 11:59:27 +0100 Subject: [PATCH] Dynamic console-ui prompt improvements, see #1051 This adds an easier way to create complex dynamic prompts --- .../jline/consoleui/prompt/ConsolePrompt.java | 51 ++++++++++++- .../consoleui/examples/BasicDynamic.java | 74 ++++++++++--------- 2 files changed, 89 insertions(+), 36 deletions(-) diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/ConsolePrompt.java b/console-ui/src/main/java/org/jline/consoleui/prompt/ConsolePrompt.java index 558ae8a01..a6c69c126 100644 --- a/console-ui/src/main/java/org/jline/consoleui/prompt/ConsolePrompt.java +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/ConsolePrompt.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import org.jline.builtins.Styles; @@ -141,6 +142,55 @@ public Map prompt( } } + public Map prompt( + Function, List> promptableElementLists) + throws IOException { + return prompt(new ArrayList<>(), promptableElementLists); + } + + public Map prompt( + List headerIn, + Function, List> promptableElementLists) + throws IOException { + Map resultMap = new HashMap<>(); + Deque> prevLists = new ArrayDeque<>(); + Deque> prevResults = new ArrayDeque<>(); + boolean cancellable = config.cancellableFirstPrompt(); + // Get our first list of prompts + List peList = promptableElementLists.apply(new HashMap<>()); + Map peResult = new HashMap<>(); + while (peList != null) { + // Second and later prompts should always be cancellable + config.setCancellableFirstPrompt(!prevLists.isEmpty() || cancellable); + // Prompt the user + prompt(headerIn, peList, peResult); + if (peResult.isEmpty()) { + // The prompt was cancelled by the user, so let's go back to the + // previous list of prompts and its results (if any) + peList = prevLists.pollFirst(); + peResult = prevResults.pollFirst(); + if (peResult != null) { + // Remove the results of the previous prompt from the main result map + peResult.forEach((k, v) -> resultMap.remove(k)); + headerIn.remove(headerIn.size() - 1); + } + } else { + // We remember the list of prompts and their results + prevLists.push(peList); + prevResults.push(peResult); + // Add the results to the main result map + resultMap.putAll(peResult); + // And we get our next list of prompts (if any) + peList = promptableElementLists.apply(resultMap); + peResult = new HashMap<>(); + } + } + // Restore the original state of cancellable + config.setCancellableFirstPrompt(cancellable); + + return resultMap; + } + /** * Prompt a list of choices (questions). This method takes a list of promptable elements, typically * created with {@link PromptBuilder}. Each of the elements is processed and the user entries and @@ -190,7 +240,6 @@ public void prompt( continue; } else { if (config.cancellableFirstPrompt()) { - header.remove(header.size() - 1); resultMap.clear(); return; } else { diff --git a/console-ui/src/test/java/org/jline/consoleui/examples/BasicDynamic.java b/console-ui/src/test/java/org/jline/consoleui/examples/BasicDynamic.java index 0371209cd..5d82ffbd0 100644 --- a/console-ui/src/test/java/org/jline/consoleui/examples/BasicDynamic.java +++ b/console-ui/src/test/java/org/jline/consoleui/examples/BasicDynamic.java @@ -10,11 +10,11 @@ import java.io.IOError; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import org.jline.consoleui.elements.ConfirmChoice; +import org.jline.consoleui.elements.PromptableElementIF; import org.jline.consoleui.prompt.ConfirmResult; import org.jline.consoleui.prompt.ConsolePrompt; import org.jline.consoleui.prompt.PromptResultItemIF; @@ -70,41 +70,45 @@ public static void main(String[] args) { // LineReader is needed only if you are adding JLine Completers in your prompts. // If you are not using Completers you do not need to create LineReader. // + Map result; LineReader reader = LineReaderBuilder.builder().terminal(terminal).build(); - Map result1 = new HashMap<>(), - result2 = new HashMap<>(), - result3 = new HashMap<>(); - try (ConsolePrompt prompt = new ConsolePrompt(reader, terminal, config)) { - while (result2.isEmpty()) { - prompt.prompt(header, pizzaOrHamburgerPrompt(prompt).build(), result1); - if (result1.isEmpty()) { - throw new Exception("User cancelled order."); + result = prompt.prompt(header, results -> { + if (results.isEmpty()) { + // No results yet, so we start with the first list of questions + return pizzaOrHamburgerPrompt(prompt); } - while (result3.isEmpty()) { - if ("Pizza".equals(result1.get("product").getResult())) { - prompt.prompt(header, pizzaPrompt(prompt).build(), result2); - } else { - prompt.prompt(header, hamburgerPrompt(prompt).build(), result2); + // We have some results, so we know that the user chose a "product", + // so we can return the next list of questions based on that choice + if ("Pizza".equals(results.get("product").getResult())) { + // Check if the pizza questions were already answered + if (!results.containsKey("pizzatype")) { + // No, so let's return the pizza questions + return pizzaPrompt(prompt); } - if (result2.isEmpty()) { - break; + } else { + // Check if the hamburger questions were already answered + if (!results.containsKey("hamburgertype")) { + // No, so let's return the hamburger questions + return hamburgerPrompt(prompt); } - prompt.prompt(header, finalPrompt(prompt).build(), result3); } - } + // Check if the final questions were already answered + if (!results.containsKey("payment")) { + return finalPrompt(prompt); + } + return null; + }); } - - Map result = new HashMap<>(result1); - result.putAll(result2); - result.putAll(result3); System.out.println("result = " + result); - - ConfirmResult delivery = (ConfirmResult) result.get("delivery"); - if (delivery.getConfirmed() == ConfirmChoice.ConfirmationValue.YES) { - System.out.println("We will deliver the order in 5 minutes"); + if (result.isEmpty()) { + System.out.println("User cancelled order."); + } else { + ConfirmResult delivery = (ConfirmResult) result.get("delivery"); + if (delivery.getConfirmed() == ConfirmChoice.ConfirmationValue.YES) { + System.out.println("We will deliver the order in 5 minutes"); + } } - } catch (IOError e) { System.out.println("-c pressed"); } catch (Exception e) { @@ -112,7 +116,7 @@ public static void main(String[] args) { } } - static PromptBuilder pizzaOrHamburgerPrompt(ConsolePrompt prompt) { + static List pizzaOrHamburgerPrompt(ConsolePrompt prompt) { PromptBuilder promptBuilder = prompt.getPromptBuilder(); promptBuilder .createInputPrompt() @@ -132,10 +136,10 @@ static PromptBuilder pizzaOrHamburgerPrompt(ConsolePrompt prompt) { .text("Hamburger") .add() .addPrompt(); - return promptBuilder; + return promptBuilder.build(); } - static PromptBuilder pizzaPrompt(ConsolePrompt prompt) { + static List pizzaPrompt(ConsolePrompt prompt) { PromptBuilder promptBuilder = prompt.getPromptBuilder(); promptBuilder .createListPrompt() @@ -188,10 +192,10 @@ static PromptBuilder pizzaPrompt(ConsolePrompt prompt) { .checked(true) .add() .addPrompt(); - return promptBuilder; + return promptBuilder.build(); } - static PromptBuilder hamburgerPrompt(ConsolePrompt prompt) { + static List hamburgerPrompt(ConsolePrompt prompt) { PromptBuilder promptBuilder = prompt.getPromptBuilder(); promptBuilder .createListPrompt() @@ -232,10 +236,10 @@ static PromptBuilder hamburgerPrompt(ConsolePrompt prompt) { .check() .add() .addPrompt(); - return promptBuilder; + return promptBuilder.build(); } - static PromptBuilder finalPrompt(ConsolePrompt prompt) { + static List finalPrompt(ConsolePrompt prompt) { PromptBuilder promptBuilder = prompt.getPromptBuilder(); promptBuilder .createChoicePrompt() @@ -268,6 +272,6 @@ static PromptBuilder finalPrompt(ConsolePrompt prompt) { .message("Is this order for delivery?") .defaultValue(ConfirmChoice.ConfirmationValue.YES) .addPrompt(); - return promptBuilder; + return promptBuilder.build(); } }