-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Trygve Laugstøl
committed
Mar 5, 2010
0 parents
commit c84c140
Showing
16 changed files
with
1,082 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
*~ | ||
catalog.xml | ||
.classpath | ||
hostkey.ser | ||
.idea | ||
*.iml | ||
*.ipr | ||
*.iws | ||
*.log | ||
nbactions.xml | ||
nbproject | ||
private | ||
profiles.xml | ||
.project | ||
.settings | ||
.svn | ||
target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<project> | ||
<modelVersion>4.0.0</modelVersion> | ||
<groupId>no.hackaton.termos</groupId> | ||
<artifactId>termos</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
<dependencies> | ||
<dependency> | ||
<groupId>org.apache.sshd</groupId> | ||
<artifactId>sshd-core</artifactId> | ||
<version>${version.sshd}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<version>4.7</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.slf4j</groupId> | ||
<artifactId>slf4j-simple</artifactId> | ||
<version>${version.slf4j}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
<build> | ||
<plugins> | ||
<plugin> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<configuration> | ||
<source>1.5</source> | ||
<target>1.5</target> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-resources-plugin</artifactId> | ||
<configuration> | ||
<encoding>UTF-8</encoding> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
<properties> | ||
<version.sshd>0.3.0</version.sshd> | ||
<version.slf4j>1.5.8</version.slf4j> | ||
</properties> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package no.hackaton.termos; | ||
|
||
import java.util.*; | ||
|
||
/** | ||
* @author <a href="mailto:[email protected]">Trygve Laugstøl</a> | ||
* @version $Id$ | ||
*/ | ||
public interface Completer { | ||
List<String> complete(String string); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package no.hackaton.termos; | ||
|
||
import java.util.*; | ||
|
||
/** | ||
* @author <a href="mailto:[email protected]">Trygve Laugstøl</a> | ||
* @version $Id$ | ||
*/ | ||
public class NoCompleter implements Completer { | ||
public static final Completer noCompleter = new NoCompleter(); | ||
|
||
public List<String> complete(String string) { | ||
return Collections.singletonList(string); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
package no.hackaton.termos; | ||
|
||
import static java.lang.Character.*; | ||
import static java.lang.Character.valueOf; | ||
import static java.lang.Integer.*; | ||
import static no.hackaton.termos.NoCompleter.*; | ||
import static no.hackaton.termos.ReadlineUtil.closeSilently; | ||
import org.apache.sshd.common.*; | ||
import org.apache.sshd.server.*; | ||
|
||
import java.io.*; | ||
import java.util.*; | ||
|
||
/** | ||
* TODO: Look into CharsetDecoder to decode the data on the fly and echo every successfully read byte back to the client. | ||
* <p/> | ||
* TODO: Support history | ||
* TODO: Support tab-completion | ||
* | ||
* @author <a href="mailto:[email protected]">Trygve Laugstøl</a> | ||
* @version $Id$ | ||
*/ | ||
@SuppressWarnings({"OctalInteger"}) | ||
public class ReadLine implements Closeable { | ||
public static final byte ETX = 003; // Ctrl-c | ||
public static final byte BS = 010; | ||
public static final byte TAB = 011; | ||
public static final byte FF = 014; | ||
public static final byte ESC = 033; | ||
public static final byte SPACE = 040; | ||
public static final byte BEL = 0007; | ||
|
||
public static final byte[] clearEol = {ESC, '[', 'K'}; | ||
public static final byte[] cursorHome = {ESC, '[', 'H'}; | ||
public static final byte[] cursorUp = {ESC, '[', 'A'}; | ||
public static final byte[] cursorDn = {ESC, '[', 'B'}; | ||
public static final byte[] cursorRt = {ESC, '[', 'C'}; | ||
public static final byte[] cursorLf = {ESC, '[', 'D'}; | ||
public static final byte[] clearScreenEd2 = {ESC, '[', '2', 'J'}; | ||
|
||
private final InputStream inputStream; | ||
private final OutputStream outputStream; | ||
|
||
List<Character> chars = new ArrayList<Character>(); | ||
int position = 0; | ||
private byte erase; | ||
|
||
public ReadLine(InputStream inputStream, OutputStream outputStream, Environment environment) { | ||
this.inputStream = inputStream; | ||
this.outputStream = outputStream; | ||
|
||
Integer erase = environment.getPtyModes().get(PtyMode.VERASE); | ||
this.erase = erase == null ? 0x7f : erase.byteValue(); | ||
} | ||
|
||
public int getPosition() { | ||
return position; | ||
} | ||
|
||
public String readLine() { | ||
return readLine("", noCompleter); | ||
} | ||
|
||
public String readLine(String prompt) { | ||
return readLine(prompt, noCompleter); | ||
} | ||
|
||
public String readLine(String prompt, Completer completer) { | ||
try { | ||
return doRead(prompt, completer); | ||
} catch (IOException e) { | ||
return null; | ||
} | ||
} | ||
|
||
private String doRead(String prompt, Completer completer) throws IOException { | ||
int b; | ||
int tabCount = 0; | ||
|
||
print(prompt); | ||
outputStream.flush(); | ||
|
||
// TODO: This should read all available bytes | ||
|
||
do { | ||
b = tryNext(); | ||
|
||
if (b == -1) { | ||
break; | ||
} | ||
|
||
if (0x20 <= b && b < 0x7f) { | ||
outputStream.write(b); | ||
outputStream.flush(); | ||
|
||
if (isEndOfLine()) { | ||
chars.add((char) b); | ||
position++; | ||
} else { | ||
chars.set(position++, (char) (0xff & b)); | ||
} | ||
} | ||
|
||
if (b == '\r') { | ||
// This seems to be correct, at least if ONLCR=1 | ||
outputStream.write('\r'); | ||
outputStream.write('\n'); | ||
outputStream.flush(); | ||
break; | ||
} | ||
|
||
if (b == erase) { | ||
if (position > 0) { | ||
position--; | ||
chars.remove(position); | ||
outputStream.write(BS); | ||
outputStream.write(clearEol); | ||
} | ||
} else if (b == FF) { | ||
outputStream.write(cursorHome); | ||
outputStream.write(clearScreenEd2); | ||
print(prompt); | ||
for (Character c : chars) { | ||
// TODO: Encode | ||
outputStream.write((byte) c.charValue()); | ||
} | ||
} else if (b == ESC) { | ||
b = requireNext(); | ||
|
||
if (b == '[') { | ||
b = requireNext(); | ||
if (b == 'A' || b == 'B') { // Up || down | ||
// ignore | ||
} else if (b == 'C') { // Right | ||
if (isEndOfLine()) { | ||
position++; | ||
outputStream.write(cursorRt); | ||
} | ||
} else if (b == 'D') { // Left | ||
if (position > 0) { | ||
position--; | ||
outputStream.write(cursorLf); | ||
} | ||
} | ||
} else if (b == 'b') { // backward-word | ||
int i = findStartOfWord(chars, position); | ||
|
||
int count = position - i; | ||
for (int x = 0; x < count; x++) { | ||
outputStream.write(cursorLf); | ||
} | ||
position = position - i; | ||
} else if (b == 'f') { // forward-word | ||
int i = findEndOfWord(chars, position); | ||
|
||
int count = position - i; | ||
for (int x = 0; x < count; x++) { | ||
outputStream.write(cursorLf); | ||
} | ||
position = position - i; | ||
} | ||
} else if (b == TAB && tabCount++ == 1) { | ||
tabCount = 0; | ||
|
||
String currentLine = charsToString(); | ||
|
||
List<String> options = completer.complete(currentLine); | ||
|
||
outputStream.write('\r'); | ||
if (options.size() == 1) { | ||
String match = options.get(0); | ||
int length = match.length(); | ||
chars = new ArrayList<Character>(length); | ||
for (int i = 0; i < length; i++) { | ||
chars.add(match.charAt(i)); | ||
} | ||
// Add an extra space after the match to be ready to write arguments to the command | ||
chars.add(' '); | ||
} else { | ||
outputStream.write('\n'); | ||
|
||
println("Got " + options.size() + " matches:"); | ||
for (String option : options) { | ||
println(option); | ||
} | ||
} | ||
print(prompt + charsToString()); | ||
} else if (b == ETX) { | ||
tabCount = 0; | ||
println(""); | ||
chars = new ArrayList<Character>(); | ||
print(prompt); | ||
} | ||
outputStream.flush(); | ||
} while (true); | ||
|
||
// TODO: Decode | ||
return charsToString(); | ||
} | ||
|
||
private String charsToString() { | ||
StringBuffer buffer = new StringBuffer(chars.size()); | ||
for (Character c : chars) { | ||
buffer.append(valueOf(c)); | ||
} | ||
return buffer.toString(); | ||
} | ||
|
||
private boolean isEndOfLine() { | ||
return position == chars.size(); | ||
} | ||
|
||
private int tryNext() throws IOException { | ||
int b = inputStream.read(); | ||
if (b == -1) { | ||
return -1; | ||
} | ||
System.out.println("b = 0x" + (b < 16 ? "0" : "") + toHexString(b)); | ||
return b; | ||
} | ||
|
||
private int requireNext() throws IOException { | ||
int b = inputStream.read(); | ||
if (b == -1) { | ||
throw new IOException("Unexpected EOF."); | ||
} | ||
System.out.println("b = 0x" + (b < 16 ? "0" : "") + toHexString(b)); | ||
return b; | ||
} | ||
|
||
public void close() throws IOException { | ||
closeSilently(inputStream); | ||
} | ||
|
||
public static int findStartOfWord(List<Character> chars, int position) { | ||
// int i = position; | ||
return 0; | ||
} | ||
|
||
public static int findEndOfWord(List<Character> chars, int position) { | ||
// Find first word | ||
int i = findStartOfWord(chars, position); | ||
int end = chars.size(); | ||
|
||
for (; i < end; i++) { | ||
Character c = chars.get(i); | ||
if (!isLetter(c) && !isDigit(c)) { | ||
return i; | ||
} | ||
} | ||
|
||
return i; | ||
} | ||
|
||
// ----------------------------------------------------------------------- | ||
// Output | ||
// ----------------------------------------------------------------------- | ||
|
||
private byte[] toBytes(String s) { | ||
// TODO: Encode | ||
return s.getBytes(); | ||
} | ||
|
||
public void print(String s) throws IOException { | ||
outputStream.write(toBytes(s)); | ||
} | ||
|
||
public void println(String s) throws IOException { | ||
outputStream.write(toBytes(s)); | ||
outputStream.write('\r'); | ||
outputStream.write('\n'); | ||
} | ||
|
||
/** | ||
* Sets the prompt of the terminal. | ||
*/ | ||
public void sendPrompt(String s) throws IOException { | ||
outputStream.write(ESC); | ||
outputStream.write(toBytes("]0;")); // TODO: This should be a byte array | ||
outputStream.write(toBytes(s)); | ||
outputStream.write(BEL); | ||
outputStream.flush(); | ||
} | ||
} |
Oops, something went wrong.