-
Notifications
You must be signed in to change notification settings - Fork 215
/
ConsolePrompt.java
318 lines (295 loc) · 13.9 KB
/
ConsolePrompt.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/*
* 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;
private boolean cancellable = false;
/**
*
* @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<LineReader.Option, Boolean> options = new HashMap<>();
for (LineReader.Option option : LineReader.Option.values()) {
options.put(option, reader.isSet(option));
}
config.setReaderOptions(options);
}
}
public ConsolePrompt cancellable(boolean cancellable) {
this.cancellable = cancellable;
return this;
}
/**
* 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<String, PromptResultItemIF> prompt(List<PromptableElementIF> 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<String, PromptResultItemIF> prompt(
List<AttributedString> header, List<PromptableElementIF> 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<String, PromptResultItemIF> resultMap = new HashMap<>();
for (int i = 0; i < promptableElementList.size(); i++) {
PromptableElementIF pe = promptableElementList.get(i);
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(),
computePageSize(terminal, lc.getPageSize(), lc.getPageSizeType()),
cancellable,
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, cancellable, 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, cancellable, config)
.execute();
} catch (ExpandableChoiceException e) {
result = ListChoicePrompt.getPrompt(
terminal,
header,
message.toAttributedString(),
ec.getChoiceItems(),
10,
cancellable,
config)
.execute();
}
} else if (pe instanceof Checkbox) {
Checkbox cb = (Checkbox) pe;
result = CheckboxPrompt.getPrompt(
terminal,
header,
message.toAttributedString(),
cb.getCheckboxItemList(),
computePageSize(terminal, cb.getPageSize(), cb.getPageSizeType()),
cancellable,
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, cancellable, config)
.execute();
} else {
throw new IllegalArgumentException("wrong type of promptable element");
}
if (result == null) {
// Prompt was cancelled by the user
if (i > 0) {
// Remove last result
header.remove(header.size() - 1);
// Go back to previous prompt
i -= 2;
continue;
} else {
return null;
}
}
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();
}
}
private int computePageSize(Terminal terminal, int pageSize, PageSizeType sizeType) {
int rows = terminal.getHeight();
return sizeType == PageSizeType.ABSOLUTE ? Math.min(rows, pageSize) : (rows * pageSize) / 100;
}
/**
* 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<LineReader.Option, Boolean> 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<LineReader.Option, Boolean> readerOptions) {
this.readerOptions = readerOptions;
}
public Map<LineReader.Option, Boolean> readerOptions() {
return readerOptions;
}
private static StyleResolver resolver(String style) {
Map<String, String> 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);
}
}
}