diff --git a/console-ui/LICENSE.txt b/console-ui/LICENSE.txt new file mode 100644 index 00000000..bc955e3e --- /dev/null +++ b/console-ui/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Andreas Wegmann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/console-ui/README.md b/console-ui/README.md new file mode 100644 index 00000000..8325ccc1 --- /dev/null +++ b/console-ui/README.md @@ -0,0 +1,80 @@ +ConsoleUI logo + +# ConsoleUI + +Tiny java library that provides simple UI elements on ANSI console based terminals. ConsoleUI is inspired by +[Inquirer.js](https://github.com/SBoudrias/Inquirer.js) which is written in JavaScript. + +ConsoleUI has been initially implemented using JLine2 by Andreas Wegmann. After ConsoleUI has been upgraded to use JLine3 +it has been merged into JLine3. + +# Intention + +I was impressed by JavaScript based Yeoman which leads the user through the process of creating new projects +by querying with a simple user interface on the console. An investigation how this is done, brought +me to Inquirer.js which implements a very simple and intuitive set of controls for checkbox, list and text input. + +Because I didn't find anything comparable to this in the Java ecosystem, I decided to write `Console UI` +as a library with the same easy 'look and feel'. Some parts of the API are also comparable, but Console UI is not +a Java clone of Inquirer.js. + +# Features + + Console UI currently supports: + + - Text input with completion and GNU ReadLine compatible editing + - Checkboxes + - Lists + - Expandable Choices (multiple key based answers for a question with help and optional list navigation) + - Yes/No-Questions + +A screen recording of the basic elements demo can be fund on YouTube [console UI demo](https://youtu.be/6dB3CyOX9rU). + +# Dependencies + +Console UI uses JLine for the dirty console things. + +# Maven artefact + +ConsoleUI releases are available at Maven Central [org.jline.console-ui » console-ui](https://search.maven.org/artifact/org.jline.console-ui/console-ui) + +# Test Run + +You can get an idea how the project works by looking at `org.jline.consoleui.examples.Basic`. +You can run this by executing the following from the project root: + + ./jline-console-ui.sh + +# Usage + +*Hint: see the [how to](doc/howto.md) to get a more detailed documentation how to use ConsoleUI.* + + +Entry point to the builder classes is to create a new object of type `ConsolePrompt`. + + ConsolePrompt prompt = new ConsolePrompt(); + +From the prompt object, use the `getPromptBuilder()` method to create the builder for all subsequent UI elements +you want to use. + + PromptBuilder promptBuilder = prompt.getPromptBuilder(); + +From with this `PromptBuilder` you can access UI builder with the following methods: + +- createCheckboxPrompt() + * creates a checkbox prompt. This prompt lets the user choose any number of items of a given list. +- createChoicePrompt() + * creates a choice prompt. This prompt lets the user choose one from a given number of possible answers. +- createConfirmPromp() + * creates a confirmation prompt. This prompt lets the user answer with 'yes' or 'no' to a given question. +- createInputPrompt() + * creates an input prompt. This prompt is a classic entry line like a shell. Because of the underlying readline + implementation it offers you to provide completers (like file name completer or string completer). In addition + to his, you can define a mask character which is printed on the screen instead of the typed keys like used + for hidden password entry. +- createListPrompt() + * creates a list prompt. This prompt lets the user choose one item from a given list. + + + + diff --git a/console-ui/doc/ConsoleUI-Logo-small.png b/console-ui/doc/ConsoleUI-Logo-small.png new file mode 100644 index 00000000..2c164cba Binary files /dev/null and b/console-ui/doc/ConsoleUI-Logo-small.png differ diff --git a/console-ui/doc/ConsoleUI-Logo.png b/console-ui/doc/ConsoleUI-Logo.png new file mode 100644 index 00000000..35290d12 Binary files /dev/null and b/console-ui/doc/ConsoleUI-Logo.png differ diff --git a/console-ui/doc/ConsoleUI-Logo.svg b/console-ui/doc/ConsoleUI-Logo.svg new file mode 100644 index 00000000..b09edd16 --- /dev/null +++ b/console-ui/doc/ConsoleUI-Logo.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ? Console UI>_ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/console-ui/doc/howto.md b/console-ui/doc/howto.md new file mode 100644 index 00000000..f311343b --- /dev/null +++ b/console-ui/doc/howto.md @@ -0,0 +1,295 @@ +# ConsoleUI programming guide + +This document contains a brief introduction using ConsoleUI. + +## Introduction + +ConsoleUI is a library for prompting the user for different types of input. It provides simple UI elements on ANSI console-based terminals. ConsoleUI is inspired by [Inquirer.js](https://github.com/SBoudrias/Inquirer.js) which is written in JavaScript. + +## Features + +Console UI currently supports: + +- Text input with completion and GNU ReadLine compatible editing +- Checkboxes +- Lists +- Expandable Choices (multiple key-based answers for a question with help and optional list navigation) +- Yes/No-Questions + +## A small example + +The following code presents a simple, but complete code example to use ConsoleUI for selecting an item from a list. + +```java +package org.jline.consoleui.examples; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jline.consoleui.prompt.ConsolePrompt; +import org.jline.consoleui.prompt.PromptResultItemIF; +import org.jline.consoleui.prompt.builder.PromptBuilder; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; +import org.jline.utils.AttributedString; +import org.jline.utils.AttributedStringBuilder; + +public class SimpleExample { + + public static void main(String[] args) { + List header = new ArrayList<>(); + header.add(new AttributedStringBuilder().append("Simple list example:").toAttributedString()); + + try (Terminal terminal = TerminalBuilder.builder().build()) { + ConsolePrompt prompt = new ConsolePrompt(terminal); + PromptBuilder promptBuilder = prompt.getPromptBuilder(); + + promptBuilder + .createListPrompt() + .name("pizzatype") + .message("Which pizza do you want?") + .newItem() + .text("Margherita") + .add() // without name (name defaults to text) + .newItem("veneziana") + .text("Veneziana") + .add() + .newItem("hawai") + .text("Hawai") + .add() + .newItem("quattro") + .text("Quattro Stagioni") + .add() + .addPrompt(); + + Map result = prompt.prompt(header, promptBuilder.build()); + System.out.println("result = " + result); + } catch (IOException e) { + e.printStackTrace(); + } + } +} + +``` + +Basic steps: + +1. Create a new `ConsolePrompt` object. +2. Create a `PromptBuilder`. All user interactions can be created with the prompt builder object. +3. In this example, we create a `ListPrompt` + 1. give it a name ('pizzatype') + 2. assign a prompt message ("Which pizza do you want?") + 3. create items with and name (optional) and a text and add them to the ListPrompt + 4. to finish call `addPrompt()` to create the ListPrompt and add it to the prompt builder object. +4. calling `prompt.prompt(promptBuilder.build())` builds all the prompts (in this example only one) and enters the user interaction. After all prompts are processes, the `prompt()` method returns an object with the user input results. + +# Prompting for user input + +### Input + +The InputPrompt is a classic entry line like a shell. Because of the underlying readline implementation, it offers you to provide completers (like file name completer or string completer). In addition to his, you can define a mask character that is printed on the screen instead of the typed keys like used for hidden password entry. + +```java +promptBuilder.createInputPrompt() // #1 + .name("name") // #2 + .message("Please enter your name") // #3 + .defaultValue("John Doe") // #4 + //.mask('*') // #5 + .addCompleter(new StringsCompleter("Jim", "Jack", "John")) // #6 + .addPrompt(); +``` + +Description: + +1. With the prompt builder call `createInputPrompt()` to create a new input prompt builder +2. Set a name for the prompt. Setting a name is necessary to pick the right user input from the result object after the user interaction is finished. The resulting object is `HashMap` where each key is the name of a prompt you have created before. +3. Add a message which is printed in front of the user input. +4. (optional) By adding a default value, you offer the user just to press enter to accept the default value. The default value is printed in parentheses after the prompt. +5. (optional) If you add a masking character, each key pressed by the user echoed as masked character on the console. That's useful for password entries. +6. (optional) You can add completers (like simple string completers, file completers or more complex completers) to simplify the user input. The user can use the TAB button to complete partial input. See [jline/jline2](https://github.com/jline/jline2/tree/master/src/main/java/jline/console/completer) for details and examples. + +#### Console + +input prompt + +#### User Input + +The user can use readline compatible navigation (mainly Emacs control codes) inside the user input area. This includes CTRL-a to go to the beginning of input, CTRL-e to go to the end, and so on. + +After the input, the entered value is printed in different color right after the prompt. + +#### Result + +The result of this prompt is of type `InputResult` and has a method `getInput()` to get user input. + +### List + +The ListPrompt lets the user choose one item from a given list. If the list is bigger than the terminal height, the list is partially shown and scrolled if needed. + +```java +promptBuilder.createListPrompt() // #1 + .name("pizzatype") // #2 + .message("Which pizza do you want?") // #3 + .newItem().text("Margherita").add() // without name (name defaults to text) #4 + .newItem("veneziana").text("Veneziana").add() // #5 + .newItem("hawai").text("Hawai").add() + .newItem("quattro").text("Quattro Stagioni").add() + // .pageSize(10) #6 + // .relativePageSize(66) #7 + .addPrompt(); +``` + +Description: + +1. With the prompt builder call `createListPrompt()` to create a new list prompt builder. +2. Set a name for the prompt. +3. Add a message which is printed on top of the list. +4. Add items to the list. If you call `newItem()` without a name for the item, the text of the item is used in the result. +5. Add items with `newItem()` to give the item a name which makes it possible to use a technical key or another value instead of the printed text as a result. +6. (optional) For long lists, you can use `pageSize()` to set an absolute number of items to display (default is 10). Even if you choose a higher value than the terminal, the list will never exceed the terminal height. +7. (optional) Further you can use `relativePageSize()` to set a percentage size of the terminal as height. In this example, 66 is 66/100 or 2/3 of the terminal used for the list items. At least one line is displayed, even when you select a very low value. + +#### Console + +list_prompt + +#### User input + +The user can use up and down keys or VI like the keys `'j'` for down and `'k'` for moving the selector in front of the items to the right position. Pressing the enter button selects the item. + +After the input, the list is erased from the screen and the selected value is printed in a different color right after the prompt. + +#### Result + +The result of this prompt is of type `ListResult` and has a method `getSelectedId()` to get user input. + +### Checkbox + +The checkbox input lets the user choose any number of items of a given list. It's possible to add separators between items. Further, it's possible to disable items and display a message text, why they are disabled. Sometimes this may be useful to present a consistent user interface with all usually displayed options and an explanation of why some items are not usable here. + +```java +promptBuilder.createCheckboxPrompt() // #1 + .name("topping") // #2 + .message("Please select additional toppings:") // #3 + .newSeparator("standard toppings").add() // #4 + .newItem().name("cheese").text("Cheese").add() // #5 + .newItem("bacon").text("Bacon").add() // #6 + .newItem("onions").text("Onions").disabledText("Sorry. Out of stock.").add() // #7 + .newSeparator().text("special toppings").add() + .newItem("salami").text("Very hot salami").check().add() // #8 + .newItem("salmon").text("Smoked Salmon").add() + .newSeparator("and our speciality...").add() + .newItem("special").text("Anchovies, and olives").checked(true).add() // #9 + // .pageSize(10) #10 + // .relativePageSize(66) #11 + .addPrompt(); +``` + +Description: + +1. With the prompt builder call `createCheckboxPrompt()` to create a new checkbox prompt builder. +2. Set a name for the prompt. +3. Add a message which is printed on top of the list. +4. Use `newSeparator()` to add a separation element with a describing text. +5. Use `newItem()` like in the list prompt to add selectable elements to the checkbox prompt. +6. The name can be set directly with `newItem()` instead of using the `name()` method. +7. With `disabledText()` the item is not selectable, the text is displayed after the item. *Note:* even when the item is disabled it can be checked by default. +8. Use the `check()` method to pre-check the corresponding item. +9. For more flexibility, you can use `checked()` with a boolean value to select if the item is checked by default. +10. (optional) For long lists, you can use `pageSize()` to set an absolute number of items to display (default is 10). Even if you choose a higher value than the terminal, the list will never exceed the terminal height. +11. (optional) Further you can use `relativePageSize()` to set a percentage size of the terminal as height. In this example, 66 is 66/100 or 2/3 of the terminal used for the list items. At least one line is displayed even when you select a very low value. + +#### Console + +checkbox_prompt + +#### User input + +The user can use up and down keys or VI like the keys `'j'` for down and `'k'` for moving the selector in front of the items to the right position. Toggling selection on an item is done with the space bar. Pressing the enter button finished the input. + +After the input, the list is erased from the screen and the names of the selected values are printed in a different color right after the prompt. + +#### Result + +The result of this prompt is of type `CheckboxResult` and has a method `getSelectedIds()` to get user input which is of type`HashSet` containing the names of the selected items. + +### Expandable choice + +The choice prompt lets the user choose one from a given number of possible answers. It is used for requesting input that would be done in a graphical user interface by a message box. It's usable for choices like "yes/no/cancel". Each entry is assigned to a single keystroke to provide a quick input by pressing that key. + +By default, only the message and the possible keys are displayed which may make it difficult for new users to know what keystroke is associated with what answer. To get around this, a help mode is integrated. If the user pressed the 'h' button (for help), all possible answers with explanations are shown as a list where the user can choose from. + +```java +promptBuilder.createChoicePrompt() // #1 + .name("payment") // #2 + .message("How do you want to pay?") // #3 + .newItem().name("cash").message("Cash").key('c').asDefault().add() // #4 + .newItem("visa").message("Visa Card").key('v').add() // #5 + .newItem("master").message("Master Card").key('m').add() + .newSeparator("online payment").add() // #6 + .newItem("paypal").message("Paypal").key('p').add() + .addPrompt(); +``` + +Description: + +1. With the prompt builder call `createChoicePrompt()` to create a new choice prompt builder. +2. Set a name for the prompt. +3. Add a message which is printed on the screen. +4. Create new items with name, message, and an associated key. One of the items can be a default. +5. Create other non-default items. +6. Use `newSeparator()` to add a separation element with a describing text. + +#### Console and user input + +By default, the message is displayed and a list of all short keys for selection. The default value key is printed in upper case to indicate the default value. The letter 'h' is added to the list by default to activate the more detailed helpful list view. + +expandable_choice_prompt_1 + +If the user presses one of the choice keys, the corresponding message is displayed and can be confirmed with the enter key. + +expandable_choice_prompt_2 + +If the user presses the 'h' key, the long list is displayed. + +expandable_choice_prompt_3 + +The detailed navigation is usable like the list prompt by pressing up and down arrow keys and selecting a value with the enter key. + +#### Result + +The result of this prompt is of type `ExpandableChoiceResult` and has a method `getSelectedId()` to get user input. + +### Confirmation + +The confirmation prompt lets the user answer with 'yes' or 'no' to a given question. This is the minimalistic version of the choice prompt. + +```java +promptBuilder.createConfirmPromp() // #1 + .name("delivery") // #2 + .message("Is this pizza for delivery?") // #3 + .defaultValue(ConfirmChoice.ConfirmationValue.YES) // #4 + .addPrompt(); +``` + +Description: + +1. With the prompt builder call `createChonfirmPrompt()` to create a new confirmation prompt builder. +2. Set a name for the prompt. +3. Add a message which is printed as prompt. +4. With `defaultValue()` you can set either 'yes' or 'no' as a default. + +#### Console + +confirmation_prompt + +#### User input + +By pressing 'y' or 'n' (or 'j/n' in the german localization) the user can select between 'yes' and 'no' as an answer. By pressing the enter key the user confirms the input. + +#### Result + +The result of this prompt is of type `ConfirmResult` and has a method `getConfirmed()` to get user input of type `ConfirmChoice.ConfirmationValue` which is an enum of either 'YES' or 'NO'. + diff --git a/console-ui/doc/screenshots/checkbox_prompt.png b/console-ui/doc/screenshots/checkbox_prompt.png new file mode 100644 index 00000000..ff60b567 Binary files /dev/null and b/console-ui/doc/screenshots/checkbox_prompt.png differ diff --git a/console-ui/doc/screenshots/confirmation_prompt.png b/console-ui/doc/screenshots/confirmation_prompt.png new file mode 100644 index 00000000..be93cb4f Binary files /dev/null and b/console-ui/doc/screenshots/confirmation_prompt.png differ diff --git a/console-ui/doc/screenshots/expandable_choice_prompt_1.png b/console-ui/doc/screenshots/expandable_choice_prompt_1.png new file mode 100644 index 00000000..9a2ea745 Binary files /dev/null and b/console-ui/doc/screenshots/expandable_choice_prompt_1.png differ diff --git a/console-ui/doc/screenshots/expandable_choice_prompt_2.png b/console-ui/doc/screenshots/expandable_choice_prompt_2.png new file mode 100644 index 00000000..4897d28a Binary files /dev/null and b/console-ui/doc/screenshots/expandable_choice_prompt_2.png differ diff --git a/console-ui/doc/screenshots/expandable_choice_prompt_3.png b/console-ui/doc/screenshots/expandable_choice_prompt_3.png new file mode 100644 index 00000000..bd264e74 Binary files /dev/null and b/console-ui/doc/screenshots/expandable_choice_prompt_3.png differ diff --git a/console-ui/doc/screenshots/input_prompt.png b/console-ui/doc/screenshots/input_prompt.png new file mode 100644 index 00000000..9e964f4c Binary files /dev/null and b/console-ui/doc/screenshots/input_prompt.png differ diff --git a/console-ui/doc/screenshots/list_prompt.png b/console-ui/doc/screenshots/list_prompt.png new file mode 100644 index 00000000..88ae3857 Binary files /dev/null and b/console-ui/doc/screenshots/list_prompt.png differ diff --git a/console-ui/jline-console-ui.bat b/console-ui/jline-console-ui.bat new file mode 100644 index 00000000..3edca9fb --- /dev/null +++ b/console-ui/jline-console-ui.bat @@ -0,0 +1,64 @@ +@echo off + +set DIRNAME=%~dp0% +set ROOTDIR=%DIRNAME%\.. +set TARGETDIR=%DIRNAME%target + +rem initialization +if not exist %TARGETDIR%\lib ( + echo Build jline with maven before running the demo + goto END +) + +goto :SETUP_CLASSPATH + +: APPEND_TO_CLASSPATH +set filename=%~1 +set cp=%cp%;%TARGETDIR%\lib\%filename% +goto :EOF + +:SETUP_CLASSPATH +set cp=%TARGETDIR%\classes;%TARGETDIR%\test-classes +rem JLINE +pushd %TARGETDIR%\lib +for %%G in (jline-*.jar) do call:APPEND_TO_CLASSPATH %%G + +set "opts=%JLINE_OPTS%" +:RUN_LOOP + if "%1" == "jansi" goto :EXECUTE_JANSI + if "%1" == "jna" goto :EXECUTE_JNA + if "%1" == "debug" goto :EXECUTE_DEBUG + if "%1" == "debugs" goto :EXECUTE_DEBUGS + if "%1" == "" goto :EXECUTE_MAIN + set "opts=%opts% %~1" + shift + goto :RUN_LOOP + +:EXECUTE_JANSI + for %%G in (jansi-*.jar) do call:APPEND_TO_CLASSPATH %%G + shift + goto :RUN_LOOP + +:EXECUTE_JNA + for %%G in (jna-*.jar) do call:APPEND_TO_CLASSPATH %%G + shift + goto :RUN_LOOP + +:EXECUTE_DEBUG + set "opts=%opts% -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" + shift + goto :RUN_LOOP + +:EXECUTE_DEBUGS + set "opts=%opts% -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" + shift + goto :RUN_LOOP + +:EXECUTE_MAIN +popd + +echo Launching ConsoleUI... +echo Classpath: %cp% +java -cp %cp% %opts% org.jline.consoleui.examples.Basic + +:END \ No newline at end of file diff --git a/console-ui/jline-console-ui.sh b/console-ui/jline-console-ui.sh new file mode 100755 index 00000000..f878b4fd --- /dev/null +++ b/console-ui/jline-console-ui.sh @@ -0,0 +1,84 @@ +#!/bin/sh + +realpath() { + OURPWD=${PWD} + cd "$(dirname "${1}")" + LINK=$(readlink "$(basename "${1}")") + while [ "${LINK}" ]; do + cd "$(dirname "${LINK}")" + LINK=$(readlink "$(basename "${1}")") + done + REALPATH="${PWD}/$(basename "${1}")" + cd "${OURPWD}" + echo "${REALPATH}" +} + +REALNAME=$(realpath "$0") +DIRNAME=$(dirname "${REALNAME}") +PROGNAME=$(basename "${REALNAME}") +ROOTDIR=${DIRNAME}/.. +TARGETDIR=${DIRNAME}/target + +if [ ! -e ${TARGETDIR}/lib ] ; then + echo "Build jline with maven before running the demo" + exit +fi; + +cp=${TARGETDIR}/classes:${TARGETDIR}/test-classes +# JLINE +cp=${cp}$(find ${TARGETDIR}/lib -name "jline-*.jar" -exec printf :{} ';') + +opts="${JLINE_OPTS}" +while [ "${1}" != "" ]; do + case ${1} in + 'debug') + opts="${opts} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" + shift + ;; + 'debugs') + opts="${opts} -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" + shift + ;; + 'jansi') + cp=${cp}$(find ${TARGETDIR}/lib -name "jansi-*.jar" -exec printf :{} ';') + shift + ;; + 'jna') + cp=${cp}$(find ${TARGETDIR}/lib -name "jna-*.jar" -exec printf :{} ';') + shift + ;; + *) + opts="${opts} ${1}" + shift + ;; + esac +done + +cygwin=false +mingw=false +case "$(uname)" in + CYGWIN*) + cygwin=true + ;; + MINGW*) + mingw=true + ;; +esac +if ${cygwin}; then + cp=$(cygpath --path --windows "${cp}") + DIRNAME=$(cygpath --path --windows "${DIRNAME}") +fi + +nothing() { + # nothing to do here + a=a +} +trap 'nothing' TSTP + +echo "Launching ConsoleUI..." +echo "Classpath: $cp" +set mouse=a +java -cp $cp \ + $opts \ + org.jline.consoleui.examples.Basic + diff --git a/console-ui/pom.xml b/console-ui/pom.xml new file mode 100644 index 00000000..e3ccc644 --- /dev/null +++ b/console-ui/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + + org.jline + jline-parent + 3.25.2-SNAPSHOT + + + jline-console-ui + JLine Console UI + + + org.jline.consoleui + + + + + org.jline + jline-builtins + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + maven-dependency-plugin + + + copy + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/AbstractPromptableElement.java b/console-ui/src/main/java/org/jline/consoleui/elements/AbstractPromptableElement.java new file mode 100644 index 00000000..63779af8 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/AbstractPromptableElement.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements; + +public class AbstractPromptableElement implements PromptableElementIF { + + protected String message; + protected String name; + + public AbstractPromptableElement(String message, String name) { + this.message = message; + this.name = name; + } + + public String getMessage() { + return message; + } + + public String getName() { + return name; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/Checkbox.java b/console-ui/src/main/java/org/jline/consoleui/elements/Checkbox.java new file mode 100644 index 00000000..3a75b449 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/Checkbox.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements; + +import java.util.List; + +import org.jline.consoleui.elements.items.CheckboxItemIF; + +public class Checkbox extends AbstractPromptableElement { + + private final int pageSize; + private final PageSizeType pageSizeType; + private final List checkboxItemList; + + public Checkbox( + String message, + String name, + int pageSize, + PageSizeType pageSizeType, + List checkboxItemList) { + super(message, name); + if (pageSizeType == PageSizeType.RELATIVE && (pageSize < 1 || pageSize > 100)) + throw new IllegalArgumentException("for relative page size, the valid values are from 1 to 100"); + + this.pageSizeType = pageSizeType; + this.pageSize = pageSize; + this.checkboxItemList = checkboxItemList; + } + + public String getMessage() { + return message; + } + + public List getCheckboxItemList() { + return checkboxItemList; + } + + public int getPageSize() { + return pageSize; + } + + public PageSizeType getPageSizeType() { + return pageSizeType; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/ConfirmChoice.java b/console-ui/src/main/java/org/jline/consoleui/elements/ConfirmChoice.java new file mode 100644 index 00000000..2d879c5e --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/ConfirmChoice.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements; + +public class ConfirmChoice extends AbstractPromptableElement { + + public enum ConfirmationValue { + YES, + NO + } + + private ConfirmationValue defaultConfirmation = null; + + public ConfirmChoice(String message, String name) { + super(message, name); + } + + public ConfirmChoice(String message, String name, ConfirmationValue defaultConfirmation) { + super(message, name); + this.defaultConfirmation = defaultConfirmation; + } + + public ConfirmationValue getDefaultConfirmation() { + return defaultConfirmation; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/ExpandableChoice.java b/console-ui/src/main/java/org/jline/consoleui/elements/ExpandableChoice.java new file mode 100644 index 00000000..4efc3b53 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/ExpandableChoice.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements; + +import java.util.List; + +import org.jline.consoleui.elements.items.ChoiceItemIF; + +public class ExpandableChoice extends AbstractPromptableElement { + + private final List choiceItems; + + public ExpandableChoice(String message, String name, List choiceItems) { + super(message, name); + this.choiceItems = choiceItems; + } + + public List getChoiceItems() { + return choiceItems; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/InputValue.java b/console-ui/src/main/java/org/jline/consoleui/elements/InputValue.java new file mode 100644 index 00000000..c4fa62ac --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/InputValue.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements; + +import org.jline.reader.Completer; + +public class InputValue extends AbstractPromptableElement { + private String value; + private final String defaultValue; + private Character mask; + private Completer completer; + + public InputValue(String name, String message) { + super(message, name); + this.value = null; + this.defaultValue = null; + } + + public InputValue(String name, String message, String value, String defaultValue) { + super(message, name); + // this.value = value; + if (value != null) + throw new IllegalStateException("pre filled values for InputValue are not supported at the moment."); + this.defaultValue = defaultValue; + } + + public String getValue() { + return value; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setMask(Character mask) { + this.mask = mask; + } + + public Character getMask() { + return mask; + } + + public void setCompleter(Completer completer) { + this.completer = completer; + } + + public Completer getCompleter() { + return completer; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/ListChoice.java b/console-ui/src/main/java/org/jline/consoleui/elements/ListChoice.java new file mode 100644 index 00000000..8b637f66 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/ListChoice.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements; + +import java.util.List; + +import org.jline.consoleui.elements.items.ListItemIF; + +public class ListChoice extends AbstractPromptableElement { + + private final int pageSize; + private final PageSizeType pageSizeType; + private final List listItemList; + + public ListChoice( + String message, String name, int pageSize, PageSizeType pageSizeType, List listItemList) { + super(message, name); + + if (pageSizeType == PageSizeType.RELATIVE && (pageSize < 1 || pageSize > 100)) + throw new IllegalArgumentException("for relative page size, the valid values are from 1 to 100"); + + this.pageSizeType = pageSizeType; + this.pageSize = pageSize; + this.listItemList = listItemList; + } + + public String getMessage() { + return message; + } + + public List getListItemList() { + return listItemList; + } + + public int getPageSize() { + return pageSize; + } + + public PageSizeType getPageSizeType() { + return pageSizeType; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/PageSizeType.java b/console-ui/src/main/java/org/jline/consoleui/elements/PageSizeType.java new file mode 100644 index 00000000..63a7c972 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/PageSizeType.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements; + +public enum PageSizeType { + RELATIVE, + ABSOLUTE +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/PromptableElementIF.java b/console-ui/src/main/java/org/jline/consoleui/elements/PromptableElementIF.java new file mode 100644 index 00000000..4bb24fc1 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/PromptableElementIF.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements; + +public interface PromptableElementIF { + + String getName(); + + String getMessage(); +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/items/CheckboxItemIF.java b/console-ui/src/main/java/org/jline/consoleui/elements/items/CheckboxItemIF.java new file mode 100644 index 00000000..fe258d71 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/items/CheckboxItemIF.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements.items; + +public interface CheckboxItemIF extends ConsoleUIItemIF { + default boolean isChecked() { + return false; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/items/ChoiceItemIF.java b/console-ui/src/main/java/org/jline/consoleui/elements/items/ChoiceItemIF.java new file mode 100644 index 00000000..98454078 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/items/ChoiceItemIF.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements.items; + +public interface ChoiceItemIF extends ConsoleUIItemIF, ListItemIF { + default boolean isDefaultChoice() { + return false; + } + + default Character getKey() { + return ' '; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/items/ConsoleUIItemIF.java b/console-ui/src/main/java/org/jline/consoleui/elements/items/ConsoleUIItemIF.java new file mode 100644 index 00000000..2a737a59 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/items/ConsoleUIItemIF.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements.items; + +public interface ConsoleUIItemIF { + boolean isSelectable(); + + String getName(); + + default boolean isDisabled() { + return false; + } + + String getText(); + + default String getDisabledText() { + return ""; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/items/ListItemIF.java b/console-ui/src/main/java/org/jline/consoleui/elements/items/ListItemIF.java new file mode 100644 index 00000000..307c1eeb --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/items/ListItemIF.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements.items; + +public interface ListItemIF extends ConsoleUIItemIF {} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/CheckboxItem.java b/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/CheckboxItem.java new file mode 100644 index 00000000..418d2f2a --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/CheckboxItem.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements.items.impl; + +import org.jline.consoleui.elements.items.CheckboxItemIF; +import org.jline.consoleui.elements.items.ConsoleUIItemIF; + +public class CheckboxItem implements CheckboxItemIF, ConsoleUIItemIF { + boolean checked; + String text; + String disabledText; + String name; + + public CheckboxItem(boolean checked, String text, String disabledText, String name) { + this.checked = checked; + this.text = text; + this.disabledText = disabledText; + this.name = name; + } + + public CheckboxItem(boolean checked, String text) { + this(checked, text, null, text); + } + + public CheckboxItem(String text) { + this(false, text, null, text); + } + + public CheckboxItem(String text, String disabledText) { + this(false, text, disabledText, text); + } + + public CheckboxItem() { + this(false, null, null, null); + } + + public boolean isChecked() { + return checked; + } + + public void setChecked(boolean checked) { + this.checked = checked; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public boolean isSelectable() { + return isEnabled(); + } + + public void setDisabled() { + disabledText = "disabled"; + } + + public void setDisabled(String disabledText) { + this.disabledText = disabledText; + } + + public void setEnabled() { + disabledText = null; + } + + public boolean isDisabled() { + return disabledText != null; + } + + public String getDisabledText() { + return disabledText; + } + + public boolean isEnabled() { + return disabledText == null; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/ChoiceItem.java b/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/ChoiceItem.java new file mode 100644 index 00000000..5a9fb07b --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/ChoiceItem.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements.items.impl; + +import org.jline.consoleui.elements.items.ChoiceItemIF; + +public class ChoiceItem implements ChoiceItemIF { + private final Character key; + private final String name; + private final String message; + private final boolean defaultChoice; + + public ChoiceItem(Character key, String name, String message, boolean isDefaultChoice) { + this.key = key; + this.name = name; + this.message = message; + this.defaultChoice = isDefaultChoice; + } + + public Character getKey() { + return key; + } + + public String getName() { + return name; + } + + public String getMessage() { + return message; + } + + public String getText() { + return message; + } + + public boolean isSelectable() { + return true; + } + + public boolean isDefaultChoice() { + return defaultChoice; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/ListItem.java b/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/ListItem.java new file mode 100644 index 00000000..00030fd0 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/ListItem.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements.items.impl; + +import org.jline.consoleui.elements.items.ListItemIF; + +public class ListItem implements ListItemIF { + String text; + String name; + + public ListItem(String text, String name) { + this.text = text; + if (name == null) { + this.name = text; + } else { + this.name = name; + } + } + + public ListItem(String text) { + this(text, text); + } + + public ListItem() { + this(null, null); + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getName() { + return name; + } + + public boolean isSelectable() { + return true; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/Separator.java b/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/Separator.java new file mode 100644 index 00000000..c7371c68 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/elements/items/impl/Separator.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.elements.items.impl; + +import org.jline.consoleui.elements.items.CheckboxItemIF; +import org.jline.consoleui.elements.items.ChoiceItemIF; +import org.jline.consoleui.elements.items.ListItemIF; + +public class Separator implements CheckboxItemIF, ListItemIF, ChoiceItemIF { + private String message; + + public Separator(String message) { + this.message = message; + } + + public Separator() {} + + public String getMessage() { + return message; + } + + public String getText() { + return message; + } + + public boolean isSelectable() { + return false; + } + + @Override + public String getName() { + return null; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/AbstractPrompt.java b/console-ui/src/main/java/org/jline/consoleui/prompt/AbstractPrompt.java new file mode 100644 index 00000000..fa3b69d3 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/AbstractPrompt.java @@ -0,0 +1,907 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jline.consoleui.elements.Checkbox; +import org.jline.consoleui.elements.ConfirmChoice; +import org.jline.consoleui.elements.ExpandableChoice; +import org.jline.consoleui.elements.InputValue; +import org.jline.consoleui.elements.items.CheckboxItemIF; +import org.jline.consoleui.elements.items.ChoiceItemIF; +import org.jline.consoleui.elements.items.ConsoleUIItemIF; +import org.jline.consoleui.elements.items.ListItemIF; +import org.jline.consoleui.elements.items.impl.CheckboxItem; +import org.jline.consoleui.elements.items.impl.ChoiceItem; +import org.jline.consoleui.elements.items.impl.Separator; +import org.jline.keymap.BindingReader; +import org.jline.keymap.KeyMap; +import org.jline.reader.*; +import org.jline.reader.impl.CompletionMatcherImpl; +import org.jline.reader.impl.ReaderUtils; +import org.jline.terminal.Size; +import org.jline.terminal.Terminal; +import org.jline.utils.*; + +import static org.jline.keymap.KeyMap.*; + +/** + * Classes for all prompt implementations. + */ +public abstract class AbstractPrompt { + protected final Terminal terminal; + protected final BindingReader bindingReader; + private final List header; + private final AttributedString message; + protected final List items; + protected int firstItemRow; + private final Size size = new Size(); + protected final ConsolePrompt.UiConfig config; + private Display display; + private ListRange range = null; + + public AbstractPrompt( + Terminal terminal, List header, AttributedString message, ConsolePrompt.UiConfig cfg) { + this(terminal, header, message, new ArrayList<>(), cfg); + } + + public AbstractPrompt( + Terminal terminal, + List header, + AttributedString message, + List items, + ConsolePrompt.UiConfig cfg) { + this.terminal = terminal; + this.bindingReader = new BindingReader(terminal.reader()); + this.header = header; + this.message = message; + this.items = items; + this.firstItemRow = header.size() + 1; + this.config = cfg; + } + + protected void resetHeader() { + this.firstItemRow = header.size() + 1; + } + + protected void resetDisplay() { + display = new Display(terminal, true); + size.copy(terminal.getSize()); + display.clear(); + display.reset(); + } + + protected void refreshDisplay(int row) { + refreshDisplay(row, 0, null, false); + } + + protected void refreshDisplay(int row, Set selected) { + display.resize(size.getRows(), size.getColumns()); + display.reset(); + display.update( + displayLines(row, selected), + size.cursorPos(Math.min(size.getRows() - 1, firstItemRow + items.size()), 0)); + } + + protected void refreshDisplay(int row, int column, String buffer, boolean newline) { + display.resize(size.getRows(), size.getColumns()); + AttributedStringBuilder asb = new AttributedStringBuilder(); + int crow = column == 0 ? Math.min(size.getRows() - 1, firstItemRow + items.size()) : row; + if (buffer != null) { + if (newline && !buffer.isEmpty()) { + asb.style(config.style(".pr")).append(">> "); + } + asb.style(AttributedStyle.DEFAULT).append(buffer); + } + display.update(displayLines(row, asb.toAttributedString(), newline), size.cursorPos(crow, column)); + } + + protected void refreshDisplay( + int buffRow, int buffCol, String buffer, int candRow, int candCol, List candidates) { + display.resize(size.getRows(), size.getColumns()); + AttributedStringBuilder asb = new AttributedStringBuilder(); + if (buffer != null) { + asb.style(AttributedStyle.DEFAULT).append(buffer); + } + display.update( + displayLines(candRow, candCol, asb.toAttributedString(), candidates), size.cursorPos(buffRow, buffCol)); + } + + private int candidateStartPosition(int candidatesColumn, String buffer, List cands) { + List values = cands.stream() + .map(c -> AttributedString.stripAnsi(c.displ())) + .filter(c -> !c.matches("\\w+") && c.length() > 1) + .collect(Collectors.toList()); + Set notDelimiters = new HashSet<>(); + values.forEach(v -> v.substring(0, v.length() - 1) + .chars() + .filter(c -> !Character.isDigit(c) && !Character.isAlphabetic(c)) + .forEach(c -> notDelimiters.add(Character.toString((char) c)))); + int out = candidatesColumn; + for (int i = buffer.length(); i > 0; i--) { + if (buffer.substring(0, i).matches(".*\\W") && !notDelimiters.contains(buffer.substring(i - 1, i))) { + out += i; + break; + } + } + return out; + } + + private List displayLines( + int cursorRow, int candidatesColumn, AttributedString buffer, List candidates) { + computeListRange(cursorRow, candidates.size()); + List out = new ArrayList<>(); + for (int i = range.headerStart; i < header.size(); i++) { + out.add(header.get(i)); + } + AttributedStringBuilder asb = new AttributedStringBuilder(); + asb.append(message); + asb.append(buffer); + out.add(asb.toAttributedString()); + int listStart; + if (cursorRow - firstItemRow >= 0) { + String dc = candidates.get(cursorRow - firstItemRow).displ(); + listStart = candidatesColumn + + buffer.columnLength() + - display.wcwidth(dc) + + (AttributedString.stripAnsi(dc).endsWith("*") ? 1 : 0); + } else { + listStart = candidateStartPosition(candidatesColumn, buffer.toString(), candidates); + } + int width = Math.max( + candidates.stream() + .map(Candidate::displ) + .mapToInt(display::wcwidth) + .max() + .orElse(20), + 20); + for (int i = range.first; i < range.last - 1; i++) { + Candidate c = candidates.get(i); + asb = new AttributedStringBuilder(); + AttributedStringBuilder tmp = new AttributedStringBuilder(); + tmp.ansiAppend(c.displ()); + asb.style(tmp.styleAt(0)); + if (i + firstItemRow == cursorRow) { + asb.style(new AttributedStyle().inverse()); + } + asb.append(AttributedString.stripAnsi(c.displ())); + int cl = asb.columnLength(); + for (int k = cl; k < width; k++) { + asb.append(" "); + } + AttributedStringBuilder asb2 = new AttributedStringBuilder(); + asb2.tabs(listStart); + asb2.append("\t"); + asb2.style(config.style(".cb")); + asb2.append(asb).append(" "); + out.add(asb2.toAttributedString()); + } + addFooter(out, candidates.size()); + return out; + } + + private List displayLines(int cursorRow, Set selected) { + computeListRange(cursorRow, items.size()); + List out = new ArrayList<>(); + for (int i = range.headerStart; i < header.size(); i++) { + out.add(header.get(i)); + } + AttributedStringBuilder asb = new AttributedStringBuilder(); + asb.append(message); + out.add(asb.toAttributedString()); + for (int i = range.first; i < range.last - 1; i++) { + ConsoleUIItemIF s = items.get(i); + asb = new AttributedStringBuilder(); + if (s.isSelectable()) { + asb = i + firstItemRow == cursorRow + ? asb.append(config.indicator()) + .style(AttributedStyle.DEFAULT) + .append(" ") + : fillIndicatorSpace(asb).append(" "); + asb = selected.contains(s.getName()) + ? asb.append(config.checkedBox()) + : asb.append(config.uncheckedBox()); + } else if (s instanceof CheckboxItem) { + fillIndicatorSpace(asb); + asb.append(" "); + if (s.isDisabled()) { + asb.append(config.unavailable()); + } else { + fillCheckboxSpace(asb); + } + } + asb.append(s.getText()).toAttributedString(); + if (s.isDisabled()) { + asb.append(" (").append(s.getDisabledText()).append(")"); + } + out.add(asb.toAttributedString()); + } + addFooter(out, items.size()); // footer is necessary for making the long item list scroll correctly + return out; + } + + private AttributedStringBuilder fillIndicatorSpace(AttributedStringBuilder asb) { + for (int i = 0; i < config.indicator().length(); i++) { + asb.append(" "); + } + return asb; + } + + private void fillCheckboxSpace(AttributedStringBuilder asb) { + for (int i = 0; i < config.checkedBox().length(); i++) { + asb.append(" "); + } + } + + private static class ListRange { + final int first; + final int last; + final int headerStart; + + public ListRange(int headerStart, int first, int last) { + this.headerStart = headerStart; + this.first = first; + this.last = last; + } + } + + private void computeListRange(int cursorRow, int itemsSize) { + if (range != null && range.first <= cursorRow - firstItemRow && range.last - 1 > cursorRow - firstItemRow) { + return; + } + range = new ListRange(0, 0, itemsSize + 1); + if (size.getRows() < header.size() + itemsSize + 1) { + int itemId = cursorRow - firstItemRow; + int headerStart = header.size() + 1 > 10 ? header.size() - 9 : 0; + firstItemRow = firstItemRow - headerStart; + int forList = size.getRows() - header.size() + headerStart - 1; + if (itemId < forList - 1) { + range = new ListRange(headerStart, 0, forList); + } else { + range = new ListRange(headerStart, itemId - forList + 2, itemId + 2); + } + } + } + + private void addFooter(List lines, int itemsSize) { + if (size.getRows() < header.size() + itemsSize + 1) { + AttributedStringBuilder asb = new AttributedStringBuilder(); + lines.add(asb.append(".").toAttributedString()); + } + } + + private List displayLines(int cursorRow, AttributedString buffer, boolean newline) { + computeListRange(cursorRow, items.size()); + List out = new ArrayList<>(); + for (int i = range.headerStart; i < header.size(); i++) { + out.add(header.get(i)); + } + AttributedStringBuilder asb = new AttributedStringBuilder(); + asb.append(message); + if (buffer != null && !newline) { + asb.append(buffer); + } + out.add(asb.toAttributedString()); + if (buffer != null && newline) { + asb = new AttributedStringBuilder(); + asb.append(buffer); + out.add(asb.toAttributedString()); + } + for (int i = range.first; i < range.last - 1; i++) { + ConsoleUIItemIF s = items.get(i); + asb = new AttributedStringBuilder(); + String key = s instanceof ChoiceItem ? ((ChoiceItem) s).getKey() + " - " : ""; + if (i + firstItemRow == cursorRow) { + out.add(asb.append(config.indicator()) + .style(config.style(".se")) + .append(" ") + .append(key) + .append(s.getText()) + .toAttributedString()); + } else if (!(s instanceof Separator)) { + fillIndicatorSpace(asb); + out.add(asb.append(" ").append(key).append(s.getText()).toAttributedString()); + } else { + out.add(asb.append(s.getText()).toAttributedString()); + } + } + addFooter(out, items.size()); + return out; + } + + protected static class ExpandableChoicePrompt extends AbstractPrompt { + private enum Operation { + INSERT, + EXIT + } + + private final int startColumn; + private final List items; + private final ConsolePrompt.UiConfig config; + + private ExpandableChoicePrompt( + Terminal terminal, + List header, + AttributedString message, + ExpandableChoice expandableChoice, + ConsolePrompt.UiConfig cfg) { + super(terminal, header, message, cfg); + startColumn = message.columnLength(); + items = expandableChoice.getChoiceItems(); + config = cfg; + } + + public static ExpandableChoicePrompt getPrompt( + Terminal terminal, + List header, + AttributedString message, + ExpandableChoice expandableChoice, + ConsolePrompt.UiConfig cfg) { + return new ExpandableChoicePrompt(terminal, header, message, expandableChoice, cfg); + } + + private void bindKeys(KeyMap map) { + for (char i = 32; i < KEYMAP_LENGTH; i++) { + map.bind(Operation.INSERT, Character.toString(i)); + } + map.bind(Operation.EXIT, "\r"); + } + + public ExpandableChoiceResult execute() { + resetDisplay(); + int row = firstItemRow - 1; + KeyMap keyMap = new KeyMap<>(); + bindKeys(keyMap); + StringBuilder buffer = new StringBuilder(); + String selectedId = null; + boolean expandChoiceList = false; + for (ChoiceItemIF cu : items) { + if (cu.isSelectable() && cu.isDefaultChoice()) { + selectedId = cu.getName(); + break; + } + } + while (true) { + refreshDisplay(row, startColumn, buffer.toString(), true); + Operation op = bindingReader.readBinding(keyMap); + buffer = new StringBuilder(); + switch (op) { + case INSERT: + String ch = bindingReader.getLastBinding(); + if (ch.equals("h")) { + expandChoiceList = true; + buffer.append(config.resourceBundle().getString("help.list.all.options")); + } else { + selectedId = null; + expandChoiceList = false; + boolean found = false; + for (ChoiceItemIF cu : items) { + if (cu.isSelectable() && cu.getKey().toString().equals(ch)) { + selectedId = cu.getName(); + buffer.append(selectedId); + found = true; + break; + } + } + if (!found) { + buffer.append(config.resourceBundle().getString("please.enter.a.valid.command")); + } + } + break; + case EXIT: + if (selectedId == null || expandChoiceList) { + if (expandChoiceList) { + throw new ExpandableChoiceException(); + } + break; + } + return new ExpandableChoiceResult(selectedId); + } + } + } + } + + @SuppressWarnings("serial") + protected static class ExpandableChoiceException extends RuntimeException {} + + protected static class ConfirmPrompt extends AbstractPrompt { + private enum Operation { + NO, + YES, + EXIT + } + + private final int startColumn; + private final ConfirmChoice.ConfirmationValue defaultValue; + private final ConsolePrompt.UiConfig config; + + private ConfirmPrompt( + Terminal terminal, + List header, + AttributedString message, + ConfirmChoice confirmChoice, + ConsolePrompt.UiConfig cfg) { + super(terminal, header, message, cfg); + startColumn = message.columnLength(); + defaultValue = confirmChoice.getDefaultConfirmation(); + config = cfg; + } + + public static ConfirmPrompt getPrompt( + Terminal terminal, + List header, + AttributedString message, + ConfirmChoice confirmChoice, + ConsolePrompt.UiConfig cfg) { + return new ConfirmPrompt(terminal, header, message, confirmChoice, cfg); + } + + private void bindKeys(KeyMap map) { + String yes = config.resourceBundle().getString("confirmation_yes_key"); + String no = config.resourceBundle().getString("confirmation_no_key"); + map.bind(Operation.YES, yes, yes.toUpperCase()); + map.bind(Operation.NO, no, no.toUpperCase()); + map.bind(Operation.EXIT, "\r"); + } + + public ConfirmResult execute() { + resetDisplay(); + int row = firstItemRow - 1; + int column = startColumn; + KeyMap keyMap = new KeyMap<>(); + bindKeys(keyMap); + StringBuilder buffer = new StringBuilder(); + ConfirmChoice.ConfirmationValue confirm = defaultValue; + while (true) { + refreshDisplay(row, column, buffer.toString(), false); + Operation op = bindingReader.readBinding(keyMap); + buffer = new StringBuilder(); + switch (op) { + case YES: + buffer.append(config.resourceBundle().getString("confirmation_yes_answer")); + confirm = ConfirmChoice.ConfirmationValue.YES; + column = startColumn + 3; + break; + case NO: + buffer.append(config.resourceBundle().getString("confirmation_no_answer")); + confirm = ConfirmChoice.ConfirmationValue.NO; + column = startColumn + 2; + break; + case EXIT: + if (confirm == null) { + break; + } + return new ConfirmResult(confirm); + } + } + } + } + + protected static class InputValuePrompt extends AbstractPrompt { + private enum Operation { + INSERT, + BACKSPACE, + DELETE, + RIGHT, + LEFT, + BEGINNING_OF_LINE, + END_OF_LINE, + SELECT_CANDIDATE, + EXIT + } + + private enum SelectOp { + FORWARD_ONE_LINE, + BACKWARD_ONE_LINE, + EXIT + } + + private final int startColumn; + private final String defaultValue; + private final Character mask; + private final LineReader reader; + private final Completer completer; + + private InputValuePrompt( + LineReader reader, + Terminal terminal, + List header, + AttributedString message, + InputValue inputValue, + ConsolePrompt.UiConfig cfg) { + super(terminal, header, message, cfg); + this.reader = reader; + defaultValue = inputValue.getDefaultValue(); + startColumn = message.columnLength(); + mask = inputValue.getMask(); + this.completer = inputValue.getCompleter(); + } + + public static InputValuePrompt getPrompt( + LineReader reader, + Terminal terminal, + List header, + AttributedString message, + InputValue inputValue, + ConsolePrompt.UiConfig cfg) { + return new InputValuePrompt(reader, terminal, header, message, inputValue, cfg); + } + + private void bindKeys(KeyMap map) { + map.setUnicode(Operation.INSERT); + for (char i = 32; i < KEYMAP_LENGTH; i++) { + map.bind(Operation.INSERT, Character.toString(i)); + } + map.bind(Operation.BACKSPACE, del()); + map.bind(Operation.DELETE, ctrl('D'), key(terminal, InfoCmp.Capability.key_dc)); + map.bind(Operation.BACKSPACE, ctrl('H')); + map.bind(Operation.EXIT, "\r"); + map.bind(Operation.RIGHT, key(terminal, InfoCmp.Capability.key_right)); + map.bind(Operation.LEFT, key(terminal, InfoCmp.Capability.key_left)); + map.bind(Operation.BEGINNING_OF_LINE, ctrl('A'), key(terminal, InfoCmp.Capability.key_home)); + map.bind(Operation.END_OF_LINE, ctrl('E'), key(terminal, InfoCmp.Capability.key_end)); + map.bind(Operation.RIGHT, ctrl('F')); + map.bind(Operation.LEFT, ctrl('B')); + map.bind(Operation.SELECT_CANDIDATE, "\t"); + } + + private void bindSelectKeys(KeyMap map) { + map.bind(SelectOp.FORWARD_ONE_LINE, "\t", "e", ctrl('E'), key(terminal, InfoCmp.Capability.key_down)); + map.bind(SelectOp.BACKWARD_ONE_LINE, "y", ctrl('Y'), key(terminal, InfoCmp.Capability.key_up)); + map.bind(SelectOp.EXIT, "\r"); + } + + public InputResult execute() { + resetDisplay(); + int row = firstItemRow - 1; + int column = startColumn; + List matches = new ArrayList<>(); + KeyMap keyMap = new KeyMap<>(); + bindKeys(keyMap); + StringBuilder buffer = new StringBuilder(); + CompletionMatcher completionMatcher = new CompletionMatcherImpl(); + boolean tabCompletion = completer != null && reader != null; + while (true) { + boolean displayCandidates = true; + if (tabCompletion) { + List possible = new ArrayList<>(); + CompletingWord completingWord = new CompletingWord(buffer.toString()); + completer.complete(reader, completingWord, possible); + completionMatcher.compile(config.readerOptions(), false, completingWord, false, 0, null); + matches = completionMatcher.matches(possible).stream() + .sorted(Comparator.naturalOrder()) + .collect(Collectors.toList()); + if (matches.size() > ReaderUtils.getInt(reader, LineReader.MENU_LIST_MAX, 10)) { + displayCandidates = false; + } + } + refreshDisplay( + firstItemRow - 1, + column, + buffer.toString(), + row, + startColumn, + displayCandidates ? matches : new ArrayList<>()); + Operation op = bindingReader.readBinding(keyMap); + switch (op) { + case LEFT: + if (column > startColumn) { + column--; + } + break; + case RIGHT: + if (column < startColumn + buffer.length()) { + column++; + } + break; + case INSERT: + buffer.insert(column - startColumn, mask == null ? bindingReader.getLastBinding() : mask); + column++; + break; + case BACKSPACE: + if (column > startColumn) { + buffer.deleteCharAt(column - startColumn - 1); + column--; + } + break; + case DELETE: + if (column < startColumn + buffer.length() && column >= startColumn) { + buffer.deleteCharAt(column - startColumn); + } + break; + case BEGINNING_OF_LINE: + column = startColumn; + break; + case END_OF_LINE: + column = startColumn + buffer.length(); + break; + case SELECT_CANDIDATE: + if (tabCompletion && matches.size() < ReaderUtils.getInt(reader, LineReader.LIST_MAX, 50)) { + String selected = + selectCandidate(firstItemRow - 1, buffer.toString(), row + 1, startColumn, matches); + resetHeader(); + buffer.delete(0, buffer.length()); + buffer.append(selected); + column = startColumn + buffer.length(); + } + break; + case EXIT: + if (buffer.toString().isEmpty()) { + buffer.append(defaultValue); + } + return new InputResult(buffer.toString()); + } + } + } + + String selectCandidate(int buffRow, String buffer, int row, int column, List candidates) { + if (candidates.isEmpty()) { + return buffer; + } else if (candidates.size() == 1) { + return candidates.get(0).value(); + } + KeyMap keyMap = new KeyMap<>(); + bindSelectKeys(keyMap); + while (true) { + String selected = candidates.get(row - buffRow - 1).value(); + refreshDisplay(buffRow, column + selected.length(), selected, row, column, candidates); + SelectOp op = bindingReader.readBinding(keyMap); + switch (op) { + case FORWARD_ONE_LINE: + if (row < buffRow + candidates.size()) { + row++; + } else { + row = buffRow + 1; + } + break; + case BACKWARD_ONE_LINE: + if (row > buffRow + 1) { + row--; + } else { + row = buffRow + candidates.size(); + } + break; + case EXIT: + return selected; + } + } + } + } + + private static class CompletingWord implements CompletingParsedLine { + private final String word; + + public CompletingWord(String word) { + this.word = word; + } + + @Override + public CharSequence escape(CharSequence candidate, boolean complete) { + return null; + } + + @Override + public int rawWordCursor() { + return word.length(); + } + + @Override + public int rawWordLength() { + return word.length(); + } + + @Override + public String word() { + return word; + } + + @Override + public int wordCursor() { + return word.length(); + } + + @Override + public int wordIndex() { + return 0; + } + + @Override + public List words() { + return new ArrayList<>(Collections.singletonList(word)); + } + + @Override + public String line() { + return word; + } + + @Override + public int cursor() { + return word.length(); + } + } + + private static int nextRow(int row, int firstItemRow, List items) { + int itemsSize = items.size(); + int next; + for (next = row + 1; + next - firstItemRow < itemsSize + && !items.get(next - firstItemRow).isSelectable(); + next++) {} + if (next - firstItemRow >= itemsSize) { + for (next = firstItemRow; + next - firstItemRow < itemsSize + && !items.get(next - firstItemRow).isSelectable(); + next++) {} + } + return next; + } + + private static int prevRow(int row, int firstItemRow, List items) { + int itemsSize = items.size(); + int prev; + for (prev = row - 1; + prev - firstItemRow >= 0 && !items.get(prev - firstItemRow).isSelectable(); + prev--) {} + if (prev - firstItemRow < 0) { + for (prev = firstItemRow + itemsSize - 1; + prev - firstItemRow >= 0 && !items.get(prev - firstItemRow).isSelectable(); + prev--) {} + } + return prev; + } + + protected static class ListChoicePrompt extends AbstractPrompt { + private enum Operation { + FORWARD_ONE_LINE, + BACKWARD_ONE_LINE, + INSERT, + EXIT + } + + private final List items; + + private ListChoicePrompt( + Terminal terminal, + List header, + AttributedString message, + List listItems, + ConsolePrompt.UiConfig cfg) { + super(terminal, header, message, listItems, cfg); + items = listItems; + } + + public static ListChoicePrompt getPrompt( + Terminal terminal, + List header, + AttributedString message, + List listItems, + ConsolePrompt.UiConfig cfg) { + return new ListChoicePrompt<>(terminal, header, message, listItems, cfg); + } + + private void bindKeys(KeyMap map) { + for (char i = 32; i < KEYMAP_LENGTH; i++) { + map.bind(Operation.INSERT, Character.toString(i)); + } + map.bind(Operation.FORWARD_ONE_LINE, "e", ctrl('E'), key(terminal, InfoCmp.Capability.key_down)); + map.bind(Operation.BACKWARD_ONE_LINE, "y", ctrl('Y'), key(terminal, InfoCmp.Capability.key_up)); + map.bind(Operation.EXIT, "\r"); + } + + public ListResult execute() { + resetDisplay(); + int selectRow = nextRow(firstItemRow - 1, firstItemRow, items); + KeyMap keyMap = new KeyMap<>(); + bindKeys(keyMap); + while (true) { + refreshDisplay(selectRow); + Operation op = bindingReader.readBinding(keyMap); + switch (op) { + case FORWARD_ONE_LINE: + selectRow = nextRow(selectRow, firstItemRow, items); + break; + case BACKWARD_ONE_LINE: + selectRow = prevRow(selectRow, firstItemRow, items); + break; + case INSERT: + String ch = bindingReader.getLastBinding(); + int id = 0; + for (ListItemIF cu : items) { + if (cu instanceof ChoiceItem) { + ChoiceItem ci = (ChoiceItem) cu; + if (ci.isSelectable() && ci.getKey().toString().equals(ch)) { + selectRow = firstItemRow + id; + break; + } + } + id++; + } + break; + case EXIT: + T listItem = items.get(selectRow - firstItemRow); + return new ListResult(listItem.getName()); + } + } + } + } + + protected static class CheckboxPrompt extends AbstractPrompt { + private enum Operation { + FORWARD_ONE_LINE, + BACKWARD_ONE_LINE, + TOGGLE, + EXIT + } + + private final List items; + + private CheckboxPrompt( + Terminal terminal, + List header, + AttributedString message, + Checkbox checkbox, + ConsolePrompt.UiConfig cfg) { + super(terminal, header, message, checkbox.getCheckboxItemList(), cfg); + items = checkbox.getCheckboxItemList(); + } + + public static CheckboxPrompt getPrompt( + Terminal terminal, + List header, + AttributedString message, + Checkbox checkbox, + ConsolePrompt.UiConfig cfg) { + return new CheckboxPrompt(terminal, header, message, checkbox, cfg); + } + + private void bindKeys(KeyMap map) { + map.bind(Operation.FORWARD_ONE_LINE, "e", ctrl('E'), key(terminal, InfoCmp.Capability.key_down)); + map.bind(Operation.BACKWARD_ONE_LINE, "y", ctrl('Y'), key(terminal, InfoCmp.Capability.key_up)); + map.bind(Operation.TOGGLE, " "); + map.bind(Operation.EXIT, "\r"); + } + + public CheckboxResult execute() { + resetDisplay(); + int selectRow = nextRow(firstItemRow - 1, firstItemRow, items); + Set selected = items.stream() + .filter(CheckboxItemIF::isChecked) + .flatMap(it -> Stream.of(it.getName())) + .collect(Collectors.toSet()); + KeyMap keyMap = new KeyMap<>(); + bindKeys(keyMap); + while (true) { + refreshDisplay(selectRow, selected); + Operation op = bindingReader.readBinding(keyMap); + switch (op) { + case FORWARD_ONE_LINE: + selectRow = nextRow(selectRow, firstItemRow, items); + break; + case BACKWARD_ONE_LINE: + selectRow = prevRow(selectRow, firstItemRow, items); + break; + case TOGGLE: + if (selected.contains( + items.get(selectRow - firstItemRow).getName())) { + selected.remove(items.get(selectRow - firstItemRow).getName()); + } else { + selected.add(items.get(selectRow - firstItemRow).getName()); + } + break; + case EXIT: + return new CheckboxResult(selected); + } + } + } + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/CheckboxResult.java b/console-ui/src/main/java/org/jline/consoleui/prompt/CheckboxResult.java new file mode 100644 index 00000000..23938b87 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/CheckboxResult.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt; + +import java.util.Set; + +/** + * Result of a checkbox choice. CheckboxResult contains a {@link java.util.Set} with the + * IDs of the selected checkbox items. + */ +public class CheckboxResult implements PromptResultItemIF { + Set selectedIds; + + /** + * Default Constructor. + * @param selectedIds Selected IDs. + */ + public CheckboxResult(Set selectedIds) { + this.selectedIds = selectedIds; + } + + /** + * Returns the set with the IDs of selected checkbox items. + * + * @return set with IDs + */ + public Set getSelectedIds() { + return selectedIds; + } + + public String getResult() { + return selectedIds.toString(); + } + + @Override + public String toString() { + return "CheckboxResult{" + "selectedIds=" + selectedIds + '}'; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/ConfirmResult.java b/console-ui/src/main/java/org/jline/consoleui/prompt/ConfirmResult.java new file mode 100644 index 00000000..a15bb73e --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/ConfirmResult.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt; + +import org.jline.consoleui.elements.ConfirmChoice; + +/** + * Result of a confirmation choice. Holds a single value of 'yes' or 'no' + * from enum {@link ConfirmChoice.ConfirmationValue}. + */ +public class ConfirmResult implements PromptResultItemIF { + ConfirmChoice.ConfirmationValue confirmed; + + /** + * Default constructor. + * + * @param confirm the result value to hold. + */ + public ConfirmResult(ConfirmChoice.ConfirmationValue confirm) { + this.confirmed = confirm; + } + + /** + * Returns the confirmation value. + * @return confirmation value. + */ + public ConfirmChoice.ConfirmationValue getConfirmed() { + return confirmed; + } + + public String getResult() { + return confirmed.toString(); + } + + @Override + public String toString() { + return "ConfirmResult{" + "confirmed=" + confirmed + '}'; + } +} 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 new file mode 100644 index 00000000..f664c540 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/ConsolePrompt.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +import org.jline.builtins.Styles; +import org.jline.consoleui.elements.*; +import org.jline.consoleui.elements.items.ConsoleUIItemIF; +import org.jline.consoleui.elements.items.impl.ChoiceItem; +import org.jline.consoleui.prompt.AbstractPrompt.*; +import org.jline.consoleui.prompt.builder.PromptBuilder; +import org.jline.reader.LineReader; +import org.jline.terminal.Attributes; +import org.jline.terminal.Terminal; +import org.jline.utils.*; + +/** + * ConsolePrompt encapsulates the prompting of a list of input questions for the user. + */ +public class ConsolePrompt { + private final LineReader reader; + private final Terminal terminal; + private final UiConfig config; + + /** + * + * @param terminal the terminal. + */ + public ConsolePrompt(Terminal terminal) { + this(null, terminal, new UiConfig()); + } + /** + * + * @param terminal the terminal. + * @param config ConsolePrompt cursor pointer and checkbox configuration + */ + public ConsolePrompt(Terminal terminal, UiConfig config) { + this(null, terminal, config); + } + /** + * + * @param reader the lineReader. + * @param terminal the terminal. + * @param config ConsolePrompt cursor pointer and checkbox configuration + */ + public ConsolePrompt(LineReader reader, Terminal terminal, UiConfig config) { + this.terminal = terminal; + this.config = config; + this.reader = reader; + if (reader != null) { + Map options = new HashMap<>(); + for (LineReader.Option option : LineReader.Option.values()) { + options.put(option, reader.isSet(option)); + } + config.setReaderOptions(options); + } + } + + /** + * 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 + * answers are filled in to the result map. The result map contains the key of each promptable element + * and the user entry as an object implementing {@link PromptResultItemIF}. + * + * @param promptableElementList the list of questions / prompts to ask the user for. + * @return a map containing a result for each element of promptableElementList + * @throws IOException may be thrown by terminal + */ + public Map prompt(List promptableElementList) throws IOException { + return prompt(new ArrayList<>(), promptableElementList); + } + /** + * 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 + * answers are filled in to the result map. The result map contains the key of each promptable element + * and the user entry as an object implementing {@link PromptResultItemIF}. + * + * @param header info to be displayed before first prompt. + * @param promptableElementList the list of questions / prompts to ask the user for. + * @return a map containing a result for each element of promptableElementList + * @throws IOException may be thrown by terminal + */ + public Map prompt( + List header, List promptableElementList) throws IOException { + Attributes attributes = terminal.enterRawMode(); + try { + terminal.puts(InfoCmp.Capability.enter_ca_mode); + terminal.puts(InfoCmp.Capability.keypad_xmit); + terminal.writer().flush(); + + Map resultMap = new HashMap<>(); + + for (PromptableElementIF pe : promptableElementList) { + AttributedStringBuilder message = new AttributedStringBuilder(); + message.style(config.style(".pr")).append("? "); + message.style(config.style(".me")).append(pe.getMessage()).append(" "); + AttributedStringBuilder asb = new AttributedStringBuilder(); + asb.append(message); + asb.style(AttributedStyle.DEFAULT); + PromptResultItemIF result; + if (pe instanceof ListChoice) { + ListChoice lc = (ListChoice) pe; + result = ListChoicePrompt.getPrompt( + terminal, header, asb.toAttributedString(), lc.getListItemList(), config) + .execute(); + } else if (pe instanceof InputValue) { + InputValue ip = (InputValue) pe; + if (ip.getDefaultValue() != null) { + asb.append("(").append(ip.getDefaultValue()).append(") "); + } + result = InputValuePrompt.getPrompt(reader, terminal, header, asb.toAttributedString(), ip, config) + .execute(); + } else if (pe instanceof ExpandableChoice) { + ExpandableChoice ec = (ExpandableChoice) pe; + asb.append("("); + for (ConsoleUIItemIF item : ec.getChoiceItems()) { + if (item instanceof ChoiceItem) { + ChoiceItem ci = (ChoiceItem) item; + if (ci.isSelectable()) { + asb.append(ci.isDefaultChoice() ? Character.toUpperCase(ci.getKey()) : ci.getKey()); + } + } + } + asb.append("h) "); + try { + result = ExpandableChoicePrompt.getPrompt( + terminal, header, asb.toAttributedString(), ec, config) + .execute(); + } catch (ExpandableChoiceException e) { + result = ListChoicePrompt.getPrompt( + terminal, header, message.toAttributedString(), ec.getChoiceItems(), config) + .execute(); + } + } else if (pe instanceof Checkbox) { + Checkbox cb = (Checkbox) pe; + result = CheckboxPrompt.getPrompt(terminal, header, message.toAttributedString(), cb, config) + .execute(); + } else if (pe instanceof ConfirmChoice) { + ConfirmChoice cc = (ConfirmChoice) pe; + if (cc.getDefaultConfirmation() == null) { + asb.append(config.resourceBundle().getString("confirmation_without_default")); + } else if (cc.getDefaultConfirmation() == ConfirmChoice.ConfirmationValue.YES) { + asb.append(config.resourceBundle().getString("confirmation_yes_default")); + } else { + asb.append(config.resourceBundle().getString("confirmation_no_default")); + } + asb.append(" "); + result = ConfirmPrompt.getPrompt(terminal, header, asb.toAttributedString(), cc, config) + .execute(); + } else { + throw new IllegalArgumentException("wrong type of promptable element"); + } + String resp = result.getResult(); + if (result instanceof ConfirmResult) { + ConfirmResult cr = (ConfirmResult) result; + if (cr.getConfirmed() == ConfirmChoice.ConfirmationValue.YES) { + resp = config.resourceBundle().getString("confirmation_yes_answer"); + } else { + resp = config.resourceBundle().getString("confirmation_no_answer"); + } + } + message.style(config.style(".an")).append(resp); + header.add(message.toAttributedString()); + resultMap.put(pe.getName(), result); + } + return resultMap; + } finally { + terminal.setAttributes(attributes); + terminal.puts(InfoCmp.Capability.exit_ca_mode); + terminal.puts(InfoCmp.Capability.keypad_local); + terminal.writer().flush(); + for (AttributedString as : header) { + as.println(terminal); + } + terminal.writer().flush(); + } + } + + /** + * Creates a {@link PromptBuilder}. + * + * @return a new prompt builder object. + */ + public PromptBuilder getPromptBuilder() { + return new PromptBuilder(); + } + + /** + * ConsoleUI configuration: colors, cursor pointer and selected/unselected/unavailable boxes. + * ConsoleUI colors are configurable using UI_COLORS environment variable + */ + public static class UiConfig { + static final String DEFAULT_UI_COLORS = "cu=36:be=32:bd=37:pr=32:me=1:an=36:se=36:cb=100"; + static final String UI_COLORS = "UI_COLORS"; + private final AttributedString indicator; + private final AttributedString uncheckedBox; + private final AttributedString checkedBox; + private final AttributedString unavailable; + private final StyleResolver resolver; + private final ResourceBundle resourceBundle; + private Map readerOptions = new HashMap<>(); + + public UiConfig() { + this(null, null, null, null); + } + + public UiConfig(String indicator, String uncheckedBox, String checkedBox, String unavailable) { + String uc = System.getenv(UI_COLORS); + String uiColors = uc != null && Styles.isStylePattern(uc) ? uc : DEFAULT_UI_COLORS; + this.resolver = resolver(uiColors); + this.indicator = toAttributedString(resolver, (indicator != null ? indicator : ">"), ".cu"); + this.uncheckedBox = toAttributedString(resolver, (uncheckedBox != null ? uncheckedBox : " "), ".be"); + this.checkedBox = toAttributedString(resolver, (checkedBox != null ? checkedBox : "x "), ".be"); + this.unavailable = toAttributedString(resolver, (unavailable != null ? unavailable : "- "), ".bd"); + this.resourceBundle = ResourceBundle.getBundle("consoleui_messages"); + } + + private static AttributedString toAttributedString(StyleResolver resolver, String string, String styleKey) { + AttributedStringBuilder asb = new AttributedStringBuilder(); + asb.style(resolver.resolve(styleKey)); + asb.append(string); + return asb.toAttributedString(); + } + + public AttributedString indicator() { + return indicator; + } + + public AttributedString uncheckedBox() { + return uncheckedBox; + } + + public AttributedString checkedBox() { + return checkedBox; + } + + public AttributedString unavailable() { + return unavailable; + } + + public AttributedStyle style(String key) { + return resolver.resolve(key); + } + + public ResourceBundle resourceBundle() { + return resourceBundle; + } + + protected void setReaderOptions(Map readerOptions) { + this.readerOptions = readerOptions; + } + + public Map readerOptions() { + return readerOptions; + } + + private static StyleResolver resolver(String style) { + Map colors = Arrays.stream(style.split(":")) + .collect(Collectors.toMap( + s -> s.substring(0, s.indexOf('=')), s -> s.substring(s.indexOf('=') + 1))); + return new StyleResolver(colors::get); + } + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/ExpandableChoiceResult.java b/console-ui/src/main/java/org/jline/consoleui/prompt/ExpandableChoiceResult.java new file mode 100644 index 00000000..9491af7e --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/ExpandableChoiceResult.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt; + +/** + * Result of an expandable choice. ExpandableChoiceResult contains a String with the + * IDs of the selected item. + */ +public class ExpandableChoiceResult implements PromptResultItemIF { + String selectedId; + + /** + * Default constructor. + * + * @param selectedId the selected id + */ + public ExpandableChoiceResult(String selectedId) { + this.selectedId = selectedId; + } + + /** + * Returns the selected id. + * + * @return selected id. + */ + public String getSelectedId() { + return selectedId; + } + + public String getResult() { + return selectedId; + } + + @Override + public String toString() { + return "ExpandableChoiceResult{" + "selectedId='" + selectedId + '\'' + '}'; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/InputResult.java b/console-ui/src/main/java/org/jline/consoleui/prompt/InputResult.java new file mode 100644 index 00000000..34832628 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/InputResult.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt; + +public class InputResult implements PromptResultItemIF { + private final String input; + + public InputResult(String input) { + this.input = input; + } + + public String getInput() { + return input; + } + + public String getResult() { + return input; + } + + @Override + public String toString() { + return "InputResult{" + "input='" + input + '\'' + '}'; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/ListResult.java b/console-ui/src/main/java/org/jline/consoleui/prompt/ListResult.java new file mode 100644 index 00000000..a2609067 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/ListResult.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt; + +/** + * Result of a list choice. Holds the id of the selected item. + */ +public class ListResult implements PromptResultItemIF { + + String selectedId; + + /** + * Returns the ID of the selected item. + * + * @return id of selected item + */ + public String getSelectedId() { + return selectedId; + } + + public String getResult() { + return selectedId; + } + + /** + * Default constructor. + * + * @param selectedId id of selected item. + */ + public ListResult(String selectedId) { + this.selectedId = selectedId; + } + + @Override + public String toString() { + return "ListResult{" + "selectedId='" + selectedId + '\'' + '}'; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/PromptResultItemIF.java b/console-ui/src/main/java/org/jline/consoleui/prompt/PromptResultItemIF.java new file mode 100644 index 00000000..73da5647 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/PromptResultItemIF.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt; + +public interface PromptResultItemIF { + String getResult(); +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/builder/CheckboxItemBuilder.java b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/CheckboxItemBuilder.java new file mode 100644 index 00000000..3b0dbffe --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/CheckboxItemBuilder.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt.builder; + +import org.jline.consoleui.elements.items.CheckboxItemIF; +import org.jline.consoleui.elements.items.impl.CheckboxItem; + +public class CheckboxItemBuilder { + private final CheckboxPromptBuilder checkboxPromptBuilder; + private boolean checked; + private String name; + private String text; + private String disabledText; + + public CheckboxItemBuilder(CheckboxPromptBuilder checkboxPromptBuilder) { + this.checkboxPromptBuilder = checkboxPromptBuilder; + } + + public CheckboxItemBuilder name(String name) { + if (text == null) { + text = name; + } + this.name = name; + return this; + } + + public CheckboxItemBuilder text(String text) { + if (this.name == null) { + this.name = text; + } + this.text = text; + return this; + } + + public CheckboxPromptBuilder add() { + CheckboxItemIF item = new CheckboxItem(checked, text, disabledText, name); + checkboxPromptBuilder.addItem(item); + return checkboxPromptBuilder; + } + + public CheckboxItemBuilder disabledText(String disabledText) { + this.disabledText = disabledText; + return this; + } + + public CheckboxItemBuilder check() { + this.checked = true; + return this; + } + + public CheckboxItemBuilder checked(boolean checked) { + this.checked = checked; + return this; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/builder/CheckboxPromptBuilder.java b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/CheckboxPromptBuilder.java new file mode 100644 index 00000000..de40baaf --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/CheckboxPromptBuilder.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.jline.consoleui.elements.Checkbox; +import org.jline.consoleui.elements.PageSizeType; +import org.jline.consoleui.elements.items.CheckboxItemIF; + +public class CheckboxPromptBuilder { + private final PromptBuilder promptBuilder; + private String name; + private String message; + private int pageSize; + private PageSizeType pageSizeType; + private final List itemList; + + public CheckboxPromptBuilder(PromptBuilder promptBuilder) { + this.promptBuilder = promptBuilder; + this.pageSize = 10; + this.pageSizeType = PageSizeType.ABSOLUTE; + itemList = new ArrayList<>(); + } + + void addItem(CheckboxItemIF checkboxItem) { + itemList.add(checkboxItem); + } + + public CheckboxPromptBuilder name(String name) { + this.name = name; + if (message == null) { + message = name; + } + return this; + } + + public CheckboxPromptBuilder message(String message) { + this.message = message; + if (name == null) { + name = message; + } + return this; + } + + public CheckboxPromptBuilder pageSize(int absoluteSize) { + this.pageSize = absoluteSize; + this.pageSizeType = PageSizeType.ABSOLUTE; + return this; + } + + public CheckboxPromptBuilder relativePageSize(int relativePageSize) { + this.pageSize = relativePageSize; + this.pageSizeType = PageSizeType.RELATIVE; + return this; + } + + public CheckboxItemBuilder newItem() { + return new CheckboxItemBuilder(this); + } + + public CheckboxItemBuilder newItem(String name) { + CheckboxItemBuilder checkboxItemBuilder = new CheckboxItemBuilder(this); + return checkboxItemBuilder.name(name); + } + + public PromptBuilder addPrompt() { + Checkbox checkbox = new Checkbox(message, name, pageSize, pageSizeType, itemList); + promptBuilder.addPrompt(checkbox); + return promptBuilder; + } + + public CheckboxSeparatorBuilder newSeparator() { + return new CheckboxSeparatorBuilder(this); + } + + public CheckboxSeparatorBuilder newSeparator(String text) { + CheckboxSeparatorBuilder checkboxSeperatorBuilder = new CheckboxSeparatorBuilder(this); + return checkboxSeperatorBuilder.text(text); + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/builder/CheckboxSeparatorBuilder.java b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/CheckboxSeparatorBuilder.java new file mode 100644 index 00000000..dc1e6b05 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/CheckboxSeparatorBuilder.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt.builder; + +import org.jline.consoleui.elements.items.impl.Separator; + +public class CheckboxSeparatorBuilder { + private final CheckboxPromptBuilder promptBuilder; + private String text; + + public CheckboxSeparatorBuilder(CheckboxPromptBuilder checkboxPromptBuilder) { + this.promptBuilder = checkboxPromptBuilder; + } + + public CheckboxPromptBuilder add() { + Separator separator = new Separator(text); + promptBuilder.addItem(separator); + + return promptBuilder; + } + + public CheckboxSeparatorBuilder text(String text) { + this.text = text; + return this; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ConfirmPromptBuilder.java b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ConfirmPromptBuilder.java new file mode 100644 index 00000000..b574c652 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ConfirmPromptBuilder.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt.builder; + +import org.jline.consoleui.elements.ConfirmChoice; + +public class ConfirmPromptBuilder { + private final PromptBuilder promptBuilder; + private String name; + private String message; + private ConfirmChoice.ConfirmationValue defaultConfirmationValue; + + public ConfirmPromptBuilder(PromptBuilder promptBuilder) { + this.promptBuilder = promptBuilder; + } + + public ConfirmPromptBuilder name(String name) { + this.name = name; + if (message == null) { + message = name; + } + return this; + } + + public ConfirmPromptBuilder message(String message) { + this.message = message; + if (name == null) { + name = message; + } + return this; + } + + public ConfirmPromptBuilder defaultValue(ConfirmChoice.ConfirmationValue confirmationValue) { + this.defaultConfirmationValue = confirmationValue; + return this; + } + + public PromptBuilder addPrompt() { + promptBuilder.addPrompt(new ConfirmChoice(message, name, defaultConfirmationValue)); + return promptBuilder; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ExpandableChoiceItemBuilder.java b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ExpandableChoiceItemBuilder.java new file mode 100644 index 00000000..a053730b --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ExpandableChoiceItemBuilder.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt.builder; + +import org.jline.consoleui.elements.items.impl.ChoiceItem; + +public class ExpandableChoiceItemBuilder { + private final ExpandableChoicePromptBuilder choicePromptBuilder; + private String name; + private String message; + private Character key; + private boolean asDefault; + + public ExpandableChoiceItemBuilder(ExpandableChoicePromptBuilder choicePromptBuilder) { + this.choicePromptBuilder = choicePromptBuilder; + } + + public ExpandableChoiceItemBuilder name(String name) { + this.name = name; + return this; + } + + public ExpandableChoiceItemBuilder message(String message) { + this.message = message; + return this; + } + + public ExpandableChoiceItemBuilder key(char key) { + this.key = key; + return this; + } + + public ExpandableChoicePromptBuilder add() { + ChoiceItem choiceItem = new ChoiceItem(key, name, message, asDefault); + choicePromptBuilder.addItem(choiceItem); + return choicePromptBuilder; + } + + public ExpandableChoiceItemBuilder asDefault() { + this.asDefault = true; + return this; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ExpandableChoicePromptBuilder.java b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ExpandableChoicePromptBuilder.java new file mode 100644 index 00000000..84d7023e --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ExpandableChoicePromptBuilder.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.jline.consoleui.elements.ExpandableChoice; +import org.jline.consoleui.elements.items.ChoiceItemIF; + +public class ExpandableChoicePromptBuilder { + private final PromptBuilder promptBuilder; + private String name; + private String message; + private final List itemList; + + public ExpandableChoicePromptBuilder(PromptBuilder promptBuilder) { + this.promptBuilder = promptBuilder; + this.itemList = new ArrayList<>(); + } + + void addItem(ChoiceItemIF choiceItem) { + this.itemList.add(choiceItem); + } + + public ExpandableChoicePromptBuilder name(String name) { + this.name = name; + if (message == null) { + message = name; + } + return this; + } + + public ExpandableChoicePromptBuilder message(String message) { + this.message = message; + if (name == null) { + name = message; + } + return this; + } + + public ExpandableChoiceItemBuilder newItem() { + return new ExpandableChoiceItemBuilder(this); + } + + public ExpandableChoiceItemBuilder newItem(String name) { + ExpandableChoiceItemBuilder expandableChoiceItemBuilder = new ExpandableChoiceItemBuilder(this); + return expandableChoiceItemBuilder.name(name); + } + + public PromptBuilder addPrompt() { + ExpandableChoice expandableChoice = new ExpandableChoice(message, name, itemList); + promptBuilder.addPrompt(expandableChoice); + return promptBuilder; + } + + public ExpandableChoiceSeparatorBuilder newSeparator(String text) { + ExpandableChoiceSeparatorBuilder expandableChoiceSeparatorBuilder = new ExpandableChoiceSeparatorBuilder(this); + return expandableChoiceSeparatorBuilder.text(text); + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ExpandableChoiceSeparatorBuilder.java b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ExpandableChoiceSeparatorBuilder.java new file mode 100644 index 00000000..f147c66a --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ExpandableChoiceSeparatorBuilder.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt.builder; + +import org.jline.consoleui.elements.items.impl.Separator; + +public class ExpandableChoiceSeparatorBuilder { + private final ExpandableChoicePromptBuilder expandableChoicePromptBuilder; + private String text; + + public ExpandableChoiceSeparatorBuilder(ExpandableChoicePromptBuilder expandableChoicePromptBuilder) { + this.expandableChoicePromptBuilder = expandableChoicePromptBuilder; + } + + public ExpandableChoiceSeparatorBuilder text(String text) { + this.text = text; + return this; + } + + public ExpandableChoicePromptBuilder add() { + Separator separator = new Separator(text); + expandableChoicePromptBuilder.addItem(separator); + + return expandableChoicePromptBuilder; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/builder/InputValueBuilder.java b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/InputValueBuilder.java new file mode 100644 index 00000000..cd900031 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/InputValueBuilder.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt.builder; + +import org.jline.consoleui.elements.InputValue; +import org.jline.reader.Completer; + +public class InputValueBuilder { + private final PromptBuilder promptBuilder; + private String name; + private String defaultValue; + private String message; + private Character mask; + private Completer completer; + + public InputValueBuilder(PromptBuilder promptBuilder) { + this.promptBuilder = promptBuilder; + } + + public InputValueBuilder name(String name) { + this.name = name; + return this; + } + + public InputValueBuilder defaultValue(String defaultValue) { + this.defaultValue = defaultValue; + return this; + } + + public InputValueBuilder message(String message) { + this.message = message; + return this; + } + + public InputValueBuilder mask(char mask) { + this.mask = mask; + return this; + } + + public PromptBuilder addPrompt() { + InputValue inputValue = new InputValue(name, message, null, defaultValue); + if (mask != null) { + inputValue.setMask(mask); + } + if (completer != null) { + inputValue.setCompleter(completer); + } + promptBuilder.addPrompt(inputValue); + return promptBuilder; + } + + public InputValueBuilder addCompleter(Completer completer) { + this.completer = completer; + return this; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ListItemBuilder.java b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ListItemBuilder.java new file mode 100644 index 00000000..3be5eeca --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ListItemBuilder.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt.builder; + +import org.jline.consoleui.elements.items.impl.ListItem; + +public class ListItemBuilder { + private final ListPromptBuilder listPromptBuilder; + private String text; + private String name; + + public ListItemBuilder(ListPromptBuilder listPromptBuilder) { + this.listPromptBuilder = listPromptBuilder; + } + + public ListItemBuilder text(String text) { + this.text = text; + return this; + } + + public ListItemBuilder name(String name) { + this.name = name; + return this; + } + + public ListPromptBuilder add() { + listPromptBuilder.addItem(new ListItem(text, name)); + return listPromptBuilder; + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ListPromptBuilder.java b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ListPromptBuilder.java new file mode 100644 index 00000000..a767e88e --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/ListPromptBuilder.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.jline.consoleui.elements.ListChoice; +import org.jline.consoleui.elements.PageSizeType; +import org.jline.consoleui.elements.items.ListItemIF; +import org.jline.consoleui.elements.items.impl.ListItem; + +public class ListPromptBuilder { + private final PromptBuilder promptBuilder; + private String name; + private String message; + private int pageSize; + private PageSizeType pageSizeType; + private final List itemList = new ArrayList<>(); + + public ListPromptBuilder(PromptBuilder promptBuilder) { + this.promptBuilder = promptBuilder; + this.pageSize = 10; + this.pageSizeType = PageSizeType.ABSOLUTE; + } + + public ListPromptBuilder name(String name) { + this.name = name; + if (message != null) { + this.message = name; + } + return this; + } + + public ListPromptBuilder message(String message) { + this.message = message; + if (name == null) { + name = message; + } + return this; + } + + public ListPromptBuilder pageSize(int absoluteSize) { + this.pageSize = absoluteSize; + this.pageSizeType = PageSizeType.ABSOLUTE; + return this; + } + + public ListPromptBuilder relativePageSize(int relativePageSize) { + this.pageSize = relativePageSize; + this.pageSizeType = PageSizeType.RELATIVE; + return this; + } + + public ListItemBuilder newItem() { + return new ListItemBuilder(this); + } + + public ListItemBuilder newItem(String name) { + ListItemBuilder listItemBuilder = new ListItemBuilder(this); + return listItemBuilder.name(name).text(name); + } + + public PromptBuilder addPrompt() { + ListChoice listChoice = new ListChoice(message, name, pageSize, pageSizeType, itemList); + promptBuilder.addPrompt(listChoice); + return promptBuilder; + } + + void addItem(ListItem listItem) { + this.itemList.add(listItem); + } +} diff --git a/console-ui/src/main/java/org/jline/consoleui/prompt/builder/PromptBuilder.java b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/PromptBuilder.java new file mode 100644 index 00000000..4d3b2e55 --- /dev/null +++ b/console-ui/src/main/java/org/jline/consoleui/prompt/builder/PromptBuilder.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.jline.consoleui.elements.PromptableElementIF; + +/** + * PromptBuilder is the builder class which creates + */ +public class PromptBuilder { + List promptList = new ArrayList<>(); + + public List build() { + return promptList; + } + + public void addPrompt(PromptableElementIF promptableElement) { + promptList.add(promptableElement); + } + + public InputValueBuilder createInputPrompt() { + return new InputValueBuilder(this); + } + + public ListPromptBuilder createListPrompt() { + return new ListPromptBuilder(this); + } + + public ExpandableChoicePromptBuilder createChoicePrompt() { + return new ExpandableChoicePromptBuilder(this); + } + + public CheckboxPromptBuilder createCheckboxPrompt() { + return new CheckboxPromptBuilder(this); + } + + public ConfirmPromptBuilder createConfirmPromp() { + return new ConfirmPromptBuilder(this); + } +} diff --git a/console-ui/src/main/resources/consoleui_messages.properties b/console-ui/src/main/resources/consoleui_messages.properties new file mode 100644 index 00000000..a73f3793 --- /dev/null +++ b/console-ui/src/main/resources/consoleui_messages.properties @@ -0,0 +1,10 @@ +confirmation_without_default=(y/n) +confirmation_no_default=(y/N) +confirmation_yes_default=(Y/n) +confirmation_yes_key=y +confirmation_no_key=n + +help.list.all.options=Help, list all options +please.enter.a.valid.command=Please enter a valid command +confirmation_no_answer=no +confirmation_yes_answer=yes \ No newline at end of file diff --git a/console-ui/src/main/resources/consoleui_messages_de_DE.properties b/console-ui/src/main/resources/consoleui_messages_de_DE.properties new file mode 100644 index 00000000..1da57b1f --- /dev/null +++ b/console-ui/src/main/resources/consoleui_messages_de_DE.properties @@ -0,0 +1,9 @@ +confirmation_without_default=(j/n) +confirmation_no_default=(j/N) +confirmation_yes_default=(J/n) +confirmation_yes_key=j +confirmation_no_key=n +help.list.all.options=Hilfe. Listet alle Optionen. +please.enter.a.valid.command=Bitte ein gültiges Kürzel eingeben. +confirmation_no_answer=Nein +confirmation_yes_answer=Ja \ No newline at end of file diff --git a/console-ui/src/test/java/org/jline/consoleui/examples/Basic.java b/console-ui/src/test/java/org/jline/consoleui/examples/Basic.java new file mode 100644 index 00000000..c532b5aa --- /dev/null +++ b/console-ui/src/test/java/org/jline/consoleui/examples/Basic.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.examples; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jline.consoleui.elements.ConfirmChoice; +import org.jline.consoleui.prompt.ConfirmResult; +import org.jline.consoleui.prompt.ConsolePrompt; +import org.jline.consoleui.prompt.PromptResultItemIF; +import org.jline.consoleui.prompt.builder.PromptBuilder; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.impl.completer.StringsCompleter; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; +import org.jline.utils.AttributedString; +import org.jline.utils.AttributedStringBuilder; +import org.jline.utils.AttributedStyle; +import org.jline.utils.OSUtils; + +public class Basic { + + private static void addInHeader(List header, String text) { + addInHeader(header, AttributedStyle.DEFAULT, text); + } + + private static void addInHeader(List header, AttributedStyle style, String text) { + AttributedStringBuilder asb = new AttributedStringBuilder(); + asb.style(style).append(text); + header.add(asb.toAttributedString()); + } + + public static void main(String[] args) { + List header = new ArrayList<>(); + AttributedStyle style = new AttributedStyle(); + addInHeader(header, style.italic().foreground(2), "Hello World!"); + addInHeader( + header, "This is a demonstration of ConsoleUI java library. It provides a simple console interface"); + addInHeader( + header, + "for querying information from the user. ConsoleUI is inspired by Inquirer.js which is written"); + addInHeader(header, "in JavaScript."); + try (Terminal terminal = TerminalBuilder.builder().build()) { + ConsolePrompt.UiConfig config; + if (terminal.getType().equals(Terminal.TYPE_DUMB) + || terminal.getType().equals(Terminal.TYPE_DUMB_COLOR)) { + System.out.println(terminal.getName() + ": " + terminal.getType()); + throw new IllegalStateException("Dumb terminal detected.\nConsoleUi requires real terminal to work!\n" + + "Note: On Windows Jansi or JNA library must be included in classpath."); + } else if (OSUtils.IS_WINDOWS) { + config = new ConsolePrompt.UiConfig(">", "( )", "(x)", "( )"); + } else { + config = new ConsolePrompt.UiConfig("\u276F", "\u25EF ", "\u25C9 ", "\u25EF "); + } + // + // 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. + // + LineReader reader = LineReaderBuilder.builder().terminal(terminal).build(); + ConsolePrompt prompt = new ConsolePrompt(reader, terminal, config); + PromptBuilder promptBuilder = prompt.getPromptBuilder(); + + promptBuilder + .createInputPrompt() + .name("name") + .message("Please enter your name") + .defaultValue("John Doe") + // .mask('*') + .addCompleter( + // new Completers.FilesCompleter(() -> Paths.get(System.getProperty("user.dir")))) + new StringsCompleter("Jim", "Jack", "John", "Donald", "Dock")) + .addPrompt(); + + promptBuilder + .createListPrompt() + .name("pizzatype") + .message("Which pizza do you want?") + .newItem() + .text("Margherita") + .add() // without name (name defaults to text) + .newItem("veneziana") + .text("Veneziana") + .add() + .newItem("hawai") + .text("Hawai") + .add() + .newItem("quattro") + .text("Quattro Stagioni") + .add() + .addPrompt(); + + promptBuilder + .createCheckboxPrompt() + .name("topping") + .message("Please select additional toppings:") + .newSeparator("standard toppings") + .add() + .newItem() + .name("cheese") + .text("Cheese") + .add() + .newItem("bacon") + .text("Bacon") + .add() + .newItem("onions") + .text("Onions") + .disabledText("Sorry. Out of stock.") + .add() + .newSeparator() + .text("special toppings") + .add() + .newItem("salami") + .text("Very hot salami") + .check() + .add() + .newItem("salmon") + .text("Smoked Salmon") + .add() + .newSeparator("and our speciality...") + .add() + .newItem("special") + .text("Anchovies, and olives") + .checked(true) + .add() + .addPrompt(); + + promptBuilder + .createChoicePrompt() + .name("payment") + .message("How do you want to pay?") + .newItem() + .name("cash") + .message("Cash") + .key('c') + .asDefault() + .add() + .newItem("visa") + .message("Visa Card") + .key('v') + .add() + .newItem("master") + .message("Master Card") + .key('m') + .add() + .newSeparator("online payment") + .add() + .newItem("paypal") + .message("Paypal") + .key('p') + .add() + .addPrompt(); + + promptBuilder + .createConfirmPromp() + .name("delivery") + .message("Is this pizza for delivery?") + .defaultValue(ConfirmChoice.ConfirmationValue.YES) + .addPrompt(); + + Map result = prompt.prompt(header, promptBuilder.build()); + System.out.println("result = " + result); + + ConfirmResult delivery = (ConfirmResult) result.get("delivery"); + if (delivery.getConfirmed() == ConfirmChoice.ConfirmationValue.YES) { + System.out.println("We will deliver the pizza in 5 minutes"); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/console-ui/src/test/java/org/jline/consoleui/examples/LongList.java b/console-ui/src/test/java/org/jline/consoleui/examples/LongList.java new file mode 100644 index 00000000..92ed5a38 --- /dev/null +++ b/console-ui/src/test/java/org/jline/consoleui/examples/LongList.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.examples; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jline.consoleui.prompt.ConsolePrompt; +import org.jline.consoleui.prompt.PromptResultItemIF; +import org.jline.consoleui.prompt.builder.CheckboxPromptBuilder; +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 org.jline.utils.AttributedString; +import org.jline.utils.AttributedStringBuilder; + +public class LongList { + + public static void main(String[] args) { + List header = new ArrayList<>(); + header.add(new AttributedStringBuilder() + .append("This is a demonstration of ConsoleUI java library. It provides a simple console interface") + .toAttributedString()); + header.add(new AttributedStringBuilder() + .append("for querying information from the user. ConsoleUI is inspired by Inquirer.js which is written") + .toAttributedString()); + header.add(new AttributedStringBuilder().append("in JavaScript.").toAttributedString()); + + try (Terminal terminal = TerminalBuilder.builder().build()) { + ConsolePrompt.UiConfig config = new ConsolePrompt.UiConfig(">", "( )", "(x)", "( )"); + ConsolePrompt prompt = new ConsolePrompt(terminal, config); + PromptBuilder promptBuilder = prompt.getPromptBuilder(); + + ListPromptBuilder listPrompt = promptBuilder.createListPrompt(); + listPrompt.name("longlist").message("What's your favourite Letter?").relativePageSize(66); + + for (char letter = 'A'; letter <= 'C'; letter++) + for (char letter2 = 'A'; letter2 <= 'Z'; letter2++) + listPrompt.newItem().text("" + letter + letter2).add(); + listPrompt.addPrompt(); + + CheckboxPromptBuilder checkboxPrompt = promptBuilder.createCheckboxPrompt(); + checkboxPrompt + .name("longcheckbox") + .message("What's your favourite Letter? Select all you want...") + .relativePageSize(66); + + for (char letter = 'A'; letter <= 'C'; letter++) + for (char letter2 = 'A'; letter2 <= 'Z'; letter2++) + checkboxPrompt.newItem().text("" + letter + letter2).add(); + checkboxPrompt.addPrompt(); + + Map result = prompt.prompt(header, promptBuilder.build()); + System.out.println("result = " + result); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/console-ui/src/test/java/org/jline/consoleui/examples/SimpleExample.java b/console-ui/src/test/java/org/jline/consoleui/examples/SimpleExample.java new file mode 100644 index 00000000..6d9ef0dc --- /dev/null +++ b/console-ui/src/test/java/org/jline/consoleui/examples/SimpleExample.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.examples; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jline.consoleui.prompt.ConsolePrompt; +import org.jline.consoleui.prompt.PromptResultItemIF; +import org.jline.consoleui.prompt.builder.PromptBuilder; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; +import org.jline.utils.AttributedString; +import org.jline.utils.AttributedStringBuilder; + +public class SimpleExample { + + public static void main(String[] args) { + List header = new ArrayList<>(); + header.add(new AttributedStringBuilder().append("Simple list example:").toAttributedString()); + + try (Terminal terminal = TerminalBuilder.builder().build()) { + ConsolePrompt prompt = new ConsolePrompt(terminal); + PromptBuilder promptBuilder = prompt.getPromptBuilder(); + + promptBuilder + .createListPrompt() + .name("pizzatype") + .message("Which pizza do you want?") + .newItem() + .text("Margherita") + .add() // without name (name defaults to text) + .newItem("veneziana") + .text("Veneziana") + .add() + .newItem("hawai") + .text("Hawai") + .add() + .newItem("quattro") + .text("Quattro Stagioni") + .add() + .addPrompt(); + + Map result = prompt.prompt(header, promptBuilder.build()); + System.out.println("result = " + result); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/console-ui/src/test/java/org/jline/consoleui/prompt/CheckboxPromptTest.java b/console-ui/src/test/java/org/jline/consoleui/prompt/CheckboxPromptTest.java new file mode 100644 index 00000000..92975424 --- /dev/null +++ b/console-ui/src/test/java/org/jline/consoleui/prompt/CheckboxPromptTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt; + +import java.util.ArrayList; +import java.util.List; + +import org.jline.consoleui.elements.items.CheckboxItemIF; +import org.jline.consoleui.elements.items.impl.CheckboxItem; +import org.jline.consoleui.elements.items.impl.Separator; +import org.junit.jupiter.api.Test; + +public class CheckboxPromptTest { + @Test + public void renderSimpleList() { + List list = new ArrayList<>(); + + list.add(new CheckboxItem("One")); + list.add(new CheckboxItem(true, "Two")); + CheckboxItem three = new CheckboxItem("Three"); + three.setDisabled("not available"); + list.add(three); + list.add(new Separator("some extra items")); + list.add(new CheckboxItem("Four")); + list.add(new CheckboxItem(true, "Five")); + } +} diff --git a/console-ui/src/test/java/org/jline/consoleui/prompt/ExpandableChoicePromptTest.java b/console-ui/src/test/java/org/jline/consoleui/prompt/ExpandableChoicePromptTest.java new file mode 100644 index 00000000..7e33d8e8 --- /dev/null +++ b/console-ui/src/test/java/org/jline/consoleui/prompt/ExpandableChoicePromptTest.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt; + +import org.junit.jupiter.api.Test; + +public class ExpandableChoicePromptTest { + + @Test + public void testPrompt() throws Exception {} +} diff --git a/console-ui/src/test/java/org/jline/consoleui/prompt/PromptBuilderTest.java b/console-ui/src/test/java/org/jline/consoleui/prompt/PromptBuilderTest.java new file mode 100644 index 00000000..756ef72e --- /dev/null +++ b/console-ui/src/test/java/org/jline/consoleui/prompt/PromptBuilderTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.consoleui.prompt; + +import java.io.IOException; +import java.util.List; + +import org.jline.consoleui.elements.ConfirmChoice; +import org.jline.consoleui.elements.PromptableElementIF; +import org.jline.consoleui.prompt.builder.PromptBuilder; +import org.jline.terminal.TerminalBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class PromptBuilderTest { + + @Test + public void testBuilder() throws IOException { + ConsolePrompt prompt = new ConsolePrompt(TerminalBuilder.builder().build()); + PromptBuilder promptBuilder = prompt.getPromptBuilder(); + + promptBuilder + .createConfirmPromp() + .name("wantapizza") + .message("Do you want to order a pizza?") + .defaultValue(ConfirmChoice.ConfirmationValue.YES) + .addPrompt(); + + promptBuilder + .createInputPrompt() + .name("name") + .message("Please enter your name") + .defaultValue("John Doe") + .addPrompt(); + + promptBuilder + .createListPrompt() + .name("pizzatype") + .message("Which pizza do you want?") + .newItem() + .text("Margherita") + .add() // without name (name defaults to text) + .newItem("veneziana") + .text("Veneziana") + .add() + .newItem("hawai") + .text("Hawai") + .add() + .newItem("quattro") + .text("Quattro Stagioni") + .add() + .addPrompt(); + + promptBuilder + .createCheckboxPrompt() + .name("topping") + .message("Please select additional toppings:") + .newSeparator("standard toppings") + .add() + .newItem() + .name("cheese") + .text("Cheese") + .add() + .newItem("bacon") + .text("Bacon") + .add() + .newItem("onions") + .text("Onions") + .disabledText("Sorry. Out of stock.") + .add() + .newSeparator() + .text("special toppings") + .add() + .newItem("salami") + .text("Very hot salami") + .check() + .add() + .newItem("salmon") + .text("Smoked Salmon") + .add() + .newSeparator("and our speciality...") + .add() + .newItem("special") + .text("Anchovies, and olives") + .checked(true) + .add() + .addPrompt(); + + assertNotNull(promptBuilder); + promptBuilder + .createChoicePrompt() + .name("payment") + .message("How do you want to pay?") + .newItem() + .name("cash") + .message("Cash") + .key('c') + .asDefault() + .add() + .newItem("visa") + .message("Visa Card") + .key('v') + .add() + .newItem("master") + .message("Master Card") + .key('m') + .add() + .newSeparator("online payment") + .add() + .newItem("paypal") + .message("Paypal") + .key('p') + .add() + .addPrompt(); + + List promptableElementList = promptBuilder.build(); + + // only for test. reset the default reader to a test reader to automate the input + // promptableElementList.get(0) + + // HashMap result = prompt.prompt(promptableElementList); + + } +} diff --git a/pom.xml b/pom.xml index 8ee7e520..3fa0526b 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,7 @@ terminal-jni reader builtins + console-ui console groovy remote-ssh