/* * Copyright 2009 NCHOVY * * 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 org.krakenapps.console; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import org.krakenapps.ansicode.CursorPosCode; import org.krakenapps.ansicode.EraseLineCode; import org.krakenapps.ansicode.MoveCode; import org.krakenapps.ansicode.EraseLineCode.Option; import org.krakenapps.ansicode.MoveCode.Direction; import org.krakenapps.api.FunctionKeyEvent; import org.krakenapps.api.FunctionKeyEventListener; import org.krakenapps.api.ScriptContext; import org.krakenapps.api.ScriptInputStream; import org.krakenapps.api.ScriptOutputStream; import org.krakenapps.api.FunctionKeyEvent.KeyCode; public class ConsoleInputStream implements ScriptInputStream { private BlockingQueue<Character> buffer; private ScriptContext context; private Set<FunctionKeyEventListener> callbacks; public ConsoleInputStream(ScriptContext context) { this.buffer = new LinkedBlockingQueue<Character>(); this.context = context; this.callbacks = new HashSet<FunctionKeyEventListener>(); } @Override public void supplyInput(char character) { buffer.offer(character); } @Override public void supplyFunctionKey(FunctionKeyEvent keyEvent) { KeyCode c = keyEvent.getKeyCode(); if (c == KeyCode.CTRL_C || c == KeyCode.CTRL_D) buffer.offer((char) 27); for (FunctionKeyEventListener callback : callbacks) callback.keyPressed(keyEvent); } @Override public char read() throws InterruptedException { Character character = buffer.take(); if (character.charValue() == 27) { throw new InterruptedException(); } if (context.isEchoOn()) { printEcho(character); if (character == '\r') printEcho('\n'); } return character; } @Override public String readLine() throws InterruptedException { ReadLineHandler handler = new ReadLineHandler(); try { addFunctionKeyEventListener(handler); return handler.getLine(); } finally { removeFunctionKeyEventListener(handler); } } private boolean isBackspace(char character) { if (character == 127 || character == 8) return true; return false; } @Override public void flush() { buffer.clear(); } @Override public void flush(Collection<Character> drain) { buffer.drainTo(drain); } private void printEcho(char c) { ScriptOutputStream outputStream = context.getOutputStream(); if (isBackspace(c)) { outputStream.print(Character.toString('\b')); } else outputStream.print(Character.toString(c)); } @Override public void addFunctionKeyEventListener(FunctionKeyEventListener callback) { if (callback == null) throw new IllegalArgumentException("callback must be not null"); callbacks.add(callback); } @Override public void removeFunctionKeyEventListener(FunctionKeyEventListener callback) { if (callback == null) throw new IllegalArgumentException("callback must be not null"); callbacks.remove(callback); } private class ReadLineHandler implements FunctionKeyEventListener { private StringBuilder builder; private int cursorIndex = 0; public String getLine() throws InterruptedException { ScriptOutputStream out = context.getOutputStream(); builder = new StringBuilder(514); // noMoreBackspaceEcho = true; cursorIndex = 0; while (true) { char c = read(); if (c != '\r' && c != '\n') { if (cursorIndex < builder.length()) { out.print(builder.toString().substring(cursorIndex)); out.print(new MoveCode(Direction.Left, builder.length() - cursorIndex)); builder.insert(cursorIndex, c); } else { builder.append(c); } cursorIndex++; } if (c == '\r' || c == '\n') { Character next = buffer.peek(); if (next != null && c == '\r' && next == '\n') read(); // remove \n return builder.toString(); } } } @Override public void keyPressed(FunctionKeyEvent e) { if (e.getKeyCode() == KeyCode.LEFT) { if (cursorIndex > 0) { cursorIndex--; context.getOutputStream().print(new MoveCode(MoveCode.Direction.Left, 1)); } } else if (e.getKeyCode() == KeyCode.RIGHT) { if (builder != null && builder.length() > cursorIndex) { cursorIndex++; context.getOutputStream().print(new MoveCode(MoveCode.Direction.Right, 1)); } } else if (e.getKeyCode() == KeyCode.BACKSPACE) { eraseCharacter(true); } else if (e.getKeyCode() == KeyCode.DELETE) { eraseCharacter(false); } else if (e.getKeyCode() == KeyCode.CTRL_C || e.getKeyCode() == KeyCode.CTRL_D) { buffer.offer((char) 27); } } private void eraseCharacter(boolean isBackspace) { if (isBackspace) { if (builder != null && cursorIndex > 0) { builder.deleteCharAt(cursorIndex - 1); cursorIndex--; ScriptOutputStream out = context.getOutputStream(); if (context.isEchoOn()) { out.print(new MoveCode(MoveCode.Direction.Left, 1)); out.print(new EraseLineCode(Option.CursorToEnd)); String remain = builder.substring(cursorIndex); if (remain.length() != 0) { out.print(new CursorPosCode(CursorPosCode.Option.Save)); out.print(builder.substring(cursorIndex)); out.print(new CursorPosCode(CursorPosCode.Option.Restore)); } } } } else { if (builder != null && cursorIndex < builder.length()) { builder.deleteCharAt(cursorIndex); ScriptOutputStream out = context.getOutputStream(); if (context.isEchoOn()) { out.print(new EraseLineCode(Option.CursorToEnd)); String remain = builder.substring(cursorIndex); if (remain.length() != 0) { out.print(new CursorPosCode(CursorPosCode.Option.Save)); out.print(builder.substring(cursorIndex)); out.print(new CursorPosCode(CursorPosCode.Option.Restore)); } } } } } } }