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 Sep 2, 2024
1 parent d6b5950 commit 9907772
Show file tree
Hide file tree
Showing 3 changed files with 352 additions and 13 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 @@ -92,14 +92,16 @@ public Map<String, PromptResultItemIF> prompt(List<PromptableElementIF> promptab
public Map<String, PromptResultItemIF> prompt(
List<AttributedString> header, List<PromptableElementIF> promptableElementList) throws IOException {
Attributes attributes = terminal.enterRawMode();
boolean cancelled = false;
try {
terminal.puts(InfoCmp.Capability.enter_ca_mode);
terminal.puts(InfoCmp.Capability.keypad_xmit);
terminal.writer().flush();

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 +172,25 @@ 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()) {
cancelled = true;
return null;
} else {
// Repeat current prompt
i -= 1;
continue;
}
}
}
String resp = result.getResult();
if (result instanceof ConfirmResult) {
ConfirmResult cr = (ConfirmResult) result;
Expand All @@ -189,10 +210,12 @@ public Map<String, PromptResultItemIF> prompt(
terminal.puts(InfoCmp.Capability.exit_ca_mode);
terminal.puts(InfoCmp.Capability.keypad_local);
terminal.writer().flush();
for (AttributedString as : header) {
as.println(terminal);
if (!cancelled) {
for (AttributedString as : header) {
as.println(terminal);
}
terminal.writer().flush();
}
terminal.writer().flush();
}
}

Expand Down Expand Up @@ -224,6 +247,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 +295,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 9907772

Please sign in to comment.