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