Fix ssh terminal completion and cursor handling
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@801922 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/gshell/gshell-ssh/src/main/java/org/apache/felix/karaf/gshell/ssh/SshTerminal.java b/karaf/gshell/gshell-ssh/src/main/java/org/apache/felix/karaf/gshell/ssh/SshTerminal.java
index 601c6b0..da71d2c 100644
--- a/karaf/gshell/gshell-ssh/src/main/java/org/apache/felix/karaf/gshell/ssh/SshTerminal.java
+++ b/karaf/gshell/gshell-ssh/src/main/java/org/apache/felix/karaf/gshell/ssh/SshTerminal.java
@@ -18,16 +18,42 @@
*/
package org.apache.felix.karaf.gshell.ssh;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
import jline.Terminal;
import org.apache.sshd.server.ShellFactory;
public class SshTerminal extends Terminal implements ShellFactory.SignalListener {
+ public static final short ARROW_START = 27;
+ public static final short ARROW_PREFIX = 91;
+ public static final short ARROW_LEFT = 68;
+ public static final short ARROW_RIGHT = 67;
+ public static final short ARROW_UP = 65;
+ public static final short ARROW_DOWN = 66;
+ public static final short O_PREFIX = 79;
+ public static final short HOME_CODE = 72;
+ public static final short END_CODE = 70;
+
+ public static final short DEL_THIRD = 51;
+ public static final short DEL_SECOND = 126;
+
private ShellFactory.Environment environment;
+ private boolean backspaceDeleteSwitched = false;
+ private String encoding = System.getProperty("input.encoding", "UTF-8");
+ private ReplayPrefixOneCharInputStream replayStream = new ReplayPrefixOneCharInputStream(encoding);
+ private InputStreamReader replayReader;
public SshTerminal(ShellFactory.Environment environment) {
this.environment = environment;
this.environment.addSignalListener(this);
+ try {
+ replayReader = new InputStreamReader(replayStream, encoding);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
public void initializeTerminal() throws Exception {
@@ -65,4 +91,129 @@
public void signal(int signal) {
}
+
+ public int readVirtualKey(InputStream in) throws IOException {
+ int c = readCharacter(in);
+
+ if (backspaceDeleteSwitched)
+ if (c == DELETE)
+ c = '\b';
+ else if (c == '\b')
+ c = DELETE;
+
+ // in Unix terminals, arrow keys are represented by
+ // a sequence of 3 characters. E.g., the up arrow
+ // key yields 27, 91, 68
+ if (c == ARROW_START) {
+ //also the escape key is 27
+ //thats why we read until we
+ //have something different than 27
+ //this is a bugfix, because otherwise
+ //pressing escape and than an arrow key
+ //was an undefined state
+ while (c == ARROW_START)
+ c = readCharacter(in);
+ if (c == ARROW_PREFIX || c == O_PREFIX) {
+ c = readCharacter(in);
+ if (c == ARROW_UP) {
+ return CTRL_P;
+ } else if (c == ARROW_DOWN) {
+ return CTRL_N;
+ } else if (c == ARROW_LEFT) {
+ return CTRL_B;
+ } else if (c == ARROW_RIGHT) {
+ return CTRL_F;
+ } else if (c == HOME_CODE) {
+ return CTRL_A;
+ } else if (c == END_CODE) {
+ return CTRL_E;
+ } else if (c == DEL_THIRD) {
+ c = readCharacter(in); // read 4th
+ return DELETE;
+ }
+ }
+ }
+ // handle unicode characters, thanks for a patch from amyi@inf.ed.ac.uk
+ if (c > 128) {
+ // handle unicode characters longer than 2 bytes,
+ // thanks to Marc.Herbert@continuent.com
+ replayStream.setInput(c, in);
+// replayReader = new InputStreamReader(replayStream, encoding);
+ c = replayReader.read();
+
+ }
+
+ return c;
+ }
+
+ /**
+ * This is awkward and inefficient, but probably the minimal way to add
+ * UTF-8 support to JLine
+ *
+ * @author <a href="mailto:Marc.Herbert@continuent.com">Marc Herbert</a>
+ */
+ static class ReplayPrefixOneCharInputStream extends InputStream {
+ byte firstByte;
+ int byteLength;
+ InputStream wrappedStream;
+ int byteRead;
+
+ final String encoding;
+
+ public ReplayPrefixOneCharInputStream(String encoding) {
+ this.encoding = encoding;
+ }
+
+ public void setInput(int recorded, InputStream wrapped) throws IOException {
+ this.byteRead = 0;
+ this.firstByte = (byte) recorded;
+ this.wrappedStream = wrapped;
+
+ byteLength = 1;
+ if (encoding.equalsIgnoreCase("UTF-8"))
+ setInputUTF8(recorded, wrapped);
+ else if (encoding.equalsIgnoreCase("UTF-16"))
+ byteLength = 2;
+ else if (encoding.equalsIgnoreCase("UTF-32"))
+ byteLength = 4;
+ }
+
+
+ public void setInputUTF8(int recorded, InputStream wrapped) throws IOException {
+ // 110yyyyy 10zzzzzz
+ if ((firstByte & (byte) 0xE0) == (byte) 0xC0)
+ this.byteLength = 2;
+ // 1110xxxx 10yyyyyy 10zzzzzz
+ else if ((firstByte & (byte) 0xF0) == (byte) 0xE0)
+ this.byteLength = 3;
+ // 11110www 10xxxxxx 10yyyyyy 10zzzzzz
+ else if ((firstByte & (byte) 0xF8) == (byte) 0xF0)
+ this.byteLength = 4;
+ else
+ throw new IOException("invalid UTF-8 first byte: " + firstByte);
+ }
+
+ public int read() throws IOException {
+ if (available() == 0)
+ return -1;
+
+ byteRead++;
+
+ if (byteRead == 1)
+ return firstByte;
+
+ return wrappedStream.read();
+ }
+
+ /**
+ * InputStreamReader is greedy and will try to read bytes in advance. We
+ * do NOT want this to happen since we use a temporary/"losing bytes"
+ * InputStreamReader above, that's why we hide the real
+ * wrappedStream.available() here.
+ */
+ public int available() {
+ return byteLength - byteRead;
+ }
+ }
+
}
diff --git a/karaf/gshell/gshell-ssh/src/main/resources/OSGI-INF/blueprint/gshell-ssh.xml b/karaf/gshell/gshell-ssh/src/main/resources/OSGI-INF/blueprint/gshell-ssh.xml
index e393a00..d1df80b 100644
--- a/karaf/gshell/gshell-ssh/src/main/resources/OSGI-INF/blueprint/gshell-ssh.xml
+++ b/karaf/gshell/gshell-ssh/src/main/resources/OSGI-INF/blueprint/gshell-ssh.xml
@@ -90,7 +90,8 @@
<reference id="commandProcessor" interface="org.osgi.service.command.CommandProcessor">
</reference>
- <reference-list id="functions" filter="(&(osgi.command.scope=*)(osgi.command.function=*))" availability="optional">
+ <reference-list id="functions" filter="(&(osgi.command.scope=*)(osgi.command.function=*))"
+ availability="optional" activation="eager">
<reference-listener ref="commandCompleter"
bind-method="register"
unbind-method="unregister"/>