blob: d0759318d9281701c106f53b86a3a382f169d1e7 [file] [log] [blame]
/*
* Copyright (c) 2002-2012, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* http://www.opensource.org/licenses/bsd-license.php
*/
package jdk.internal.jline.console;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jdk.internal.jline.internal.Log;
/**
* @author St\u00E5le W. Pedersen <stale.pedersen@jboss.org>
*/
public class ConsoleKeys {
private KeyMap keys;
private Map<String, KeyMap> keyMaps;
private Map<String, String> variables = new HashMap<String,String>();
public ConsoleKeys(String appName, URL inputrcUrl) {
keyMaps = KeyMap.keyMaps();
loadKeys(appName, inputrcUrl);
}
protected boolean isViEditMode() {
return keys.isViKeyMap();
}
protected boolean setKeyMap (String name) {
KeyMap map = keyMaps.get(name);
if (map == null) {
return false;
}
this.keys = map;
return true;
}
protected Map<String, KeyMap> getKeyMaps() {
return keyMaps;
}
protected KeyMap getKeys() {
return keys;
}
protected void setKeys(KeyMap keys) {
this.keys = keys;
}
protected boolean getViEditMode() {
return keys.isViKeyMap ();
}
protected void loadKeys(String appName, URL inputrcUrl) {
keys = keyMaps.get(KeyMap.EMACS);
try {
InputStream input = inputrcUrl.openStream();
try {
loadKeys(input, appName);
Log.debug("Loaded user configuration: ", inputrcUrl);
}
finally {
try {
input.close();
} catch (IOException e) {
// Ignore
}
}
}
catch (IOException e) {
if (inputrcUrl.getProtocol().equals("file")) {
File file = new File(inputrcUrl.getPath());
if (file.exists()) {
Log.warn("Unable to read user configuration: ", inputrcUrl, e);
}
} else {
Log.warn("Unable to read user configuration: ", inputrcUrl, e);
}
}
}
private void loadKeys(InputStream input, String appName) throws IOException {
BufferedReader reader = new BufferedReader( new java.io.InputStreamReader( input ) );
String line;
boolean parsing = true;
List<Boolean> ifsStack = new ArrayList<Boolean>();
while ( (line = reader.readLine()) != null ) {
try {
line = line.trim();
if (line.length() == 0) {
continue;
}
if (line.charAt(0) == '#') {
continue;
}
int i = 0;
if (line.charAt(i) == '$') {
String cmd;
String args;
for (++i; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
int s = i;
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
cmd = line.substring(s, i);
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
s = i;
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
args = line.substring(s, i);
if ("if".equalsIgnoreCase(cmd)) {
ifsStack.add( parsing );
if (!parsing) {
continue;
}
if (args.startsWith("term=")) {
// TODO
} else if (args.startsWith("mode=")) {
if (args.equalsIgnoreCase("mode=vi")) {
parsing = isViEditMode();
} else if (args.equals("mode=emacs")) {
parsing = !isViEditMode();
} else {
parsing = false;
}
} else {
parsing = args.equalsIgnoreCase(appName);
}
} else if ("else".equalsIgnoreCase(cmd)) {
if (ifsStack.isEmpty()) {
throw new IllegalArgumentException("$else found without matching $if");
}
boolean invert = true;
for (boolean b : ifsStack) {
if (!b) {
invert = false;
break;
}
}
if (invert) {
parsing = !parsing;
}
} else if ("endif".equalsIgnoreCase(cmd)) {
if (ifsStack.isEmpty()) {
throw new IllegalArgumentException("endif found without matching $if");
}
parsing = ifsStack.remove( ifsStack.size() - 1 );
} else if ("include".equalsIgnoreCase(cmd)) {
// TODO
}
continue;
}
if (!parsing) {
continue;
}
boolean equivalency;
String keySeq = "";
if (line.charAt(i++) == '"') {
boolean esc = false;
for (;; i++) {
if (i >= line.length()) {
throw new IllegalArgumentException("Missing closing quote on line '" + line + "'");
}
if (esc) {
esc = false;
} else if (line.charAt(i) == '\\') {
esc = true;
} else if (line.charAt(i) == '"') {
break;
}
}
}
for (; i < line.length() && line.charAt(i) != ':'
&& line.charAt(i) != ' ' && line.charAt(i) != '\t'
; i++);
keySeq = line.substring(0, i);
equivalency = (i + 1 < line.length() && line.charAt(i) == ':' && line.charAt(i + 1) == '=');
i++;
if (equivalency) {
i++;
}
if (keySeq.equalsIgnoreCase("set")) {
String key;
String val;
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
int s = i;
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
key = line.substring( s, i );
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
s = i;
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
val = line.substring( s, i );
setVar( key, val );
} else {
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
int start = i;
if (i < line.length() && (line.charAt(i) == '\'' || line.charAt(i) == '\"')) {
char delim = line.charAt(i++);
boolean esc = false;
for (;; i++) {
if (i >= line.length()) {
break;
}
if (esc) {
esc = false;
} else if (line.charAt(i) == '\\') {
esc = true;
} else if (line.charAt(i) == delim) {
break;
}
}
}
for (; i < line.length() && line.charAt(i) != ' ' && line.charAt(i) != '\t'; i++);
String val = line.substring(Math.min(start, line.length()), Math.min(i, line.length()));
if (keySeq.charAt(0) == '"') {
keySeq = translateQuoted(keySeq);
} else {
// Bind key name
String keyName = keySeq.lastIndexOf('-') > 0 ? keySeq.substring( keySeq.lastIndexOf('-') + 1 ) : keySeq;
char key = getKeyFromName(keyName);
keyName = keySeq.toLowerCase();
keySeq = "";
if (keyName.contains("meta-") || keyName.contains("m-")) {
keySeq += "\u001b";
}
if (keyName.contains("control-") || keyName.contains("c-") || keyName.contains("ctrl-")) {
key = (char)(Character.toUpperCase( key ) & 0x1f);
}
keySeq += key;
}
if (val.length() > 0 && (val.charAt(0) == '\'' || val.charAt(0) == '\"')) {
keys.bind( keySeq, translateQuoted(val) );
} else {
String operationName = val.replace('-', '_').toUpperCase();
try {
keys.bind(keySeq, Operation.valueOf(operationName));
} catch(IllegalArgumentException e) {
Log.info("Unable to bind key for unsupported operation: ", val);
}
}
}
} catch (IllegalArgumentException e) {
Log.warn("Unable to parse user configuration: ", e);
}
}
}
private String translateQuoted(String keySeq) {
int i;
String str = keySeq.substring( 1, keySeq.length() - 1 );
keySeq = "";
for (i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '\\') {
boolean ctrl = str.regionMatches(i, "\\C-", 0, 3)|| str.regionMatches(i, "\\M-\\C-", 0, 6);
boolean meta = str.regionMatches(i, "\\M-", 0, 3)|| str.regionMatches(i, "\\C-\\M-", 0, 6);
i += (meta ? 3 : 0) + (ctrl ? 3 : 0) + (!meta && !ctrl ? 1 : 0);
if (i >= str.length()) {
break;
}
c = str.charAt(i);
if (meta) {
keySeq += "\u001b";
}
if (ctrl) {
c = c == '?' ? 0x7f : (char)(Character.toUpperCase( c ) & 0x1f);
}
if (!meta && !ctrl) {
switch (c) {
case 'a': c = 0x07; break;
case 'b': c = '\b'; break;
case 'd': c = 0x7f; break;
case 'e': c = 0x1b; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'v': c = 0x0b; break;
case '\\': c = '\\'; break;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
c = 0;
for (int j = 0; j < 3; j++, i++) {
if (i >= str.length()) {
break;
}
int k = Character.digit(str.charAt(i), 8);
if (k < 0) {
break;
}
c = (char)(c * 8 + k);
}
c &= 0xFF;
break;
case 'x':
i++;
c = 0;
for (int j = 0; j < 2; j++, i++) {
if (i >= str.length()) {
break;
}
int k = Character.digit(str.charAt(i), 16);
if (k < 0) {
break;
}
c = (char)(c * 16 + k);
}
c &= 0xFF;
break;
case 'u':
i++;
c = 0;
for (int j = 0; j < 4; j++, i++) {
if (i >= str.length()) {
break;
}
int k = Character.digit(str.charAt(i), 16);
if (k < 0) {
break;
}
c = (char)(c * 16 + k);
}
break;
}
}
keySeq += c;
} else {
keySeq += c;
}
}
return keySeq;
}
private char getKeyFromName(String name) {
if ("DEL".equalsIgnoreCase(name) || "Rubout".equalsIgnoreCase(name)) {
return 0x7f;
} else if ("ESC".equalsIgnoreCase(name) || "Escape".equalsIgnoreCase(name)) {
return '\033';
} else if ("LFD".equalsIgnoreCase(name) || "NewLine".equalsIgnoreCase(name)) {
return '\n';
} else if ("RET".equalsIgnoreCase(name) || "Return".equalsIgnoreCase(name)) {
return '\r';
} else if ("SPC".equalsIgnoreCase(name) || "Space".equalsIgnoreCase(name)) {
return ' ';
} else if ("Tab".equalsIgnoreCase(name)) {
return '\t';
} else {
return name.charAt(0);
}
}
private void setVar(String key, String val) {
if ("keymap".equalsIgnoreCase(key)) {
if (keyMaps.containsKey(val)) {
keys = keyMaps.get(val);
}
} else if ("editing-mode".equals(key)) {
if ("vi".equalsIgnoreCase(val)) {
keys = keyMaps.get(KeyMap.VI_INSERT);
} else if ("emacs".equalsIgnoreCase(key)) {
keys = keyMaps.get(KeyMap.EMACS);
}
} else if ("blink-matching-paren".equals(key)) {
if ("on".equalsIgnoreCase(val)) {
keys.setBlinkMatchingParen(true);
} else if ("off".equalsIgnoreCase(val)) {
keys.setBlinkMatchingParen(false);
}
}
/*
* Technically variables should be defined as a functor class
* so that validation on the variable value can be done at parse
* time. This is a stop-gap.
*/
variables.put(key, val);
}
/**
* Retrieves the value of a variable that was set in the .inputrc file
* during processing
* @param var The variable name
* @return The variable value.
*/
public String getVariable(String var) {
return variables.get (var);
}
}