Skip to content

Commit

Permalink
Added possibility of cancelling prompts
Browse files Browse the repository at this point in the history
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 jline#1035
  • Loading branch information
quintesse committed Aug 14, 2024
1 parent d6b5950 commit 2315824
Show file tree
Hide file tree
Showing 3 changed files with 343 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public abstract class AbstractPrompt<T extends ConsoleUIItemIF> {
private Display display;
private ListRange range = null;

public static final long DEFAULT_TIMEOUT_WITH_ESC = 150L;

public AbstractPrompt(
Terminal terminal, List<AttributedString> header, AttributedString message, ConsolePrompt.UiConfig cfg) {
this(terminal, header, message, new ArrayList<>(), 0, cfg);
Expand Down Expand Up @@ -312,7 +314,8 @@ private List<AttributedString> displayLines(int cursorRow, AttributedString buff
protected static class ExpandableChoicePrompt extends AbstractPrompt<ListItemIF> {
private enum Operation {
INSERT,
EXIT
EXIT,
CANCEL
}

private final int startColumn;
Expand Down Expand Up @@ -345,6 +348,8 @@ private void bindKeys(KeyMap<Operation> map) {
map.bind(Operation.INSERT, Character.toString(i));
}
map.bind(Operation.EXIT, "\r");
map.bind(Operation.CANCEL, KeyMap.esc());
map.setAmbiguousTimeout(DEFAULT_TIMEOUT_WITH_ESC);
}

public ExpandableChoiceResult execute() {
Expand Down Expand Up @@ -396,6 +401,8 @@ public ExpandableChoiceResult execute() {
break;
}
return new ExpandableChoiceResult(selectedId);
case CANCEL:
return null;
}
}
}
Expand All @@ -408,7 +415,8 @@ protected static class ConfirmPrompt extends AbstractPrompt<ListItemIF> {
private enum Operation {
NO,
YES,
EXIT
EXIT,
CANCEL
}

private final int startColumn;
Expand Down Expand Up @@ -442,6 +450,8 @@ private void bindKeys(KeyMap<Operation> map) {
map.bind(Operation.YES, yes, yes.toUpperCase());
map.bind(Operation.NO, no, no.toUpperCase());
map.bind(Operation.EXIT, "\r");
map.bind(Operation.CANCEL, KeyMap.esc());
map.setAmbiguousTimeout(DEFAULT_TIMEOUT_WITH_ESC);
}

public ConfirmResult execute() {
Expand Down Expand Up @@ -472,6 +482,8 @@ public ConfirmResult execute() {
break;
}
return new ConfirmResult(confirm);
case CANCEL:
return null;
}
}
}
Expand All @@ -487,13 +499,15 @@ private enum Operation {
BEGINNING_OF_LINE,
END_OF_LINE,
SELECT_CANDIDATE,
EXIT
EXIT,
CANCEL
}

private enum SelectOp {
FORWARD_ONE_LINE,
BACKWARD_ONE_LINE,
EXIT
EXIT,
CANCEL
}

private final int startColumn;
Expand Down Expand Up @@ -543,12 +557,16 @@ private void bindKeys(KeyMap<Operation> map) {
map.bind(Operation.RIGHT, ctrl('F'));
map.bind(Operation.LEFT, ctrl('B'));
map.bind(Operation.SELECT_CANDIDATE, "\t");
map.bind(Operation.CANCEL, KeyMap.esc());
map.setAmbiguousTimeout(DEFAULT_TIMEOUT_WITH_ESC);
}

private void bindSelectKeys(KeyMap<SelectOp> 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");
map.bind(SelectOp.CANCEL, KeyMap.esc());
map.setAmbiguousTimeout(DEFAULT_TIMEOUT_WITH_ESC);
}

public InputResult execute() {
Expand Down Expand Up @@ -620,16 +638,20 @@ public InputResult execute() {
String selected =
selectCandidate(firstItemRow - 1, buffer.toString(), row + 1, startColumn, matches);
resetHeader();
buffer.delete(0, buffer.length());
buffer.append(selected);
column = startColumn + buffer.length();
if (selected != null) {
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());
case CANCEL:
return null;
}
}
}
Expand Down Expand Up @@ -663,6 +685,8 @@ String selectCandidate(int buffRow, String buffer, int row, int column, List<Can
break;
case EXIT:
return selected;
case CANCEL:
return null;
}
}
}
Expand Down Expand Up @@ -756,7 +780,8 @@ private enum Operation {
FORWARD_ONE_LINE,
BACKWARD_ONE_LINE,
INSERT,
EXIT
EXIT,
CANCEL
}

private final List<T> items;
Expand Down Expand Up @@ -789,6 +814,8 @@ private void bindKeys(KeyMap<Operation> 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.EXIT, "\r");
map.bind(Operation.CANCEL, KeyMap.esc());
map.setAmbiguousTimeout(DEFAULT_TIMEOUT_WITH_ESC);
}

public ListResult execute() {
Expand Down Expand Up @@ -823,6 +850,8 @@ public ListResult execute() {
case EXIT:
T listItem = items.get(selectRow - firstItemRow);
return new ListResult(listItem.getName());
case CANCEL:
return null;
}
}
}
Expand All @@ -833,7 +862,8 @@ private enum Operation {
FORWARD_ONE_LINE,
BACKWARD_ONE_LINE,
TOGGLE,
EXIT
EXIT,
CANCEL
}

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

public CheckboxResult execute() {
Expand Down Expand Up @@ -895,6 +927,8 @@ public CheckboxResult execute() {
break;
case EXIT:
return new CheckboxResult(selected);
case CANCEL:
return null;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ public Map<String, PromptResultItemIF> prompt(

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

for (PromptableElementIF pe : promptableElementList) {
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(" ");
Expand Down Expand Up @@ -170,6 +171,24 @@ public Map<String, PromptResultItemIF> prompt(
} 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 {
if (config.cancellableFirstPrompt()) {
return null;
} else {
// Repeat current prompt
i -= 1;
continue;
}
}
}
String resp = result.getResult();
if (result instanceof ConfirmResult) {
ConfirmResult cr = (ConfirmResult) result;
Expand Down Expand Up @@ -224,6 +243,7 @@ public static class UiConfig {
private final StyleResolver resolver;
private final ResourceBundle resourceBundle;
private Map<LineReader.Option, Boolean> readerOptions = new HashMap<>();
private boolean cancellableFirstPrompt;

public UiConfig() {
this(null, null, null, null);
Expand Down Expand Up @@ -271,6 +291,14 @@ public ResourceBundle resourceBundle() {
return resourceBundle;
}

public boolean cancellableFirstPrompt() {
return cancellableFirstPrompt;
}

public void setCancellableFirstPrompt(boolean cancellableFirstPrompt) {
this.cancellableFirstPrompt = cancellableFirstPrompt;
}

protected void setReaderOptions(Map<LineReader.Option, Boolean> readerOptions) {
this.readerOptions = readerOptions;
}
Expand Down
Loading

0 comments on commit 2315824

Please sign in to comment.