All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jline.UnixTerminal Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved.
 *
 * This software is distributable under the BSD license. See the terms of the
 * BSD license in the documentation provided with this software.
 */
package jline;

import java.io.*;
import java.util.*;

/**
 *  

* Terminal that is used for unix platforms. Terminal initialization * is handled by issuing the stty command against the * /dev/tty file to disable character echoing and enable * character input. All known unix systems (including * Linux and Macintosh OS X) support the stty), so this * implementation should work for an reasonable POSIX system. *

* * @author Marc Prud'hommeaux * @author Updates Dale Kemp 2005-12-03 */ public class UnixTerminal extends Terminal { 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 Map terminfo; private boolean echoEnabled; private String ttyConfig; private boolean backspaceDeleteSwitched = false; private static String sttyCommand = System.getProperty("jline.sttyCommand", "stty"); String encoding = System.getProperty("input.encoding", "UTF-8"); ReplayPrefixOneCharInputStream replayStream = new ReplayPrefixOneCharInputStream(encoding); InputStreamReader replayReader; public UnixTerminal() { try { replayReader = new InputStreamReader(replayStream, encoding); } catch (Exception e) { throw new RuntimeException(e); } } protected void checkBackspace(){ String[] ttyConfigSplit = ttyConfig.split(":|="); if (ttyConfigSplit.length < 7) return; if (ttyConfigSplit[6] == null) return; backspaceDeleteSwitched = ttyConfigSplit[6].equals("7f"); } /** * Remove line-buffered input by invoking "stty -icanon min 1" * against the current terminal. */ public void initializeTerminal() throws IOException, InterruptedException { // save the initial tty configuration ttyConfig = stty("-g"); // sanity check if ((ttyConfig.length() == 0) || ((ttyConfig.indexOf("=") == -1) && (ttyConfig.indexOf(":") == -1))) { throw new IOException("Unrecognized stty code: " + ttyConfig); } checkBackspace(); // set the console to be character-buffered instead of line-buffered stty("-icanon min 1"); // disable character echoing stty("-echo"); echoEnabled = false; // at exit, restore the original tty configuration (for JDK 1.3+) try { Runtime.getRuntime().addShutdownHook(new Thread() { public void start() { try { restoreTerminal(); } catch (Exception e) { consumeException(e); } } }); } catch (AbstractMethodError ame) { // JDK 1.3+ only method. Bummer. consumeException(ame); } } /** * Restore the original terminal configuration, which can be used when * shutting down the console reader. The ConsoleReader cannot be * used after calling this method. */ public void restoreTerminal() throws Exception { if (ttyConfig != null) { stty(ttyConfig); ttyConfig = null; } resetTerminal(); } 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 [email protected] if (c > 128) { // handle unicode characters longer than 2 bytes, // thanks to [email protected] replayStream.setInput(c, in); // replayReader = new InputStreamReader(replayStream, encoding); c = replayReader.read(); } return c; } /** * No-op for exceptions we want to silently consume. */ private void consumeException(Throwable e) { } public boolean isSupported() { return true; } public boolean getEcho() { return false; } /** * Returns the value of "stty size" width param. * * Note: this method caches the value from the * first time it is called in order to increase speed, which means * that changing to size of the terminal will not be reflected * in the console. */ public int getTerminalWidth() { int val = -1; try { val = getTerminalProperty("columns"); } catch (Exception e) { } if (val == -1) { val = 80; } return val; } /** * Returns the value of "stty size" height param. * * Note: this method caches the value from the * first time it is called in order to increase speed, which means * that changing to size of the terminal will not be reflected * in the console. */ public int getTerminalHeight() { int val = -1; try { val = getTerminalProperty("rows"); } catch (Exception e) { } if (val == -1) { val = 24; } return val; } private static int getTerminalProperty(String prop) throws IOException, InterruptedException { // need to be able handle both output formats: // speed 9600 baud; 24 rows; 140 columns; // and: // speed 38400 baud; rows = 49; columns = 111; ypixels = 0; xpixels = 0; String props = stty("-a"); for (StringTokenizer tok = new StringTokenizer(props, ";\n"); tok.hasMoreTokens();) { String str = tok.nextToken().trim(); if (str.startsWith(prop)) { int index = str.lastIndexOf(" "); return Integer.parseInt(str.substring(index).trim()); } else if (str.endsWith(prop)) { int index = str.indexOf(" "); return Integer.parseInt(str.substring(0, index).trim()); } } return -1; } /** * Execute the stty command with the specified arguments * against the current active terminal. */ private static String stty(final String args) throws IOException, InterruptedException { return exec("stty " + args + " < /dev/tty").trim(); } /** * Execute the specified command and return the output * (both stdout and stderr). */ private static String exec(final String cmd) throws IOException, InterruptedException { return exec(new String[] { "sh", "-c", cmd }); } /** * Execute the specified command and return the output * (both stdout and stderr). */ private static String exec(final String[] cmd) throws IOException, InterruptedException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); Process p = Runtime.getRuntime().exec(cmd); int c; InputStream in; in = p.getInputStream(); while ((c = in.read()) != -1) { bout.write(c); } in = p.getErrorStream(); while ((c = in.read()) != -1) { bout.write(c); } p.waitFor(); String result = new String(bout.toByteArray()); return result; } /** * The command to use to set the terminal options. Defaults * to "stty", or the value of the system property "jline.sttyCommand". */ public static void setSttyCommand(String cmd) { sttyCommand = cmd; } /** * The command to use to set the terminal options. Defaults * to "stty", or the value of the system property "jline.sttyCommand". */ public static String getSttyCommand() { return sttyCommand; } public synchronized boolean isEchoEnabled() { return echoEnabled; } public synchronized void enableEcho() { try { stty("echo"); echoEnabled = true; } catch (Exception e) { consumeException(e); } } public synchronized void disableEcho() { try { stty("-echo"); echoEnabled = false; } catch (Exception e) { consumeException(e); } } /** * This is awkward and inefficient, but probably the minimal way to add * UTF-8 support to JLine * * @author Marc Herbert */ 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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy