/*
* 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.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.krakenapps.ansicode.CursorPosCode;
import org.krakenapps.ansicode.EraseLineCode;
import org.krakenapps.ansicode.MoveCode;
import org.krakenapps.ansicode.MoveToCode;
import org.krakenapps.ansicode.EraseLineCode.Option;
import org.krakenapps.api.FunctionKeyEvent;
import org.krakenapps.api.ScriptAutoCompletion;
import org.krakenapps.api.ScriptContext;
import org.krakenapps.api.ScriptOutputStream;
import org.krakenapps.api.FunctionKeyEvent.KeyCode;
public class ConsoleController {
private ScriptContext sc;
private LinkedList<String> dataList;
private boolean hasLine;
private TelnetArrowKeyHandler arrowKeyHandler;
private ConsoleAutoComplete autoComplete;
private int cursorPos;
public ConsoleController(ScriptContext sc, ConsoleAutoComplete autoComplete) {
this.sc = sc;
dataList = new LinkedList<String>();
setCursorPos(0);
this.autoComplete = autoComplete;
}
public void addCharacter(String character) {
ScriptOutputStream out = sc.getOutputStream();
if (character.length() > 0 && (character.getBytes()[0] == (byte) 127 || character.getBytes()[0] == (byte) 8)) {
eraseCharacter(character, false);
try {
throw new Exception("asdf");
} catch (Exception e) {
e.printStackTrace();
}
} else if (character.equals("\t")) {
String input = peekLine();
doAutoCompletion(input);
} else if (character.equals("\n") || character.equals("\r")) {
hasLine = true;
if (sc.isEchoOn()) {
dataList.addLast("\r");
dataList.addLast("\n");
out.print(new EraseLineCode(Option.CursorToEnd));
Iterator<String> i = dataList.listIterator(cursorPos);
while (i.hasNext())
out.print(i.next());
}
} else {
if (character.equals("\r"))
return;
dataList.add(cursorPos, character);
increaseCursorPos();
if (sc.isEchoOn()) {
out.print(new EraseLineCode(Option.CursorToEnd));
out.print(character);
out.print(new CursorPosCode(CursorPosCode.Option.Save));
Iterator<String> i = dataList.listIterator(cursorPos);
while (i.hasNext())
out.print(i.next());
out.print(new CursorPosCode(CursorPosCode.Option.Restore));
}
}
}
private void doAutoCompletion(String input) {
ScriptOutputStream out = sc.getOutputStream();
String lastToken = getLastToken(input);
List<ScriptAutoCompletion> terms = autoComplete.search(sc.getSession(), input);
if (terms.size() > 1) {
out.print("\r\n");
for (ScriptAutoCompletion term : terms) {
out.print(term.getSuggestion());
out.print(" ");
}
out.print("\r\n");
sc.printPrompt();
String commonPrefix = extractCommonPrefix(terms);
String remainingCommonPrefix = "";
if (commonPrefix.length() >= lastToken.length())
remainingCommonPrefix = commonPrefix.substring(lastToken.length());
String semiCompletedLine = input + remainingCommonPrefix;
if (semiCompletedLine.length() != 0) {
out.print(semiCompletedLine);
}
for (int i = 0; i < remainingCommonPrefix.length(); ++i) {
dataList.add(cursorPos, Character.toString(remainingCommonPrefix.charAt(i)));
increaseCursorPos();
}
} else if (terms.size() == 1) {
String term = terms.get(0).getCompletion();
String completion = term.substring(lastToken.length());
if (completion.length() > 0) {
for (int i = 0; i < completion.length(); i++) {
dataList.add(cursorPos, Character.toString(completion.charAt(i)));
increaseCursorPos();
}
out.print(completion);
}
}
}
private String getLastToken(String input) {
String[] tokens = ScriptArgumentParser.tokenize(input);
if (tokens.length == 0)
return "";
String lastToken = "";
if (!input.endsWith(" ")) {
lastToken = tokens[tokens.length - 1];
if (tokens.length == 1) {
int p = lastToken.indexOf('.');
if (p > 0)
lastToken = lastToken.substring(p + 1);
}
}
return lastToken;
}
private String extractCommonPrefix(List<ScriptAutoCompletion> terms) {
if (terms.size() == 0)
return new String("");
else if (terms.size() == 1)
return terms.get(0).getCompletion();
else {
String commonPrefix = terms.get(0).getCompletion();
for (int i = 1; i < terms.size(); ++i) {
String rhs = terms.get(i).getCompletion();
for (int endPos = commonPrefix.length(); endPos >= 0; --endPos) {
if (endPos == 0) {
return new String("");
}
if (rhs.regionMatches(0, commonPrefix, 0, endPos)) {
commonPrefix = commonPrefix.substring(0, endPos);
break;
}
}
if (commonPrefix.length() == 0)
return commonPrefix;
}
return commonPrefix;
}
}
public boolean onArrowKeyPressed(FunctionKeyEvent event) {
ScriptOutputStream out = sc.getOutputStream();
if (arrowKeyHandler == null)
return true;
if (event.isPressed(KeyCode.UP) || event.isPressed(KeyCode.CTRL_P)) {
arrowKeyHandler.onPressUp();
return true;
} else if (event.isPressed(KeyCode.DOWN) || event.isPressed(KeyCode.CTRL_N)) {
arrowKeyHandler.onPressDown();
return true;
} else if (event.isPressed(KeyCode.LEFT) || event.isPressed(KeyCode.CTRL_B)) {
boolean handled = arrowKeyHandler.onPressLeft();
if (!handled) {
if (decreaseCursorPos())
out.print(new MoveCode(MoveCode.Direction.Left, 1));
}
return true;
} else if (event.isPressed(KeyCode.RIGHT) || event.isPressed(KeyCode.CTRL_F)) {
boolean handled = arrowKeyHandler.onPressRight();
if (!handled) {
if (increaseCursorPos())
out.print(new MoveCode(MoveCode.Direction.Right, 1));
}
return true;
} else if (event.isPressed(KeyCode.HOME) || event.isPressed(KeyCode.CTRL_A)) {
setCursorPos(0);
out.print(new MoveToCode(sc.getSession().getPrompt().length() + 1));
return true;
} else if (event.isPressed(KeyCode.END) || event.isPressed(KeyCode.CTRL_E)) {
setCursorPos(dataList.size());
out.print(new MoveToCode(sc.getSession().getPrompt().length() + 1 + dataList.size()));
return true;
}
return false;
}
public String getLine() {
StringBuilder sb = new StringBuilder(1024);
while (true) {
if (dataList.isEmpty())
break;
String character = dataList.removeFirst();
decreaseCursorPos();
if (character == null)
break;
sb.append(character);
if (character.equals("\n"))
break;
}
hasLine = false;
return sb.toString();
}
public boolean hasLine() {
return hasLine;
}
public void setLine(String line) {
ScriptOutputStream out = sc.getOutputStream();
dataList = new LinkedList<String>();
setCursorPos(0);
for (int i = 0; i < line.length(); ++i) {
dataList.add(cursorPos, new String(new char[] { line.charAt(i) }));
increaseCursorPos();
}
revertLine();
if (line.length() > 0)
out.print(line);
}
public void eraseCharacter(String character, boolean isDelete) {
ScriptOutputStream out = sc.getOutputStream();
if (dataList.isEmpty())
return;
if (isDelete) {
if (cursorPos == dataList.size())
return;
dataList.remove(cursorPos);
} else {
if (cursorPos == 0)
return;
dataList.remove(cursorPos - 1);
decreaseCursorPos();
if (sc.isEchoOn())
out.print(new MoveCode(MoveCode.Direction.Left, 1));
}
out.print(new EraseLineCode(Option.CursorToEnd));
out.print(new CursorPosCode(CursorPosCode.Option.Save));
Iterator<String> i = dataList.listIterator(cursorPos);
while (i.hasNext())
out.print(i.next());
out.print(new CursorPosCode(CursorPosCode.Option.Restore));
}
private void revertLine() {
ScriptOutputStream out = sc.getOutputStream();
out.print(new MoveToCode(0));
out.print(new EraseLineCode(Option.EntireLine));
out.print(sc.getSession().getPrompt());
}
private String peekLine() {
StringBuilder sb = new StringBuilder(1024);
Iterator<String> iter = dataList.iterator();
while (iter.hasNext()) {
sb.append(iter.next());
}
return sb.toString();
}
public void setArrowKeyHandler(TelnetArrowKeyHandler arrowKeyHandler) {
this.arrowKeyHandler = arrowKeyHandler;
}
public TelnetArrowKeyHandler getArrowKeyHandler() {
return this.arrowKeyHandler;
}
private void setCursorPos(int newPos) {
cursorPos = newPos;
}
private boolean increaseCursorPos() {
if (cursorPos + 1 <= dataList.size()) {
cursorPos++;
return true;
} else
return false;
}
private boolean decreaseCursorPos() {
if (cursorPos - 1 >= 0) {
cursorPos--;
return true;
} else
return false;
}
public boolean onFunctionKeyPressed(FunctionKeyEvent ev) {
switch (ev.getKeyCode()) {
case CTRL_U:
setLine("");
return true;
case DELETE:
eraseCharacter("", true);
return true;
case BACKSPACE:
eraseCharacter("", false);
return true;
}
return onArrowKeyPressed(ev);
}
public void onCharacterInput(String message) {
if (getArrowKeyHandler() != null) {
getArrowKeyHandler().onOtherKeyPressed();
}
addCharacter((String) message);
}
}