Components can't use interactive mode without tty
- Adding a concept of no-tty which in this commit simply tracks DumbTerminal as jline creates that if there nothing better. - For components without tty don't go to interaction loop. - For new sample show that we can at least manually handle required option with a flow while command option is not required. - Fixes #444
This commit is contained in:
@@ -210,6 +210,10 @@ public interface CommandExecution {
|
||||
this.parserExceptions = parserExceptions;
|
||||
}
|
||||
|
||||
public static CommandParserExceptionsException of(String message, List<CommandParserException> parserExceptions) {
|
||||
return new CommandParserExceptionsException(message, parserExceptions);
|
||||
}
|
||||
|
||||
public List<CommandParserException> getParserExceptions() {
|
||||
return parserExceptions;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import org.jline.keymap.BindingReader;
|
||||
import org.jline.keymap.KeyMap;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.shell.component.ConfirmationInput.ConfirmationInputContext;
|
||||
import org.springframework.shell.component.context.ComponentContext;
|
||||
@@ -39,6 +41,7 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class ConfirmationInput extends AbstractTextComponent<Boolean, ConfirmationInputContext> {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(ConfirmationInput.class);
|
||||
private final boolean defaultValue;
|
||||
private ConfirmationInputContext currentContext;
|
||||
|
||||
@@ -78,6 +81,10 @@ public class ConfirmationInput extends AbstractTextComponent<Boolean, Confirmati
|
||||
@Override
|
||||
protected boolean read(BindingReader bindingReader, KeyMap<String> keyMap, ConfirmationInputContext context) {
|
||||
String operation = bindingReader.readBinding(keyMap);
|
||||
log.debug("Binding read result {}", operation);
|
||||
if (operation == null) {
|
||||
return true;
|
||||
}
|
||||
String input;
|
||||
switch (operation) {
|
||||
case OPERATION_CHAR:
|
||||
|
||||
@@ -70,7 +70,10 @@ public class MultiItemSelector<T, I extends Nameable & Matchable & Enableable &
|
||||
@Override
|
||||
protected MultiItemSelectorContext<T, I> runInternal(MultiItemSelectorContext<T, I> context) {
|
||||
super.runInternal(context);
|
||||
loop(context);
|
||||
// if there's no tty don't try to loop as it would then cause user interaction
|
||||
if (hasTty()) {
|
||||
loop(context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.jline.keymap.BindingReader;
|
||||
import org.jline.keymap.KeyMap;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.shell.component.PathInput.PathInputContext;
|
||||
import org.springframework.shell.component.context.ComponentContext;
|
||||
@@ -42,6 +44,7 @@ import org.springframework.util.StringUtils;;
|
||||
*/
|
||||
public class PathInput extends AbstractTextComponent<Path, PathInputContext> {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(PathInput.class);
|
||||
private PathInputContext currentContext;
|
||||
private Function<String, Path> pathProvider = (path) -> Paths.get(path);
|
||||
|
||||
@@ -75,6 +78,10 @@ public class PathInput extends AbstractTextComponent<Path, PathInputContext> {
|
||||
@Override
|
||||
protected boolean read(BindingReader bindingReader, KeyMap<String> keyMap, PathInputContext context) {
|
||||
String operation = bindingReader.readBinding(keyMap);
|
||||
log.debug("Binding read result {}", operation);
|
||||
if (operation == null) {
|
||||
return true;
|
||||
}
|
||||
String input;
|
||||
switch (operation) {
|
||||
case OPERATION_CHAR:
|
||||
|
||||
@@ -70,7 +70,10 @@ public class SingleItemSelector<T, I extends Nameable & Matchable & Enableable &
|
||||
@Override
|
||||
protected SingleItemSelectorContext<T, I> runInternal(SingleItemSelectorContext<T, I> context) {
|
||||
super.runInternal(context);
|
||||
loop(context);
|
||||
// if there's no tty don't try to loop as it would then cause user interaction
|
||||
if (hasTty()) {
|
||||
loop(context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ import org.jline.keymap.BindingReader;
|
||||
import org.jline.keymap.KeyMap;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.shell.component.StringInput.StringInputContext;
|
||||
import org.springframework.shell.component.context.ComponentContext;
|
||||
@@ -38,6 +40,7 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class StringInput extends AbstractTextComponent<String, StringInputContext> {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(StringInput.class);
|
||||
private final String defaultValue;
|
||||
private StringInputContext currentContext;
|
||||
private Character maskCharacter;
|
||||
@@ -83,6 +86,10 @@ public class StringInput extends AbstractTextComponent<String, StringInputContex
|
||||
@Override
|
||||
protected boolean read(BindingReader bindingReader, KeyMap<String> keyMap, StringInputContext context) {
|
||||
String operation = bindingReader.readBinding(keyMap);
|
||||
log.debug("Binding read result {}", operation);
|
||||
if (operation == null) {
|
||||
return true;
|
||||
}
|
||||
String input;
|
||||
switch (operation) {
|
||||
case OPERATION_CHAR:
|
||||
|
||||
@@ -56,6 +56,14 @@ public interface ComponentContext<C extends ComponentContext<C>> {
|
||||
*/
|
||||
<T> T get(Object key, Class<T> type);
|
||||
|
||||
/**
|
||||
* Check if a context contains a key.
|
||||
*
|
||||
* @param key the key
|
||||
* @return true if context contains key
|
||||
*/
|
||||
boolean containsKey(Object key);
|
||||
|
||||
/**
|
||||
* Put an entry into a context.
|
||||
*
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.jline.keymap.KeyMap;
|
||||
import org.jline.terminal.Attributes;
|
||||
import org.jline.terminal.Size;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.terminal.impl.DumbTerminal;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.Display;
|
||||
import org.jline.utils.InfoCmp.Capability;
|
||||
@@ -153,7 +154,8 @@ public abstract class AbstractComponent<T extends ComponentContext<T>> implement
|
||||
context = runPreRunHandlers(getThisContext(context));
|
||||
T run = runInternal(getThisContext(context));
|
||||
context = runPostRunHandlers(getThisContext(context));
|
||||
if (printResults) {
|
||||
// if there's no tty don't try to print results as it'd be pointless
|
||||
if (printResults && hasTty()) {
|
||||
printResults(context);
|
||||
}
|
||||
return run;
|
||||
@@ -186,6 +188,22 @@ public abstract class AbstractComponent<T extends ComponentContext<T>> implement
|
||||
this.templateLocation = templateLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this component has an existing {@code tty}.
|
||||
*
|
||||
* @return true if component has tty
|
||||
*/
|
||||
protected boolean hasTty() {
|
||||
boolean hasTty = true;
|
||||
if (this.terminal instanceof DumbTerminal) {
|
||||
if (this.terminal.getSize().getRows() == 0) {
|
||||
hasTty = false;
|
||||
}
|
||||
}
|
||||
log.debug("Terminal is {} with size {}, marking hasTty as {}", this.terminal, this.terminal.getSize(), hasTty);
|
||||
return hasTty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a given template with attributes.
|
||||
*
|
||||
|
||||
@@ -26,6 +26,8 @@ import org.jline.keymap.BindingReader;
|
||||
import org.jline.keymap.KeyMap;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.utils.InfoCmp.Capability;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.shell.component.context.BaseComponentContext;
|
||||
import org.springframework.shell.component.context.ComponentContext;
|
||||
@@ -46,6 +48,7 @@ import static org.jline.keymap.KeyMap.key;
|
||||
public abstract class AbstractSelectorComponent<T, C extends SelectorComponentContext<T, I, C>, I extends Nameable & Matchable & Enableable & Itemable<T>>
|
||||
extends AbstractComponent<C> {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(AbstractSelectorComponent.class);
|
||||
protected final String name;
|
||||
private final List<I> items;
|
||||
private Comparator<I> comparator = (o1, o2) -> 0;
|
||||
@@ -155,6 +158,10 @@ public abstract class AbstractSelectorComponent<T, C extends SelectorComponentCo
|
||||
ItemStateViewProjection buildItemStateView = buildItemStateView(start.get(), thisContext);
|
||||
List<ItemState<I>> itemStateView = buildItemStateView.items;
|
||||
String operation = bindingReader.readBinding(keyMap);
|
||||
log.debug("Binding read result {}", operation);
|
||||
if (operation == null) {
|
||||
return true;
|
||||
}
|
||||
String input;
|
||||
switch (operation) {
|
||||
case OPERATION_SELECT:
|
||||
|
||||
@@ -66,7 +66,10 @@ public abstract class AbstractTextComponent<T, C extends TextComponentContext<T,
|
||||
|
||||
@Override
|
||||
protected C runInternal(C context) {
|
||||
loop(context);
|
||||
// if there's no tty don't try to loop as it would then cause user interaction
|
||||
if (hasTty()) {
|
||||
loop(context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user