1
2
3
4
5
6
7
8
9 package 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 jline.internal.Log;
22
23
24
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
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
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
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
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
384
385
386
387 variables.put(key, val);
388 }
389
390
391
392
393
394
395
396 public String getVariable(String var) {
397 return variables.get (var);
398 }
399 }