InputConfirmation.java
/*
* Copyright 2022 Terminal Utils Authors
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package net.morimekta.terminal.input;
import net.morimekta.io.tty.TTYMode;
import net.morimekta.strings.chr.Char;
import net.morimekta.terminal.Terminal;
import java.io.Closeable;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UncheckedIOException;
import static net.morimekta.strings.StringUtil.printableWidth;
import static net.morimekta.strings.chr.Control.CURSOR_ERASE;
import static net.morimekta.strings.chr.Control.cursorLeft;
/**
* Handles reading a yes/no confirmation from the terminal in raw mode.
*/
public class InputConfirmation implements Closeable {
/**
* Constructor for simple confirmation.
*
* @param terminal Terminal to use.
* @param confirmation Message to print.
*/
public InputConfirmation(Terminal terminal, String confirmation) {
this(terminal, confirmation, null);
}
/**
* Constructor for complete line-input.
*
* @param terminal Terminal to use.
* @param confirmation Message to print.
* @param defaultValue Default value to return on e.g. 'enter'.
*/
public InputConfirmation(Terminal terminal, String confirmation, Boolean defaultValue) {
this.terminal = terminal.withMode(TTYMode.RAW);
this.out = terminal.rawOut();
this.confirmation = confirmation;
this.defaultValue = defaultValue;
}
@Override
public void close() {
terminal.close();
}
/**
* @return Run confirmation and return value as boolean.
*/
public boolean getAsBoolean() {
terminal.printWriter().append(confirmation).flush();
try {
for (; ; ) {
Char c = terminal.charReader().read();
if (c == null) {
throw new IOException("End of stream.");
}
handleInterrupt(c);
if (isConfirmation(c)) {
printConfirmation(true);
return true;
} else if (isRejection(c)) {
printConfirmation(false);
return false;
} else if (isDefault(c)) {
if (defaultValue != null) {
printConfirmation(defaultValue);
return defaultValue;
} else {
// ignore this letter if no default value exists.
continue;
}
}
printMessage(String.format("'%s' is not valid input.", c.asString()));
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Handle backspace. These are not control sequences, so must be handled separately
* from those.
*
* @param c The char instance.
* @return True if default value should be used.
*/
protected boolean isDefault(Char c) {
return c.codepoint() == '\n' || c.codepoint() == ' ';
}
/**
* @param c The char instance.
* @return True if confirmation is rejected.
*/
protected boolean isRejection(Char c) {
return c.codepoint() == 'n' || c.codepoint() == Char.DEL || c.codepoint() == Char.BS;
}
/**
* If the provided char indicates confirmation.
*
* @param c The char instance.
* @return True if confirmation is accepted.
*/
protected boolean isConfirmation(Char c) {
return c.codepoint() == 'y';
}
/**
* Handle user interrupts.
*
* @param c The char instance.
* @throws IOException If interrupted.
*/
protected void handleInterrupt(Char c) throws IOException {
if (c.codepoint() == Char.ESC || c.codepoint() == Char.ABR || c.codepoint() == Char.EOF) {
printMessage("User interrupted.");
throw new IOException("User interrupted: " + c.asString());
}
}
/**
* Print confirmation.
*
* @param result The result to be printed.
*/
protected void printConfirmation(boolean result) {
if (result) {
printMessage("Yes.");
} else {
printMessage("No.");
}
}
private void printMessage(String message) {
if (printedMessage == null) {
out.print(' ');
out.print(message);
} else {
out.print(cursorLeft(printableWidth(printedMessage)));
out.print(CURSOR_ERASE);
out.print(message);
}
printedMessage = message;
}
private final Terminal terminal;
private final PrintStream out;
private final String confirmation;
private final Boolean defaultValue;
private String printedMessage;
}