Refactor main interpreter code
Fixed several minor issues Only print the last value when interpreting a file Resolves #4
This commit is contained in:
parent
5cb6212d2a
commit
058e937c3e
|
@ -21,7 +21,7 @@ public class RuntimeEnvironment {
|
||||||
private String path;
|
private String path;
|
||||||
private Runnable terminationFunction;
|
private Runnable terminationFunction;
|
||||||
private Runnable errorTerminationFunction;
|
private Runnable errorTerminationFunction;
|
||||||
private Function<String, String> outputDecorator;
|
private Function<String, String> promptDecorator;
|
||||||
private Function<String, String> valueOutputDecorator;
|
private Function<String, String> valueOutputDecorator;
|
||||||
private Function<String, String> warningOutputDecorator;
|
private Function<String, String> warningOutputDecorator;
|
||||||
private Function<String, String> errorOutputDecorator;
|
private Function<String, String> errorOutputDecorator;
|
||||||
|
@ -40,7 +40,7 @@ public class RuntimeEnvironment {
|
||||||
path = null;
|
path = null;
|
||||||
terminationFunction = null;
|
terminationFunction = null;
|
||||||
errorTerminationFunction = null;
|
errorTerminationFunction = null;
|
||||||
outputDecorator = null;
|
promptDecorator = null;
|
||||||
valueOutputDecorator = null;
|
valueOutputDecorator = null;
|
||||||
warningOutputDecorator = null;
|
warningOutputDecorator = null;
|
||||||
errorOutputDecorator = null;
|
errorOutputDecorator = null;
|
||||||
|
@ -79,8 +79,8 @@ public class RuntimeEnvironment {
|
||||||
this.errorTerminationFunction = errorTerminationFunction;
|
this.errorTerminationFunction = errorTerminationFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOutputDecorator(Function<String, String> outputDecorator) {
|
public void setPromptDecorator(Function<String, String> promptDecorator) {
|
||||||
this.outputDecorator = outputDecorator;
|
this.promptDecorator = promptDecorator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setValueOutputDecorator(Function<String, String> valueOutputDecorator) {
|
public void setValueOutputDecorator(Function<String, String> valueOutputDecorator) {
|
||||||
|
@ -131,8 +131,8 @@ public class RuntimeEnvironment {
|
||||||
errorTerminationFunction.run();
|
errorTerminationFunction.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String decorateOutput(String output) {
|
public String decoratePrompt(String prompt) {
|
||||||
return outputDecorator.apply(output);
|
return promptDecorator.apply(prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String decorateValueOutput(String valueOutput) {
|
public String decorateValueOutput(String valueOutput) {
|
||||||
|
|
|
@ -1,11 +1,29 @@
|
||||||
package interpreter;
|
package interpreter;
|
||||||
|
|
||||||
|
import sexpression.SExpression;
|
||||||
|
|
||||||
public class FileLispInterpreter extends LispInterpreter {
|
public class FileLispInterpreter extends LispInterpreter {
|
||||||
|
|
||||||
|
private SExpression lastSExpression;
|
||||||
|
|
||||||
|
public FileLispInterpreter() {
|
||||||
|
this.lastSExpression = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void afterInterpreting() {
|
protected SExpression evaluateNextSExpression() {
|
||||||
environment.getOutput().println();
|
return this.lastSExpression = super.evaluateNextSExpression();
|
||||||
environment.getOutput().close();
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void printSExpression(SExpression sExpression) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void applyFinishingTouches() {
|
||||||
|
if (lastSExpression != null)
|
||||||
|
super.printSExpression(lastSExpression);
|
||||||
|
|
||||||
|
super.applyFinishingTouches();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,28 @@
|
||||||
package interpreter;
|
package interpreter;
|
||||||
|
|
||||||
|
import sexpression.SExpression;
|
||||||
|
|
||||||
public class InteractiveLispInterpreter extends LispInterpreter {
|
public class InteractiveLispInterpreter extends LispInterpreter {
|
||||||
|
|
||||||
private static final String GREETING = "Transcendental Lisp - Version 1.0.0";
|
|
||||||
private static final String PROMPT = "~ ";
|
private static final String PROMPT = "~ ";
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void beforeInterpreting() {
|
|
||||||
environment.getOutput().println(GREETING);
|
|
||||||
environment.getOutput().println();
|
|
||||||
environment.getOutput().flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void prompt() {
|
protected void prompt() {
|
||||||
environment.getOutput().print(environment.decorateOutput(PROMPT));
|
environment.getOutput().print(environment.decoratePrompt(PROMPT));
|
||||||
environment.getOutput().flush();
|
environment.getOutput().flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void printValueOfNextSExpression() {
|
protected void printSExpression(SExpression sExpression) {
|
||||||
environment.getOutput().println();
|
environment.getOutput().println();
|
||||||
super.printValueOfNextSExpression();
|
super.printSExpression(sExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void afterInterpreting() {
|
protected void applyFinishingTouches() {
|
||||||
environment.getOutput().println();
|
environment.getOutput().println();
|
||||||
environment.getOutput().println();
|
environment.getOutput().println(environment.decoratePrompt(""));
|
||||||
environment.getOutput().close();
|
environment.getOutput().flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,39 +18,44 @@ public class LispInterpreter {
|
||||||
|
|
||||||
public void interpret() {
|
public void interpret() {
|
||||||
createParser();
|
createParser();
|
||||||
beforeInterpreting();
|
|
||||||
|
|
||||||
for (prompt(); !parser.isEof(); prompt())
|
for (prompt(); !parser.isEof(); prompt())
|
||||||
printValueOfNextSExpression();
|
evaluateAndPrintNextSExpression();
|
||||||
|
|
||||||
afterInterpreting();
|
applyFinishingTouches();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createParser() {
|
private void createParser() {
|
||||||
parser = new LispParser(environment.getInput(), environment.getInputName());
|
parser = new LispParser(environment.getInput(), environment.getInputName());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void beforeInterpreting() {}
|
|
||||||
|
|
||||||
protected void prompt() {}
|
protected void prompt() {}
|
||||||
|
|
||||||
protected void printValueOfNextSExpression() {
|
private void evaluateAndPrintNextSExpression() {
|
||||||
try {
|
try {
|
||||||
printValueOfNextSExpressionWithException();
|
evaluateAndPrintNextSExpressionWithException();
|
||||||
} catch (LispException e) {
|
} catch (LispException e) {
|
||||||
environment.getErrorManager().handle(e);
|
environment.getErrorManager().handle(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printValueOfNextSExpressionWithException() {
|
private void evaluateAndPrintNextSExpressionWithException() {
|
||||||
SExpression sExpression = parser.getNextSExpression();
|
printSExpression(evaluateNextSExpression());
|
||||||
String result = environment.decorateValueOutput(String.valueOf(eval(sExpression)));
|
}
|
||||||
|
|
||||||
|
protected SExpression evaluateNextSExpression() {
|
||||||
|
SExpression sExpression = parser.getNextSExpression();
|
||||||
|
|
||||||
|
return eval(sExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void printSExpression(SExpression sExpression) {
|
||||||
|
String result = environment.decorateValueOutput(String.valueOf(sExpression));
|
||||||
environment.getOutput().println(result);
|
environment.getOutput().println(result);
|
||||||
environment.getOutput().flush();
|
environment.getOutput().flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void afterInterpreting() {
|
protected void applyFinishingTouches() {
|
||||||
environment.getOutput().println();
|
environment.getOutput().println();
|
||||||
environment.getOutput().flush();
|
environment.getOutput().flush();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,15 @@ public interface LispInterpreterBuilder {
|
||||||
|
|
||||||
void useFile(String fileName);
|
void useFile(String fileName);
|
||||||
|
|
||||||
void setOutputDecorator(Function<String, String> outputDecorator);
|
void setPromptDecorator(Function<String, String> decorator);
|
||||||
|
|
||||||
void setValueOutputDecorator(Function<String, String> valueOutputDecorator);
|
void setValueOutputDecorator(Function<String, String> decorator);
|
||||||
|
|
||||||
void setWarningOutputDecorator(Function<String, String> warningOutputDecorator);
|
void setWarningOutputDecorator(Function<String, String> decorator);
|
||||||
|
|
||||||
void setErrorOutputDecorator(Function<String, String> errorOutputDecorator);
|
void setErrorOutputDecorator(Function<String, String> decorator);
|
||||||
|
|
||||||
void setCriticalOutputDecorator(Function<String, String> criticalOutputDecorator);
|
void setCriticalOutputDecorator(Function<String, String> decorator);
|
||||||
|
|
||||||
LispInterpreter build();
|
LispInterpreter build();
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ public class LispInterpreterBuilderImpl implements LispInterpreterBuilder {
|
||||||
private PrintStream errorOutputStream;
|
private PrintStream errorOutputStream;
|
||||||
private Runnable terminationFunction;
|
private Runnable terminationFunction;
|
||||||
private Runnable errorTerminationFunction;
|
private Runnable errorTerminationFunction;
|
||||||
private Function<String, String> outputDecorator;
|
private Function<String, String> promptDecorator;
|
||||||
private Function<String, String> valueOutputDecorator;
|
private Function<String, String> valueOutputDecorator;
|
||||||
private Function<String, String> warningOutputDecorator;
|
private Function<String, String> warningOutputDecorator;
|
||||||
private Function<String, String> errorOutputDecorator;
|
private Function<String, String> errorOutputDecorator;
|
||||||
|
@ -38,7 +38,7 @@ public class LispInterpreterBuilderImpl implements LispInterpreterBuilder {
|
||||||
this.isInteractive = true;
|
this.isInteractive = true;
|
||||||
this.isFileBased = false;
|
this.isFileBased = false;
|
||||||
this.isBuilt = false;
|
this.isBuilt = false;
|
||||||
this.outputDecorator = s -> s;
|
this.promptDecorator = s -> s;
|
||||||
this.valueOutputDecorator = s -> s;
|
this.valueOutputDecorator = s -> s;
|
||||||
this.warningOutputDecorator = s -> s;
|
this.warningOutputDecorator = s -> s;
|
||||||
this.errorOutputDecorator = s -> s;
|
this.errorOutputDecorator = s -> s;
|
||||||
|
@ -85,8 +85,8 @@ public class LispInterpreterBuilderImpl implements LispInterpreterBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setOutputDecorator(Function<String, String> decorator) {
|
public void setPromptDecorator(Function<String, String> decorator) {
|
||||||
this.outputDecorator = decorator;
|
this.promptDecorator = decorator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -135,7 +135,7 @@ public class LispInterpreterBuilderImpl implements LispInterpreterBuilder {
|
||||||
environment.setErrorManager(errorManager);
|
environment.setErrorManager(errorManager);
|
||||||
environment.setTerminationFunction(terminationFunction);
|
environment.setTerminationFunction(terminationFunction);
|
||||||
environment.setErrorTerminationFunction(errorTerminationFunction);
|
environment.setErrorTerminationFunction(errorTerminationFunction);
|
||||||
environment.setOutputDecorator(outputDecorator);
|
environment.setPromptDecorator(promptDecorator);
|
||||||
environment.setValueOutputDecorator(valueOutputDecorator);
|
environment.setValueOutputDecorator(valueOutputDecorator);
|
||||||
environment.setWarningOutputDecorator(warningOutputDecorator);
|
environment.setWarningOutputDecorator(warningOutputDecorator);
|
||||||
environment.setErrorOutputDecorator(errorOutputDecorator);
|
environment.setErrorOutputDecorator(errorOutputDecorator);
|
||||||
|
|
|
@ -14,6 +14,7 @@ import terminal.SafeStream.UncheckedIOException;
|
||||||
|
|
||||||
public class LispMain {
|
public class LispMain {
|
||||||
|
|
||||||
|
private static final String GREETING = "Transcendental Lisp - Version 1.0.1";
|
||||||
private static final String ANSI_RESET = "\u001B[0m";
|
private static final String ANSI_RESET = "\u001B[0m";
|
||||||
private static final String ANSI_RED = "\u001B[31m";
|
private static final String ANSI_RED = "\u001B[31m";
|
||||||
private static final String ANSI_GREEN = "\u001B[32m";
|
private static final String ANSI_GREEN = "\u001B[32m";
|
||||||
|
@ -33,30 +34,55 @@ public class LispMain {
|
||||||
private PipedOutputStream inputWriter;
|
private PipedOutputStream inputWriter;
|
||||||
private PipedInputStream outputReader;
|
private PipedInputStream outputReader;
|
||||||
private PipedOutputStream outputWriter;
|
private PipedOutputStream outputWriter;
|
||||||
|
private PrintStream outputStream;
|
||||||
private SafePipedOutputStream safeOutputWriter;
|
private SafePipedOutputStream safeOutputWriter;
|
||||||
private LispTerminal lispTerminal;
|
private LispTerminal lispTerminal;
|
||||||
|
|
||||||
|
private void runInteractive() {
|
||||||
|
initializeTerminalAndStreams();
|
||||||
|
printGreeting();
|
||||||
|
lispTerminal.start();
|
||||||
|
buildInteractiveInterpreter().interpret();
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeTerminalAndStreams() {
|
||||||
|
try {
|
||||||
|
initalizeTerminalAndStreamsWithException();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initalizeTerminalAndStreamsWithException() throws IOException {
|
||||||
|
inputReader = new PipedInputStream();
|
||||||
|
inputWriter = new PipedOutputStream(inputReader);
|
||||||
|
outputReader = new PipedInputStream();
|
||||||
|
outputWriter = new PipedOutputStream(outputReader);
|
||||||
|
outputStream = new PrintStream(outputWriter);
|
||||||
|
safeOutputWriter = new SafePipedOutputStream(outputWriter);
|
||||||
|
lispTerminal = new LispTerminal(createIOSafeTerminal(), inputWriter, outputReader);
|
||||||
|
}
|
||||||
|
|
||||||
private IOSafeTerminal createIOSafeTerminal() throws IOException {
|
private IOSafeTerminal createIOSafeTerminal() throws IOException {
|
||||||
Terminal defaultTerminal = new DefaultTerminalFactory().createTerminal();
|
Terminal defaultTerminal = new DefaultTerminalFactory().createTerminal();
|
||||||
|
|
||||||
return IOSafeTerminalAdapter.createRuntimeExceptionConvertingAdapter(defaultTerminal);
|
return IOSafeTerminalAdapter.createRuntimeExceptionConvertingAdapter(defaultTerminal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runInteractive() {
|
private void printGreeting() {
|
||||||
initializeTerminal();
|
outputStream.println(GREETING);
|
||||||
lispTerminal.start();
|
outputStream.println();
|
||||||
buildInteractiveInterpreter().interpret();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LispInterpreter buildInteractiveInterpreter() {
|
private LispInterpreter buildInteractiveInterpreter() {
|
||||||
LispInterpreterBuilder builder = LispInterpreterBuilderImpl.getInstance();
|
LispInterpreterBuilder builder = LispInterpreterBuilderImpl.getInstance();
|
||||||
PrintStream outputStream = new PrintStream(outputWriter);
|
|
||||||
builder.setInput(inputReader, "terminal");
|
builder.setInput(inputReader, "terminal");
|
||||||
builder.setOutput(outputStream);
|
builder.setOutput(outputStream);
|
||||||
builder.setErrorOutput(outputStream);
|
builder.setErrorOutput(outputStream);
|
||||||
builder.setTerminationFunction(this::shutdown);
|
builder.setTerminationFunction(this::shutdown);
|
||||||
builder.setErrorTerminationFunction(this::shutdown);
|
builder.setErrorTerminationFunction(this::shutdown);
|
||||||
builder.setOutputDecorator(makeInteractiveDecorator(ANSI_GREEN));
|
builder.setPromptDecorator(makeSegmentDecorator(ANSI_GREEN));
|
||||||
builder.setValueOutputDecorator(s -> s);
|
builder.setValueOutputDecorator(s -> s);
|
||||||
builder.setWarningOutputDecorator(s -> s);
|
builder.setWarningOutputDecorator(s -> s);
|
||||||
builder.setErrorOutputDecorator(s -> s);
|
builder.setErrorOutputDecorator(s -> s);
|
||||||
|
@ -66,11 +92,13 @@ public class LispMain {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutdown() {
|
private void shutdown() {
|
||||||
|
outputStream.println();
|
||||||
|
outputStream.println(END_OF_SEGMENT);
|
||||||
lispTerminal.stop();
|
lispTerminal.stop();
|
||||||
safeOutputWriter.close();
|
safeOutputWriter.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Function<String, String> makeInteractiveDecorator(String color) {
|
private static Function<String, String> makeSegmentDecorator(String color) {
|
||||||
return new Function<String, String>() {
|
return new Function<String, String>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -80,23 +108,6 @@ public class LispMain {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeTerminal() {
|
|
||||||
try {
|
|
||||||
initalizeTerminalWithException();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initalizeTerminalWithException() throws IOException {
|
|
||||||
inputReader = new PipedInputStream();
|
|
||||||
inputWriter = new PipedOutputStream(inputReader);
|
|
||||||
outputReader = new PipedInputStream();
|
|
||||||
outputWriter = new PipedOutputStream(outputReader);
|
|
||||||
safeOutputWriter = new SafePipedOutputStream(outputWriter);
|
|
||||||
lispTerminal = new LispTerminal(createIOSafeTerminal(), inputWriter, outputReader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runWithFile(String fileName) {
|
private void runWithFile(String fileName) {
|
||||||
buildFileInterpreter(fileName).interpret();
|
buildFileInterpreter(fileName).interpret();
|
||||||
}
|
}
|
||||||
|
@ -108,7 +119,6 @@ public class LispMain {
|
||||||
builder.setErrorOutput(System.err);
|
builder.setErrorOutput(System.err);
|
||||||
builder.setTerminationFunction(() -> System.exit(0));
|
builder.setTerminationFunction(() -> System.exit(0));
|
||||||
builder.setErrorTerminationFunction(() -> System.exit(1));
|
builder.setErrorTerminationFunction(() -> System.exit(1));
|
||||||
builder.setOutputDecorator(makeColorDecorator(ANSI_GREEN));
|
|
||||||
builder.setValueOutputDecorator(makeColorDecorator(ANSI_GREEN));
|
builder.setValueOutputDecorator(makeColorDecorator(ANSI_GREEN));
|
||||||
builder.setWarningOutputDecorator(makeColorDecorator(ANSI_YELLOW));
|
builder.setWarningOutputDecorator(makeColorDecorator(ANSI_YELLOW));
|
||||||
builder.setErrorOutputDecorator(makeColorDecorator(ANSI_RED));
|
builder.setErrorOutputDecorator(makeColorDecorator(ANSI_RED));
|
||||||
|
|
|
@ -100,17 +100,23 @@ public class LispTerminal {
|
||||||
private KeyStroke getKeyStroke() {
|
private KeyStroke getKeyStroke() {
|
||||||
KeyStroke keyStroke = null;
|
KeyStroke keyStroke = null;
|
||||||
|
|
||||||
|
// issue #299
|
||||||
try {
|
try {
|
||||||
keyStroke = terminal.pollInput();
|
keyStroke = terminal.pollInput();
|
||||||
} catch (IllegalStateException e) { // issue #299
|
} catch (IllegalStateException e) {
|
||||||
moveCursorToEndOfInput();
|
doControlC();
|
||||||
terminal.putCharacter('\n');
|
|
||||||
stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyStroke;
|
return keyStroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void doControlC() {
|
||||||
|
moveCursorToEndOfInput();
|
||||||
|
terminal.putCharacter('\n');
|
||||||
|
updateOrigin();
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized void handleKey(KeyStroke keyStroke) {
|
private synchronized void handleKey(KeyStroke keyStroke) {
|
||||||
doKey(keyStroke);
|
doKey(keyStroke);
|
||||||
terminal.flush();
|
terminal.flush();
|
||||||
|
@ -324,9 +330,7 @@ public class LispTerminal {
|
||||||
private void takeNap() {
|
private void takeNap() {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1);
|
Thread.sleep(1);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException ignored) {}
|
||||||
isStopped = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeOutput() {
|
private void writeOutput() {
|
||||||
|
|
|
@ -91,10 +91,10 @@ public class RuntimeEnvironmentTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void assignOutputDecorator() {
|
public void assignPromptDecorator() {
|
||||||
environment.setOutputDecorator(s -> "[" + s + "]");
|
environment.setPromptDecorator(s -> "[" + s + "]");
|
||||||
|
|
||||||
assertEquals("[test]", environment.decorateOutput("test"));
|
assertEquals("[test]", environment.decoratePrompt("test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -135,7 +135,7 @@ public class RuntimeEnvironmentTest {
|
||||||
environment.setPath("testpath/");
|
environment.setPath("testpath/");
|
||||||
environment.setTerminationFunction(() -> indicatorSet.add(TERMINATED_SUCCESSFULLY));
|
environment.setTerminationFunction(() -> indicatorSet.add(TERMINATED_SUCCESSFULLY));
|
||||||
environment.setErrorTerminationFunction(() -> indicatorSet.add(TERMINATED_EXCEPTIONALLY));
|
environment.setErrorTerminationFunction(() -> indicatorSet.add(TERMINATED_EXCEPTIONALLY));
|
||||||
environment.setOutputDecorator(s -> "[" + s + "]");
|
environment.setPromptDecorator(s -> "[" + s + "]");
|
||||||
environment.setValueOutputDecorator(s -> "(" + s + ")");
|
environment.setValueOutputDecorator(s -> "(" + s + ")");
|
||||||
environment.setWarningOutputDecorator(s -> "|" + s + "|");
|
environment.setWarningOutputDecorator(s -> "|" + s + "|");
|
||||||
environment.setErrorOutputDecorator(s -> "{" + s + "}");
|
environment.setErrorOutputDecorator(s -> "{" + s + "}");
|
||||||
|
@ -160,8 +160,8 @@ public class RuntimeEnvironmentTest {
|
||||||
} catch (NullPointerException e) {}
|
} catch (NullPointerException e) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
environment.decorateOutput("");
|
environment.decoratePrompt("");
|
||||||
fail("decorateOutput");
|
fail("decoratePrompt");
|
||||||
} catch (NullPointerException e) {}
|
} catch (NullPointerException e) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -47,7 +47,6 @@ public class LOADTest {
|
||||||
environment.setErrorOutput(new PrintStream(errorOutputStream));
|
environment.setErrorOutput(new PrintStream(errorOutputStream));
|
||||||
environment.setErrorManager(new ErrorManager());
|
environment.setErrorManager(new ErrorManager());
|
||||||
environment.setPath("");
|
environment.setPath("");
|
||||||
environment.setOutputDecorator(s -> s);
|
|
||||||
environment.setWarningOutputDecorator(s -> s);
|
environment.setWarningOutputDecorator(s -> s);
|
||||||
environment.setErrorOutputDecorator(s -> s);
|
environment.setErrorOutputDecorator(s -> s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package terminal;
|
package terminal;
|
||||||
|
|
||||||
|
import static com.googlecode.lanterna.input.KeyType.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static terminal.LispTerminal.END_OF_SEGMENT;
|
import static terminal.LispTerminal.END_OF_SEGMENT;
|
||||||
|
|
||||||
|
@ -146,7 +147,7 @@ public class LispTerminalTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void leftArrowDoesNotMovePastOrigin() {
|
public void leftArrowDoesNotMovePastOrigin() {
|
||||||
pressKey(KeyType.ArrowLeft);
|
pressKey(ArrowLeft);
|
||||||
assertCursorPosition(0, 0);
|
assertCursorPosition(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,13 +155,13 @@ public class LispTerminalTest {
|
||||||
public void leftArrowWorksAfterEnteringCharacters() {
|
public void leftArrowWorksAfterEnteringCharacters() {
|
||||||
enterCharacters("abc");
|
enterCharacters("abc");
|
||||||
assertCursorPosition(3, 0);
|
assertCursorPosition(3, 0);
|
||||||
pressKey(KeyType.ArrowLeft);
|
pressKey(ArrowLeft);
|
||||||
assertCursorPosition(2, 0);
|
assertCursorPosition(2, 0);
|
||||||
pressKey(KeyType.ArrowLeft);
|
pressKey(ArrowLeft);
|
||||||
assertCursorPosition(1, 0);
|
assertCursorPosition(1, 0);
|
||||||
pressKey(KeyType.ArrowLeft);
|
pressKey(ArrowLeft);
|
||||||
assertCursorPosition(0, 0);
|
assertCursorPosition(0, 0);
|
||||||
pressKey(KeyType.ArrowLeft);
|
pressKey(ArrowLeft);
|
||||||
assertCursorPosition(0, 0);
|
assertCursorPosition(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,13 +170,13 @@ public class LispTerminalTest {
|
||||||
setColumns(5);
|
setColumns(5);
|
||||||
enterCharacters("123451");
|
enterCharacters("123451");
|
||||||
assertCursorPosition(1, 1);
|
assertCursorPosition(1, 1);
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 2);
|
pressKeyTimes(ArrowLeft, 2);
|
||||||
assertCursorPosition(4, 0);
|
assertCursorPosition(4, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rightArrowDoesNotMovePastEndOfInput() {
|
public void rightArrowDoesNotMovePastEndOfInput() {
|
||||||
pressKey(KeyType.ArrowRight);
|
pressKey(ArrowRight);
|
||||||
assertCursorPosition(0, 0);
|
assertCursorPosition(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,11 +184,11 @@ public class LispTerminalTest {
|
||||||
public void rightArrowWorksAfterMovingLeft() {
|
public void rightArrowWorksAfterMovingLeft() {
|
||||||
enterCharacters("12");
|
enterCharacters("12");
|
||||||
assertCursorPosition(2, 0);
|
assertCursorPosition(2, 0);
|
||||||
pressKey(KeyType.ArrowLeft);
|
pressKey(ArrowLeft);
|
||||||
assertCursorPosition(1, 0);
|
assertCursorPosition(1, 0);
|
||||||
pressKey(KeyType.ArrowRight);
|
pressKey(ArrowRight);
|
||||||
assertCursorPosition(2, 0);
|
assertCursorPosition(2, 0);
|
||||||
pressKey(KeyType.ArrowRight);
|
pressKey(ArrowRight);
|
||||||
assertCursorPosition(2, 0);
|
assertCursorPosition(2, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,9 +197,9 @@ public class LispTerminalTest {
|
||||||
setColumns(5);
|
setColumns(5);
|
||||||
enterCharacters("123451");
|
enterCharacters("123451");
|
||||||
assertCursorPosition(1, 1);
|
assertCursorPosition(1, 1);
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 3);
|
pressKeyTimes(ArrowLeft, 3);
|
||||||
assertCursorPosition(3, 0);
|
assertCursorPosition(3, 0);
|
||||||
pressKeyTimes(KeyType.ArrowRight, 3);
|
pressKeyTimes(ArrowRight, 3);
|
||||||
assertCursorPosition(1, 1);
|
assertCursorPosition(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +213,7 @@ public class LispTerminalTest {
|
||||||
@Test
|
@Test
|
||||||
public void characterIsInserted() {
|
public void characterIsInserted() {
|
||||||
enterCharacters("abcd");
|
enterCharacters("abcd");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 2);
|
pressKeyTimes(ArrowLeft, 2);
|
||||||
enterCharacter('x');
|
enterCharacter('x');
|
||||||
assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c', 'd' } });
|
assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c', 'd' } });
|
||||||
}
|
}
|
||||||
|
@ -221,21 +222,21 @@ public class LispTerminalTest {
|
||||||
public void characterIsInserted_PushesInputToNextRow() {
|
public void characterIsInserted_PushesInputToNextRow() {
|
||||||
setColumns(4);
|
setColumns(4);
|
||||||
enterCharacters("abcd");
|
enterCharacters("abcd");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 2);
|
pressKeyTimes(ArrowLeft, 2);
|
||||||
enterCharacter('x');
|
enterCharacter('x');
|
||||||
assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c' }, { 'd', ' ', ' ', ' ' } });
|
assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c' }, { 'd', ' ', ' ', ' ' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void backspaceDoesNothingAtOrigin() {
|
public void backspaceDoesNothingAtOrigin() {
|
||||||
pressKey(KeyType.Backspace);
|
pressKey(Backspace);
|
||||||
assertCursorPosition(0, 0);
|
assertCursorPosition(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void backspaceWorksAfterInput() {
|
public void backspaceWorksAfterInput() {
|
||||||
enterCharacters("12345");
|
enterCharacters("12345");
|
||||||
pressKeyTimes(KeyType.Backspace, 2);
|
pressKeyTimes(Backspace, 2);
|
||||||
assertCursorPosition(3, 0);
|
assertCursorPosition(3, 0);
|
||||||
assertCharacterPositions(new char[][] { { '1', '2', '3', ' ', ' ', ' ' } });
|
assertCharacterPositions(new char[][] { { '1', '2', '3', ' ', ' ', ' ' } });
|
||||||
}
|
}
|
||||||
|
@ -244,7 +245,7 @@ public class LispTerminalTest {
|
||||||
public void backspaceWorksAcrossRow() {
|
public void backspaceWorksAcrossRow() {
|
||||||
setColumns(4);
|
setColumns(4);
|
||||||
enterCharacters("1234567");
|
enterCharacters("1234567");
|
||||||
pressKeyTimes(KeyType.Backspace, 5);
|
pressKeyTimes(Backspace, 5);
|
||||||
assertCursorPosition(2, 0);
|
assertCursorPosition(2, 0);
|
||||||
assertCharacterPositions(new char[][] { { '1', '2', ' ', ' ' }, { ' ', ' ', ' ', ' ' } });
|
assertCharacterPositions(new char[][] { { '1', '2', ' ', ' ' }, { ' ', ' ', ' ', ' ' } });
|
||||||
}
|
}
|
||||||
|
@ -252,22 +253,22 @@ public class LispTerminalTest {
|
||||||
@Test
|
@Test
|
||||||
public void backspaceWorksInMiddleOfInput() {
|
public void backspaceWorksInMiddleOfInput() {
|
||||||
enterCharacters("12345");
|
enterCharacters("12345");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 2);
|
pressKeyTimes(ArrowLeft, 2);
|
||||||
pressKey(KeyType.Backspace);
|
pressKey(Backspace);
|
||||||
assertCursorPosition(2, 0);
|
assertCursorPosition(2, 0);
|
||||||
assertCharacterPositions(new char[][] { { '1', '2', '4', '5' } });
|
assertCharacterPositions(new char[][] { { '1', '2', '4', '5' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void deleteDoesNothingAtOrigin() {
|
public void deleteDoesNothingAtOrigin() {
|
||||||
pressKey(KeyType.Delete);
|
pressKey(Delete);
|
||||||
assertCursorPosition(0, 0);
|
assertCursorPosition(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void deleteDoesNothingAtEndOfInput() {
|
public void deleteDoesNothingAtEndOfInput() {
|
||||||
enterCharacters("del");
|
enterCharacters("del");
|
||||||
pressKey(KeyType.Delete);
|
pressKey(Delete);
|
||||||
assertCursorPosition(3, 0);
|
assertCursorPosition(3, 0);
|
||||||
assertCharacterPositions(new char[][] { { 'd', 'e', 'l' } });
|
assertCharacterPositions(new char[][] { { 'd', 'e', 'l' } });
|
||||||
}
|
}
|
||||||
|
@ -275,8 +276,8 @@ public class LispTerminalTest {
|
||||||
@Test
|
@Test
|
||||||
public void deleteWorksAtStartOfInput() {
|
public void deleteWorksAtStartOfInput() {
|
||||||
enterCharacters("del");
|
enterCharacters("del");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 3);
|
pressKeyTimes(ArrowLeft, 3);
|
||||||
pressKeyTimes(KeyType.Delete, 3);
|
pressKeyTimes(Delete, 3);
|
||||||
assertCursorPosition(0, 0);
|
assertCursorPosition(0, 0);
|
||||||
assertCharacterPositions(new char[][] { { ' ', ' ', ' ' } });
|
assertCharacterPositions(new char[][] { { ' ', ' ', ' ' } });
|
||||||
}
|
}
|
||||||
|
@ -285,56 +286,56 @@ public class LispTerminalTest {
|
||||||
public void deleteWorksAcrossRow() {
|
public void deleteWorksAcrossRow() {
|
||||||
setColumns(4);
|
setColumns(4);
|
||||||
enterCharacters("delete");
|
enterCharacters("delete");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 5);
|
pressKeyTimes(ArrowLeft, 5);
|
||||||
pressKey(KeyType.Delete);
|
pressKey(Delete);
|
||||||
assertCursorPosition(1, 0);
|
assertCursorPosition(1, 0);
|
||||||
assertCharacterPositions(new char[][] { { 'd', 'l', 'e', 't' }, { 'e', ' ' } });
|
assertCharacterPositions(new char[][] { { 'd', 'l', 'e', 't' }, { 'e', ' ' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enterMovesToNextLine() {
|
public void enterMovesToNextLine() {
|
||||||
pressKey(KeyType.Enter);
|
pressKey(Enter);
|
||||||
assertCursorPosition(0, 1);
|
assertCursorPosition(0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enterWritesLineToPipedStream() {
|
public void enterWritesLineToPipedStream() {
|
||||||
enterCharacters("enter");
|
enterCharacters("enter");
|
||||||
pressKey(KeyType.Enter);
|
pressKey(Enter);
|
||||||
assertInputWritten("enter\n");
|
assertInputWritten("enter\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enterPressedInMiddleOfInput_WritesEntireLineToPipedStream() {
|
public void enterPressedInMiddleOfInput_WritesEntireLineToPipedStream() {
|
||||||
enterCharacters("enter");
|
enterCharacters("enter");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 2);
|
pressKeyTimes(ArrowLeft, 2);
|
||||||
pressKey(KeyType.Enter);
|
pressKey(Enter);
|
||||||
assertInputWritten("enter\n");
|
assertInputWritten("enter\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enterAfterInsertedText_WritesLineToPipedStream() {
|
public void enterAfterInsertedText_WritesLineToPipedStream() {
|
||||||
enterCharacters("enter");
|
enterCharacters("enter");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 2);
|
pressKeyTimes(ArrowLeft, 2);
|
||||||
enterCharacters("||");
|
enterCharacters("||");
|
||||||
pressKey(KeyType.Enter);
|
pressKey(Enter);
|
||||||
assertInputWritten("ent||er\n");
|
assertInputWritten("ent||er\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enterAfterBackspace_WritesLineToPipedStream() {
|
public void enterAfterBackspace_WritesLineToPipedStream() {
|
||||||
enterCharacters("enter");
|
enterCharacters("enter");
|
||||||
pressKeyTimes(KeyType.Backspace, 2);
|
pressKeyTimes(Backspace, 2);
|
||||||
pressKey(KeyType.Enter);
|
pressKey(Enter);
|
||||||
assertInputWritten("ent\n");
|
assertInputWritten("ent\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enterAfterDelete_WritesLineToPipedStream() {
|
public void enterAfterDelete_WritesLineToPipedStream() {
|
||||||
enterCharacters("enter");
|
enterCharacters("enter");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 2);
|
pressKeyTimes(ArrowLeft, 2);
|
||||||
pressKeyTimes(KeyType.Delete, 2);
|
pressKeyTimes(Delete, 2);
|
||||||
pressKey(KeyType.Enter);
|
pressKey(Enter);
|
||||||
assertInputWritten("ent\n");
|
assertInputWritten("ent\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,7 +350,7 @@ public class LispTerminalTest {
|
||||||
@Test
|
@Test
|
||||||
public void controlDWorksInMiddleOfInput() {
|
public void controlDWorksInMiddleOfInput() {
|
||||||
enterCharacters("control-d");
|
enterCharacters("control-d");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 2);
|
pressKeyTimes(ArrowLeft, 2);
|
||||||
enterControlCharacter('d');
|
enterControlCharacter('d');
|
||||||
assertInputStreamClosed();
|
assertInputStreamClosed();
|
||||||
assertInputWritten("control-d\n");
|
assertInputWritten("control-d\n");
|
||||||
|
@ -357,7 +358,7 @@ public class LispTerminalTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void escapeDoesNothing() {
|
public void escapeDoesNothing() {
|
||||||
pressKey(KeyType.Escape);
|
pressKey(Escape);
|
||||||
assertCursorPosition(0, 0);
|
assertCursorPosition(0, 0);
|
||||||
assertInputWritten("");
|
assertInputWritten("");
|
||||||
}
|
}
|
||||||
|
@ -371,7 +372,7 @@ public class LispTerminalTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void controlEnterDoesNothing() {
|
public void controlEnterDoesNothing() {
|
||||||
pressControlKey(KeyType.Enter);
|
pressControlKey(Enter);
|
||||||
assertCursorPosition(0, 0);
|
assertCursorPosition(0, 0);
|
||||||
assertInputWritten("");
|
assertInputWritten("");
|
||||||
}
|
}
|
||||||
|
@ -403,9 +404,9 @@ public class LispTerminalTest {
|
||||||
public void insertingTextPushesInputPastEndOfBuffer() {
|
public void insertingTextPushesInputPastEndOfBuffer() {
|
||||||
setColumns(3);
|
setColumns(3);
|
||||||
setRows(4);
|
setRows(4);
|
||||||
pressKey(KeyType.Enter);
|
pressKey(Enter);
|
||||||
enterCharacters("00011122");
|
enterCharacters("00011122");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 4);
|
pressKeyTimes(ArrowLeft, 4);
|
||||||
assertCursorPosition(1, 2);
|
assertCursorPosition(1, 2);
|
||||||
enterCharacters("zz");
|
enterCharacters("zz");
|
||||||
assertCursorPosition(0, 2);
|
assertCursorPosition(0, 2);
|
||||||
|
@ -418,7 +419,7 @@ public class LispTerminalTest {
|
||||||
setColumns(3);
|
setColumns(3);
|
||||||
setRows(3);
|
setRows(3);
|
||||||
enterCharacters("00011122");
|
enterCharacters("00011122");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 4);
|
pressKeyTimes(ArrowLeft, 4);
|
||||||
assertCursorPosition(1, 1);
|
assertCursorPosition(1, 1);
|
||||||
enterCharacters("zz");
|
enterCharacters("zz");
|
||||||
assertCursorPosition(1, 1);
|
assertCursorPosition(1, 1);
|
||||||
|
@ -454,7 +455,7 @@ public class LispTerminalTest {
|
||||||
public void printedOutputDoesNotOverwriteInput() {
|
public void printedOutputDoesNotOverwriteInput() {
|
||||||
setColumns(3);
|
setColumns(3);
|
||||||
enterCharacters("01201201");
|
enterCharacters("01201201");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 5);
|
pressKeyTimes(ArrowLeft, 5);
|
||||||
produceOutput("out");
|
produceOutput("out");
|
||||||
assertCursorPosition(0, 4);
|
assertCursorPosition(0, 4);
|
||||||
assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', '2' }, { '0', '1', 'o' },
|
assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', '2' }, { '0', '1', 'o' },
|
||||||
|
@ -465,8 +466,8 @@ public class LispTerminalTest {
|
||||||
public void printedOutputDoesNotOverwriteInput_AfterEnter() {
|
public void printedOutputDoesNotOverwriteInput_AfterEnter() {
|
||||||
setColumns(3);
|
setColumns(3);
|
||||||
enterCharacters("01201201");
|
enterCharacters("01201201");
|
||||||
pressKeyTimes(KeyType.ArrowLeft, 5);
|
pressKeyTimes(ArrowLeft, 5);
|
||||||
pressKey(KeyType.Enter);
|
pressKey(Enter);
|
||||||
produceOutput("out");
|
produceOutput("out");
|
||||||
assertCursorPosition(0, 4);
|
assertCursorPosition(0, 4);
|
||||||
assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', '2' }, { '0', '1', ' ' },
|
assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', '2' }, { '0', '1', ' ' },
|
||||||
|
@ -476,12 +477,35 @@ public class LispTerminalTest {
|
||||||
@Test
|
@Test
|
||||||
public void resizeIsHandledGracefully() {
|
public void resizeIsHandledGracefully() {
|
||||||
enterCharacters("resize");
|
enterCharacters("resize");
|
||||||
pressKey(KeyType.Enter);
|
pressKey(Enter);
|
||||||
enterCharacters("test");
|
enterCharacters("test");
|
||||||
setColumns(10);
|
setColumns(3);
|
||||||
assertCursorPosition(4, 0);
|
assertCursorPosition(1, 1);
|
||||||
assertCharacterPositions(new char[][] { { 't', 'e', 's', 't', ' ', ' ', ' ', ' ', ' ', ' ' },
|
assertCharacterPositions(new char[][] { { 't', 'e', 's' }, { 't', ' ', ' ' } });
|
||||||
{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' } });
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void backspaceWorksAfterResize() {
|
||||||
|
enterCharacters("resize");
|
||||||
|
pressKey(Enter);
|
||||||
|
enterCharacters("test");
|
||||||
|
setColumns(3);
|
||||||
|
pressKeyTimes(Backspace, 20);
|
||||||
|
assertCursorPosition(0, 0);
|
||||||
|
assertCharacterPositions(new char[][] { { ' ', ' ', ' ' }, { ' ', ' ', ' ' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteWorksAfterResize() {
|
||||||
|
enterCharacters("resize");
|
||||||
|
pressKey(Enter);
|
||||||
|
enterCharacters("test");
|
||||||
|
setColumns(3);
|
||||||
|
pressKeyTimes(ArrowLeft, 20);
|
||||||
|
pressKeyTimes(Delete, 20);
|
||||||
|
pressKeyTimes(ArrowRight, 20);
|
||||||
|
assertCursorPosition(0, 0);
|
||||||
|
assertCharacterPositions(new char[][] { { ' ', ' ', ' ' }, { ' ', ' ', ' ' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue