1 /* 2 * Copyright (c) 2002-2012, the original author or authors. 3 * 4 * This software is distributable under the BSD license. See the terms of the 5 * BSD license in the documentation provided with this software. 6 * 7 * http://www.opensource.org/licenses/bsd-license.php 8 */ 9 package jdk.internal.jline.console; 10 11 import java.io.BufferedReader; 12 import java.io.File; 13 import java.io.IOException; 14 import java.io.InputStream; 15 import java.net.URL; 16 import java.util.ArrayList; 17 import java.util.HashMap; 18 import java.util.List; 19 import java.util.Map; 20 21 import jdk.internal.jline.internal.Log; 22 23 /** 24 * @author St\u00E5le W. Pedersen <stale.pedersen@jboss.org> 25 */ 26 public class ConsoleKeys { 27 28 private KeyMap keys; 29 30 private Map<String, KeyMap> keyMaps; 31 private Map<String, String> variables = new HashMap<String,String>(); 32 33 public ConsoleKeys(String appName, URL inputrcUrl) { 34 keyMaps = KeyMap.keyMaps(); 35 loadKeys(appName, inputrcUrl); 36 } 37 38 protected boolean isViEditMode() { 39 return keys.isViKeyMap(); 40 } 41 42 protected boolean setKeyMap (String name) { 43 KeyMap map = keyMaps.get(name); 44 if (map == null) { 45 return false; 46 } 47 this.keys = map; 48 return true; 49 } 50 51 protected Map<String, KeyMap> getKeyMaps() { 52 return keyMaps; 53 } 54 55 protected KeyMap getKeys() { 56 return keys; 57 } 58 59 protected void setKeys(KeyMap keys) { 60 this.keys = keys; 61 } 62 63 protected boolean getViEditMode() { 64 return keys.isViKeyMap (); 65 } 66 67 protected void loadKeys(String appName, URL inputrcUrl) { 68 keys = keyMaps.get(KeyMap.EMACS); 69 70 try { 71 InputStream input = inputrcUrl.openStream(); 72 try { 73 loadKeys(input, appName); 74 Log.debug("Loaded user configuration: ", inputrcUrl); 75 } 76 finally { 77 try { 78 input.close(); 79 } catch (IOException e) { 80 // Ignore 81 } 82 } 83 } 84 catch (IOException e) { 85 if (inputrcUrl.getProtocol().equals("file")) { 86 File file = new File(inputrcUrl.getPath()); 87 if (file.exists()) { 88 Log.warn("Unable to read user configuration: ", inputrcUrl, e); 89 } 90 } else { 91 Log.warn("Unable to read user configuration: ", inputrcUrl, e); 92 } 93 } 94 } 95 96 private void loadKeys(InputStream input, String appName) throws IOException { 97 BufferedReader reader = new BufferedReader( new java.io.InputStreamReader( input ) ); 98 String line; 99 boolean parsing = true; 100 List<Boolean> ifsStack = new ArrayList<Boolean>(); 101 while ( (line = reader.readLine()) != null ) { 102 try { 103 line = line.trim(); 104 if (line.length() == 0) { 105 continue; 106 } 107 if (line.charAt(0) == '#') { 108 continue; 109 } 110 int i = 0; 111 if (line.charAt(i) == '$') { 112 String cmd; 113 String args; 114 for (++i; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++); 115 int s = i; 116 for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++); 117 cmd = line.substring(s, i); 118 for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++); 119 s = i; 120 for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++); 121 args = line.substring(s, i); 122 if ("if".equalsIgnoreCase(cmd)) { 123 ifsStack.add( parsing ); 124 if (!parsing) { 125 continue; 126 } 127 if (args.startsWith("term=")) { 128 // TODO 129 } else if (args.startsWith("mode=")) { 130 if (args.equalsIgnoreCase("mode=vi")) { 131 parsing = isViEditMode(); 132 } else if (args.equals("mode=emacs")) { 133 parsing = !isViEditMode(); 134 } else { 135 parsing = false; 136 } 137 } else { 138 parsing = args.equalsIgnoreCase(appName); 139 } 140 } else if ("else".equalsIgnoreCase(cmd)) { 141 if (ifsStack.isEmpty()) { 142 throw new IllegalArgumentException("$else found without matching $if"); 143 } 144 boolean invert = true; 145 for (boolean b : ifsStack) { 146 if (!b) { 147 invert = false; 148 break; 149 } 150 } 151 if (invert) { 152 parsing = !parsing; 153 } 154 } else if ("endif".equalsIgnoreCase(cmd)) { 155 if (ifsStack.isEmpty()) { 156 throw new IllegalArgumentException("endif found without matching $if"); 157 } 158 parsing = ifsStack.remove( ifsStack.size() - 1 ); 159 } else if ("include".equalsIgnoreCase(cmd)) { 160 // TODO 161 } 162 continue; 163 } 164 if (!parsing) { 165 continue; 166 } 167 boolean equivalency; 168 String keySeq = ""; 169 if (line.charAt(i++) == '"') { 170 boolean esc = false; 171 for (;; i++) { 172 if (i >= line.length()) { 173 throw new IllegalArgumentException("Missing closing quote on line '" + line + "'"); 174 } 175 if (esc) { 176 esc = false; 177 } else if (line.charAt(i) == '\\') { 178 esc = true; 179 } else if (line.charAt(i) == '"') { 180 break; 181 } 182 } 183 } 184 for (; i < line.length() && line.charAt(i) != ':' 185 && line.charAt(i) != ' ' && line.charAt(i) != '\t' 186 ; i++); 187 keySeq = line.substring(0, i); 188 equivalency = (i + 1 < line.length() && line.charAt(i) == ':' && line.charAt(i + 1) == '='); 189 i++; 190 if (equivalency) { 191 i++; 192 } 193 if (keySeq.equalsIgnoreCase("set")) { 194 String key; 195 String val; 196 for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++); 197 int s = i; 198 for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++); 199 key = line.substring( s, i ); 200 for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++); 201 s = i; 202 for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++); 203 val = line.substring( s, i ); 204 setVar( key, val ); 205 } else { 206 for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++); 207 int start = i; 208 if (i < line.length() && (line.charAt(i) == '\'' || line.charAt(i) == '\"')) { 209 char delim = line.charAt(i++); 210 boolean esc = false; 211 for (;; i++) { 212 if (i >= line.length()) { 213 break; 214 } 215 if (esc) { 216 esc = false; 217 } else if (line.charAt(i) == '\\') { 218 esc = true; 219 } else if (line.charAt(i) == delim) { 220 break; 221 } 222 } 223 } 224 for (; i < line.length() && line.charAt(i) != ' ' && line.charAt(i) != '\t'; i++); 225 String val = line.substring(Math.min(start, line.length()), Math.min(i, line.length())); 226 if (keySeq.charAt(0) == '"') { 227 keySeq = translateQuoted(keySeq); 228 } else { 229 // Bind key name 230 String keyName = keySeq.lastIndexOf('-') > 0 ? keySeq.substring( keySeq.lastIndexOf('-') + 1 ) : keySeq; 231 char key = getKeyFromName(keyName); 232 keyName = keySeq.toLowerCase(); 233 keySeq = ""; 234 if (keyName.contains("meta-") || keyName.contains("m-")) { 235 keySeq += "\u001b"; 236 } 237 if (keyName.contains("control-") || keyName.contains("c-") || keyName.contains("ctrl-")) { 238 key = (char)(Character.toUpperCase( key ) & 0x1f); 239 } 240 keySeq += key; 241 } 242 if (val.length() > 0 && (val.charAt(0) == '\'' || val.charAt(0) == '\"')) { 243 keys.bind( keySeq, translateQuoted(val) ); 244 } else { 245 String operationName = val.replace('-', '_').toUpperCase(); 246 try { 247 keys.bind(keySeq, Operation.valueOf(operationName)); 248 } catch(IllegalArgumentException e) { 249 Log.info("Unable to bind key for unsupported operation: ", val); 250 } 251 } 252 } 253 } catch (IllegalArgumentException e) { 254 Log.warn("Unable to parse user configuration: ", e); 255 } 256 } 257 } 258 259 private String translateQuoted(String keySeq) { 260 int i; 261 String str = keySeq.substring( 1, keySeq.length() - 1 ); 262 keySeq = ""; 263 for (i = 0; i < str.length(); i++) { 264 char c = str.charAt(i); 265 if (c == '\\') { 266 boolean ctrl = str.regionMatches(i, "\\C-", 0, 3)|| str.regionMatches(i, "\\M-\\C-", 0, 6); 267 boolean meta = str.regionMatches(i, "\\M-", 0, 3)|| str.regionMatches(i, "\\C-\\M-", 0, 6); 268 i += (meta ? 3 : 0) + (ctrl ? 3 : 0) + (!meta && !ctrl ? 1 : 0); 269 if (i >= str.length()) { 270 break; 271 } 272 c = str.charAt(i); 273 if (meta) { 274 keySeq += "\u001b"; 275 } 276 if (ctrl) { 277 c = c == '?' ? 0x7f : (char)(Character.toUpperCase( c ) & 0x1f); 278 } 279 if (!meta && !ctrl) { 280 switch (c) { 281 case 'a': c = 0x07; break; 282 case 'b': c = '\b'; break; 283 case 'd': c = 0x7f; break; 284 case 'e': c = 0x1b; break; 285 case 'f': c = '\f'; break; 286 case 'n': c = '\n'; break; 287 case 'r': c = '\r'; break; 288 case 't': c = '\t'; break; 289 case 'v': c = 0x0b; break; 290 case '\\': c = '\\'; break; 291 case '0': case '1': case '2': case '3': 292 case '4': case '5': case '6': case '7': 293 c = 0; 294 for (int j = 0; j < 3; j++, i++) { 295 if (i >= str.length()) { 296 break; 297 } 298 int k = Character.digit(str.charAt(i), 8); 299 if (k < 0) { 300 break; 301 } 302 c = (char)(c * 8 + k); 303 } 304 c &= 0xFF; 305 break; 306 case 'x': 307 i++; 308 c = 0; 309 for (int j = 0; j < 2; j++, i++) { 310 if (i >= str.length()) { 311 break; 312 } 313 int k = Character.digit(str.charAt(i), 16); 314 if (k < 0) { 315 break; 316 } 317 c = (char)(c * 16 + k); 318 } 319 c &= 0xFF; 320 break; 321 case 'u': 322 i++; 323 c = 0; 324 for (int j = 0; j < 4; j++, i++) { 325 if (i >= str.length()) { 326 break; 327 } 328 int k = Character.digit(str.charAt(i), 16); 329 if (k < 0) { 330 break; 331 } 332 c = (char)(c * 16 + k); 333 } 334 break; 335 } 336 } 337 keySeq += c; 338 } else { 339 keySeq += c; 340 } 341 } 342 return keySeq; 343 } 344 345 private char getKeyFromName(String name) { 346 if ("DEL".equalsIgnoreCase(name) || "Rubout".equalsIgnoreCase(name)) { 347 return 0x7f; 348 } else if ("ESC".equalsIgnoreCase(name) || "Escape".equalsIgnoreCase(name)) { 349 return '\033'; 350 } else if ("LFD".equalsIgnoreCase(name) || "NewLine".equalsIgnoreCase(name)) { 351 return '\n'; 352 } else if ("RET".equalsIgnoreCase(name) || "Return".equalsIgnoreCase(name)) { 353 return '\r'; 354 } else if ("SPC".equalsIgnoreCase(name) || "Space".equalsIgnoreCase(name)) { 355 return ' '; 356 } else if ("Tab".equalsIgnoreCase(name)) { 357 return '\t'; 358 } else { 359 return name.charAt(0); 360 } 361 } 362 363 private void setVar(String key, String val) { 364 if ("keymap".equalsIgnoreCase(key)) { 365 if (keyMaps.containsKey(val)) { 366 keys = keyMaps.get(val); 367 } 368 } else if ("editing-mode".equals(key)) { 369 if ("vi".equalsIgnoreCase(val)) { 370 keys = keyMaps.get(KeyMap.VI_INSERT); 371 } else if ("emacs".equalsIgnoreCase(key)) { 372 keys = keyMaps.get(KeyMap.EMACS); 373 } 374 } else if ("blink-matching-paren".equals(key)) { 375 if ("on".equalsIgnoreCase(val)) { 376 keys.setBlinkMatchingParen(true); 377 } else if ("off".equalsIgnoreCase(val)) { 378 keys.setBlinkMatchingParen(false); 379 } 380 } 381 382 /* 383 * Technically variables should be defined as a functor class 384 * so that validation on the variable value can be done at parse 385 * time. This is a stop-gap. 386 */ 387 variables.put(key, val); 388 } 389 390 /** 391 * Retrieves the value of a variable that was set in the .inputrc file 392 * during processing 393 * @param var The variable name 394 * @return The variable value. 395 */ 396 public String getVariable(String var) { 397 return variables.get (var); 398 } 399 }