/* * 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 */ public class ConsoleKeys { private KeyMap keys; private Map keyMaps; private Map variables = new HashMap(); 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 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 ifsStack = new ArrayList(); 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); } }