8000 Added possibility of cancelling prompts (#1046) · jline/jline3@5283218 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5283218

Browse files
authored
Added possibility of cancelling prompts (#1046)
Cancelling a prompt by hitting the ESC key makes it go back to the previous prompt. By default, if we're already at the fist prompt we'll repeat the question until the user either answers it or forcefully quits the application. If on the other hand the `cancellableFirstPrompt` option of `UiConfig` has been set to `true`, the `prompt()` method will return `null`. Fixes #1035
1 parent fd634b5 commit 5283218

File tree

3 files changed

+352
-13
lines changed

3 files changed

+352
-13
lines changed

console-ui/src/main/java/org/jline/consoleui/prompt/AbstractPrompt.java

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public abstract class AbstractPrompt<T extends ConsoleUIItemIF> {
4848
private Display display;
4949
private ListRange range = null;
5050

51+
public static final long DEFAULT_TIMEOUT_WITH_ESC = 150L;
52+
5153
public AbstractPrompt(
5254
Terminal terminal, List<AttributedString> header, AttributedString message, ConsolePrompt.UiConfig cfg) {
5355
this(terminal, header, message, new ArrayList<>(), 0, cfg);
@@ -312,7 +314,8 @@ private List<AttributedString> displayLines(int cursorRow, AttributedString buff
312314
protected static class ExpandableChoicePrompt extends AbstractPrompt<ListItemIF> {
313315
private enum Operation {
314316
INSERT,
315-
EXIT
317+
EXIT,
318+
CANCEL
316319
}
317320

318321
private final int startColumn;
@@ -345,6 +348,8 @@ private void bindKeys(KeyMap<Operation> map) {
345348
map.bind(Operation.INSERT, Character.toString(i));
346349
}
347350
map.bind(Operation.EXIT, "\r");
351+
map.bind(Operation.CANCEL, KeyMap.esc());
352+
map.setAmbiguousTimeout(DEFAULT_TIMEOUT_WITH_ESC);
348353
}
349354

350355
public ExpandableChoiceResult execute() {
@@ -396,6 +401,8 @@ public ExpandableChoiceResult execute() {
396401
break;
397402
}
398403
return new ExpandableChoiceResult(selectedId);
404+
case CANCEL:
405+
return null;
399406
}
400407
}
401408
}
@@ -408,7 +415,8 @@ protected static class ConfirmPrompt extends AbstractPrompt<ListItemIF> {
408415
private enum Operation {
409416
NO,
410417
YES,
411-
EXIT
418+
EXIT,
419+
CANCEL
412420
}
413421

414422
private final int startColumn;
@@ -442,6 +450,8 @@ private void bindKeys(KeyMap<Operation> map) {
442450
map.bind(Operation.YES, yes, yes.toUpperCase());
443451
map.bind(Operation.NO, no, no.toUpperCase());
444452
map.bind(Operation.EXIT, "\r");
453+
map.bind(Operation.CANCEL, KeyMap.esc());
454+
map.setAmbiguousTimeout(DEFAULT_TIMEOUT_WITH_ESC);
445455
}
446456

447457
public ConfirmResult execute() {
@@ -472,6 +482,8 @@ public ConfirmResult execute() {
472482
break;
473483
}
474484
return new ConfirmResult(confirm);
485+
case CANCEL:
486+
return null;
475487
}
476488
}
477489
}
@@ -487,13 +499,15 @@ private enum Operation {
487499
BEGINNING_OF_LINE,
488500
END_OF_LINE,
489501
SELECT_CANDIDATE,
490-
EXIT
502+
EXIT,
503+
CANCEL
491504
}
492505

493506
private enum SelectOp {
494507
FORWARD_ONE_LINE,
495508
BACKWARD_ONE_LINE,
496-
EXIT
509+
EXIT,
510+
CANCEL
497511
}
498512

499513
private final int startColumn;
@@ -543,12 +557,16 @@ private void bindKeys(KeyMap<Operation> map) {
543557
map.bind(Operation.RIGHT, ctrl('F'));
544558
map.bind(Operation.LEFT, ctrl('B'));
545559
map.bind(Operation.SELECT_CANDIDATE, "\t");
560+
map.bind(Operation.CANCEL, KeyMap.esc());
561+
map.setAmbiguousTimeout(DEFAULT_TIMEOUT_WITH_ESC);
546562
}
547563

548564
private void bindSelectKeys(KeyMap<SelectOp> map) {
549565
map.bind(SelectOp.FORWARD_ONE_LINE, "\t", "e", ctrl('E'), key(terminal, InfoCmp.Capability.key_down));
550566
map.bind(SelectOp.BACKWARD_ONE_LINE, "y", ctrl('Y'), key(terminal, InfoCmp.Capability.key_up));
551567
map.bind(SelectOp.EXIT, "\r");
568+
map.bind(SelectOp.CANCEL, KeyMap.esc());
569+
map.setAmbiguousTimeout(DEFAULT_TIMEOUT_WITH_ESC);
552570
}
553571

554572
public InputResult execute() {
@@ -620,16 +638,20 @@ public InputResult execute() {
620638
String selected =
621639
selectCandidate(firstItemRow - 1, buffer.toString(), row + 1, startColumn, matches);
622640
resetHeader();
623-
buffer.delete(0, buffer.length());
624-
buffer.append(selected);
625-
column = startColumn + buffer.length();
641+
if (selected != null) {
642+
buffer.delete(0, buffer.length());
643+
buffer.append(selected);
644+
column = startColumn + buffer.length();
645+
}
626646
}
627647
break;
628648
case EXIT:
629649
if (buffer.toString().isEmpty()) {
630650
buffer.append(defaultValue);
631651
}
632652
return new InputResult(buffer.toString());
653+
case CANCEL:
654+
return null;
633655
}
634656
}
635657
}
@@ -663,6 +685,8 @@ String selectCandidate(int buffRow, String buffer, int row, int column, List<Can
663685
break;
664686
case EXIT:
665687
return selected;
688+
case CANCEL:
689+
return null;
666690
}
667691
}
668692
}
@@ -756,7 +780,8 @@ private enum Operation {
756780
FORWARD_ONE_LINE,
757781
BACKWARD_ONE_LINE,
758782
INSERT,
759-
EXIT
783+
EXIT,
784+
CANCEL
760785
}
761786

762787
private final List<T> items;
@@ -789,6 +814,8 @@ private void bindKeys(KeyMap<Operation> map) {
789814
map.bind(Operation.FORWARD_ONE_LINE, "e", ctrl('E'), key(terminal, InfoCmp.Capability.key_down));
790815
map.bind(Operation.BACKWARD_ONE_LINE, "y", ctrl('Y'), key(terminal, InfoCmp.Capability.key_up));
791816
map.bind(Operation.EXIT, "\r");
817+
map.bind(Operation.CANCEL, KeyMap.esc());
818+
map.setAmbiguousTimeout(DEFAULT_TIMEOUT_WITH_ESC);
792819
}
793820

794821
public ListResult execute() {
@@ -823,6 +850,8 @@ public ListResult execute() {
823850
case EXIT:
824851
T listItem = items.get(selectRow - firstItemRow);
825852
return new ListResult(listItem.getName());
853+
case CANCEL:
854+
return null;
826855
}
827856
}
828857
}
@@ -833,7 +862,8 @@ private enum Operation {
833862
FORWARD_ONE_LINE,
834863
BACKWARD_ONE_LINE,
835864
TOGGLE,
836-
EXIT
865+
EXIT,
866+
CANCEL
837867
}
838868

839869
private final List<CheckboxItemIF> items;
@@ -864,6 +894,8 @@ private void bindKeys(KeyMap<Operation> map) {
864894
map.bind(Operation.BACKWARD_ONE_LINE, "y", ctrl('Y'), key(terminal, InfoCmp.Capability.key_up));
865895
map.bind(Operation.TOGGLE, " ");
866896
map.bind(Operation.EXIT, "\r");
897+
map.bind(Operation.CANCEL, KeyMap.esc());
898+
map.setAmbiguousTimeout(DEFAULT_TIMEOUT_WITH_ESC);
867899
}
868900

869901
public CheckboxResult execute() {
@@ -895,6 +927,8 @@ public CheckboxResult execute() {
895927
break;
896928
case EXIT:
897929
return new CheckboxResult(selected);
930+
case CANCEL:
931+
return null;
898932
}
899933
}
900934
}

console-ui/src/main/java/org/jline/consoleui/prompt/ConsolePrompt.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,16 @@ public Map<String, PromptResultItemIF> prompt(List<PromptableElementIF> promptab
9292
public Map<String, PromptResultItemIF> prompt(
9393
List<AttributedString> header, List<PromptableElementIF> promptableElementList) throws IOException {
9494
Attributes attributes = terminal.enterRawMode();
95+
boolean cancelled = false;
9596
try {
9697
terminal.puts(InfoCmp.Capability.enter_ca_mode);
9798
terminal.puts(InfoCmp.Capability.keypad_xmit);
9899
terminal.writer().flush();
99100

100101
Map<String, PromptResultItemIF> resultMap = new HashMap<>();
101102

102-
for (PromptableElementIF pe : promptableElementList) {
103+
for (int i = 0; i < promptableElementList.size(); i++) {
104+
PromptableElementIF pe = promptableElementList.get(i);
103105
AttributedStringBuilder message = new AttributedStringBuilder();
104106
message.style(config.style(".pr")).append("? ");
105107
message.style(config.style(".me")).append(pe.getMessage()).append(" ");
@@ -170,6 +172,25 @@ public Map<String, PromptResultItemIF> prompt(
170172
} else {
171173
throw new IllegalArgumentException("wrong type of promptable element");
172174
}
175+
if (result == null) {
176+
// Prompt was cancelled by the user
177+
if (i > 0) {
178+
// Remove last result
179+
header.remove(header.size() - 1);
180+
// Go back to previous prompt
181+
i -= 2;
182+
continue;
183+
} else {
184+
if (config.cancellableFirstPrompt()) {
185+
cancelled = true;
186+
return null;
187+
} else {
188+
// Repeat current prompt
189+
i -= 1;
190+
continue;
191+
}
192+
}
193+
}
173194
String resp = result.getResult();
174195
if (result instanceof ConfirmResult) {
175196
ConfirmResult cr = (ConfirmResult) result;
@@ -189,10 +210,12 @@ public Map<String, PromptResultItemIF> prompt(
189210
terminal.puts(InfoCmp.Capability.exit_ca_mode);
190211
terminal.puts(InfoCmp.Capability.keypad_local);
191212
terminal.writer().flush();
192-
for (AttributedString as : header) {
193-
as.println(terminal);
213+
if (!cancelled) {
214+
for (AttributedString as : header) {
215+
as.println(terminal);
216+
}
217+
terminal.writer().flush();
194218
}
195-
terminal.writer().flush();
196219
}
197220
}
198221

@@ -224,6 +247,7 @@ public static class UiConfig {
224247
private final StyleResolver resolver;
225248
private final ResourceBundle resourceBundle;
226249
private Map<LineReader.Option, Boolean> readerOptions = new HashMap<>();
250+
private boolean cancellableFirstPrompt;
227251

228252
public UiConfig() {
229253
this(null, null, null, null);
@@ -271,6 +295,14 @@ public ResourceBundle resourceBundle() {
271295
return resourceBundle;
272296
}
273297

298+
public boolean cancellableFirstPrompt() {
299+
return cancellableFirstPrompt;
300+
}
301+
302+
public void setCancellableFirstPrompt(boolean cancellableFirstPrompt) {
303+
this.cancellableFirstPrompt = cancellableFirstPrompt;
304+
}
305+
274306
protected void setReaderOptions(Map<LineReader.Option, Boolean> readerOptions) {
275307
this.readerOptions = readerOptions;
276308
}

0 commit comments

Comments
 (0)
0