/* * Copyright 2015 Julien Viet * * Licensed 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 io.termd.core.readline; import io.termd.core.readline.functions.BackwardChar; import io.termd.core.readline.functions.BackwardDeleteChar; import io.termd.core.readline.functions.BackwardKillWord; import io.termd.core.readline.functions.BackwardWord; import io.termd.core.readline.functions.BeginningOfLine; import io.termd.core.readline.functions.Complete; import io.termd.core.readline.functions.DeleteChar; import io.termd.core.readline.functions.EndOfLine; import io.termd.core.readline.functions.ForwardChar; import io.termd.core.readline.functions.ForwardWord; import io.termd.core.readline.functions.KillLine; import io.termd.core.readline.functions.NextHistory; import io.termd.core.readline.functions.PreviousHistory; import io.termd.core.TestBase; import io.termd.core.tty.TtyConnection; import io.termd.core.tty.TtyEvent; import io.termd.core.tty.TtyOutputMode; import io.termd.core.util.Vector; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; /** * @author <a href="mailto:julien@julienviet.com">Julien Viet</a> */ class TestTerm { private TestBase readlineTest; private int[][] buffer = new int[10][]; private int row; private int cursor; private int status = 0; private int acc = -1; private int bell; private int width = 40; Consumer<int[]> writeHandler = new Consumer<int[]>() { @Override public void accept(int[] event) { for (int i : event) { if (buffer[row] == null) { buffer[row] = new int[100]; } switch (status) { case 0: if (i >= 32) { buffer[row][cursor] = i; forward(); } else { switch (i) { case 7: bell++; break; case '\r': cursor = 0; break; case '\n': row++; break; case 27: status = 1; break; case '\b': backward(); break; } } break; case 1: if (i == '[') { status = 2; } else { throw new UnsupportedOperationException(); } break; case 2: if (i >= '0' && i <= '9') { if (acc == -1) { acc = i - '0'; } else { acc = acc * 10 + (i - '0'); } } else { switch (i) { case 'A': while (acc-- > 0 && row > 0) { row--; } break; case 'B': while (acc-- > 0 && row < buffer.length) { row++; } break; case 'C': while (acc-- > 0) { forward(); } break; case 'D': while (acc-- > 0) { backward(); } break; case 'K': { if (acc != -1) { throw new UnsupportedOperationException("Not yet implemented"); } else { for (int j = cursor;j < buffer[row].length;j++) { buffer[row][j] = 0; } } break; } default: throw new UnsupportedOperationException("Implement escape sequence " + i); } acc = -1; status = 0; } break; default: throw new UnsupportedOperationException("Unsupported cp=" + i + " with status=" + status); } } } private void backward() { if (cursor > 0) { cursor--; } else { throw new UnsupportedOperationException(); } } private void forward() { cursor++; if (cursor == width) { cursor = 0; row++; } } }; final Consumer<int[]> stdout = new TtyOutputMode(writeHandler); final Readline readline; Consumer<int[]> readHandler; Consumer<Vector> sizeHandler; BiConsumer<TtyEvent, Integer> eventHandler; private LinkedList<Runnable> tasks = new LinkedList<>(); TtyConnection conn = new TtyConnection() { @Override public Charset inputCharset() { return StandardCharsets.UTF_8; } @Override public Charset outputCharset() { return StandardCharsets.UTF_8; } @Override public long lastAccessedTime() { return 0; } @Override public String terminalType() { return "xterm"; } @Override public Vector size() { return new Vector(width, 20); } @Override public Consumer<String> getTerminalTypeHandler() { throw new UnsupportedOperationException(); } @Override public void setTerminalTypeHandler(Consumer<String> handler) { throw new UnsupportedOperationException(); } @Override public Consumer<Vector> getSizeHandler() { return sizeHandler; } @Override public void setSizeHandler(Consumer<Vector> handler) { sizeHandler = handler; if (handler != null) { handler.accept(new Vector(40, 20)); } } @Override public BiConsumer<TtyEvent, Integer> getEventHandler() { return eventHandler; } @Override public void setEventHandler(BiConsumer<TtyEvent, Integer> handler) { eventHandler = handler; } @Override public Consumer<int[]> getStdinHandler() { return readHandler; } @Override public void setStdinHandler(Consumer<int[]> handler) { readHandler = handler; } @Override public Consumer<int[]> stdoutHandler() { return stdout; } @Override public void execute(Runnable task) { tasks.add(task); } @Override public void schedule(Runnable task, long delay, TimeUnit unit) { throw new UnsupportedOperationException(); } @Override public void setCloseHandler(Consumer<Void> closeHandler) { throw new UnsupportedOperationException(); } @Override public Consumer<Void> getCloseHandler() { throw new UnsupportedOperationException(); } @Override public void close() { throw new UnsupportedOperationException(); } }; public TestTerm(TestBase test) { this.readlineTest = test; Keymap keymap = InputrcParser.create(); readline = new Readline(keymap); readline.addFunction(new BackwardDeleteChar()); readline.addFunction(new BackwardChar()); readline.addFunction(new ForwardChar()); readline.addFunction(new PreviousHistory()); readline.addFunction(new NextHistory()); readline.addFunction(new BeginningOfLine()); readline.addFunction(new EndOfLine()); readline.addFunction(new DeleteChar()); readline.addFunction(new Complete()); readline.addFunction(new KillLine()); readline.addFunction(new BackwardWord()); readline.addFunction(new ForwardWord()); readline.addFunction(new BackwardKillWord()); } public void readlineFail() { readline(event -> readlineTest.fail("Was not accepting a call")); } public Supplier<String> readlineComplete() { final AtomicReference<String> queue = new AtomicReference<>(); readline(event -> queue.compareAndSet(null, event)); return () -> queue.get(); } public void readline(Consumer<String> readlineHandler) { readline(readlineHandler, null); } public Supplier<String> readlineComplete(Consumer<Completion> completionHandler) { final AtomicReference<String> queue = new AtomicReference<>(); readline(event -> queue.compareAndSet(null, event), completionHandler); return () -> queue.get(); } public void readline(Consumer<String> readlineHandler, Consumer<Completion> completionHandler) { readline.readline(conn, "% ", readlineHandler, completionHandler); } public void executeTasks() { while (!tasks.isEmpty()) { Runnable task = tasks.removeFirst(); task.run(); } } public int getBellCount() { return bell; } public void resetBellCount() { bell = 0; } private List<String> render() { List<String> lines = new ArrayList<>(); for (int[] row : buffer) { if (row == null) { break; } StringBuilder line = new StringBuilder(); for (int codePoint : row) { if (codePoint < 32) { break; } line.appendCodePoint(codePoint); } lines.add(line.toString()); } return lines; } void assertScreen(String... expected) { List<String> lines = render(); readlineTest.assertEquals(Arrays.asList(expected), lines); } void assertAt(int row, int cursor) { readlineTest.assertEquals(row, this.row); readlineTest.assertEquals(cursor, this.cursor); } void read(int... data) { readHandler.accept(data); } TestTerm setWidth(int width) { this.width = width; if (sizeHandler != null) { sizeHandler.accept(conn.size()); } return this; } }