|
1 # Copyright 2000-2004 Michael Hudson mwh@python.net |
|
2 # |
|
3 # All Rights Reserved |
|
4 # |
|
5 # Portions Copyright (c) 2005 Nokia Corporation |
|
6 # |
|
7 # Permission to use, copy, modify, and distribute this software and |
|
8 # its documentation for any purpose is hereby granted without fee, |
|
9 # provided that the above copyright notice appear in all copies and |
|
10 # that both that copyright notice and this permission notice appear in |
|
11 # supporting documentation. |
|
12 # |
|
13 # THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO |
|
14 # THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
|
15 # AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, |
|
16 # INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER |
|
17 # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF |
|
18 # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|
19 # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
20 |
|
21 """ |
|
22 functions for parsing keyspecs |
|
23 |
|
24 Support for turning keyspecs into appropriate sequences. |
|
25 |
|
26 pyrepl uses it's own bastardized keyspec format, which is meant to be |
|
27 a strict superset of readline's \"KEYSEQ\" format (which is to say |
|
28 that if you can come up with a spec readline accepts that this |
|
29 doesn't, you've found a bug and should tell me about it). |
|
30 |
|
31 Note that this is the `\\C-o' style of readline keyspec, not the |
|
32 `Control-o' sort. |
|
33 |
|
34 A keyspec is a string representing a sequence of keypresses that can |
|
35 be bound to a command. |
|
36 |
|
37 All characters other than the backslash represent themselves. In the |
|
38 traditional manner, a backslash introduces a escape sequence. |
|
39 |
|
40 The extension to readline is that the sequence \\<KEY> denotes the |
|
41 sequence of charaters produced by hitting KEY. |
|
42 |
|
43 Examples: |
|
44 |
|
45 `a' - what you get when you hit the `a' key |
|
46 `\\EOA' - Escape - O - A (up, on my terminal) |
|
47 `\\<UP>' - the up arrow key |
|
48 `\\<up>' - ditto (keynames are case insensitive) |
|
49 `\\C-o', `\\c-o' - control-o |
|
50 `\\M-.' - meta-period |
|
51 `\\E.' - ditto (that's how meta works for pyrepl) |
|
52 `\\<tab>', `\\<TAB>', `\\t', `\\011', '\\x09', '\\X09', '\\C-i', '\\C-I' |
|
53 - all of these are the tab character. Can you think of any more? |
|
54 """ |
|
55 |
|
56 import ascii |
|
57 |
|
58 _escapes = { |
|
59 '\\':'\\', |
|
60 "'":"'", |
|
61 '"':'"', |
|
62 'a':'\a', |
|
63 'b':'\h', |
|
64 'e':'\033', |
|
65 'f':'\f', |
|
66 'n':'\n', |
|
67 'r':'\r', |
|
68 't':'\t', |
|
69 'v':'\v' |
|
70 } |
|
71 |
|
72 _keynames = { |
|
73 'backspace': 'backspace', |
|
74 'delete': 'delete', |
|
75 'down': 'down', |
|
76 'end': 'end', |
|
77 'enter': '\r', |
|
78 'escape': '\033', |
|
79 'f1' : 'f1', 'f2' : 'f2', 'f3' : 'f3', 'f4' : 'f4', |
|
80 'f5' : 'f5', 'f6' : 'f6', 'f7' : 'f7', 'f8' : 'f8', |
|
81 'f9' : 'f9', 'f10': 'f10', 'f11': 'f11', 'f12': 'f12', |
|
82 'f13': 'f13', 'f14': 'f14', 'f15': 'f15', 'f16': 'f16', |
|
83 'f17': 'f17', 'f18': 'f18', 'f19': 'f19', 'f20': 'f20', |
|
84 'home': 'home', |
|
85 'insert': 'insert', |
|
86 'left': 'left', |
|
87 'page down': 'page down', |
|
88 'page up': 'page up', |
|
89 # 'return': 'enter', |
|
90 'return': '\r', |
|
91 'right': 'right', |
|
92 'space': ' ', |
|
93 'tab': '\t', |
|
94 'up': 'up', |
|
95 } |
|
96 |
|
97 class KeySpecError(Exception): |
|
98 pass |
|
99 |
|
100 def _parse_key1(key, s): |
|
101 ctrl = 0 |
|
102 meta = 0 |
|
103 ret = '' |
|
104 while not ret and s < len(key): |
|
105 if key[s] == '\\': |
|
106 c = key[s+1].lower() |
|
107 if _escapes.has_key(c): |
|
108 ret = _escapes[c] |
|
109 s += 2 |
|
110 elif c == "c": |
|
111 if key[s + 2] != '-': |
|
112 raise KeySpecError, \ |
|
113 "\\C must be followed by `-' (char %d of %s)"%( |
|
114 s + 2, repr(key)) |
|
115 if ctrl: |
|
116 raise KeySpecError, "doubled \\C- (char %d of %s)"%( |
|
117 s + 1, repr(key)) |
|
118 ctrl = 1 |
|
119 s += 3 |
|
120 elif c == "m": |
|
121 if key[s + 2] != '-': |
|
122 raise KeySpecError, \ |
|
123 "\\M must be followed by `-' (char %d of %s)"%( |
|
124 s + 2, repr(key)) |
|
125 if meta: |
|
126 raise KeySpecError, "doubled \\M- (char %d of %s)"%( |
|
127 s + 1, repr(key)) |
|
128 meta = 1 |
|
129 s += 3 |
|
130 elif c.isdigit(): |
|
131 n = key[s+1:s+4] |
|
132 ret = chr(int(n, 8)) |
|
133 s += 4 |
|
134 elif c == 'x': |
|
135 n = key[s+2:s+4] |
|
136 ret = chr(int(n, 16)) |
|
137 s += 4 |
|
138 elif c == '<': |
|
139 t = key.find('>', s) |
|
140 if t == -1: |
|
141 raise KeySpecError, \ |
|
142 "unterminated \\< starting at char %d of %s"%( |
|
143 s + 1, repr(key)) |
|
144 ret = key[s+2:t].lower() |
|
145 if ret not in _keynames: |
|
146 raise KeySpecError, \ |
|
147 "unrecognised keyname `%s' at char %d of %s"%( |
|
148 ret, s + 2, repr(key)) |
|
149 ret = _keynames[ret] |
|
150 s = t + 1 |
|
151 else: |
|
152 raise KeySpecError, \ |
|
153 "unknown backslash escape %s at char %d of %s"%( |
|
154 `c`, s + 2, repr(key)) |
|
155 else: |
|
156 ret = key[s] |
|
157 s += 1 |
|
158 if ctrl: |
|
159 if len(ret) > 1: |
|
160 raise KeySpecError, "\\C- must be followed by a character" |
|
161 ret = ascii.ctrl(ret) |
|
162 if meta: |
|
163 ret = ['\033', ret] |
|
164 else: |
|
165 ret = [ret] |
|
166 return ret, s |
|
167 |
|
168 def parse_keys(key): |
|
169 s = 0 |
|
170 r = [] |
|
171 while s < len(key): |
|
172 k, s = _parse_key1(key, s) |
|
173 r.extend(k) |
|
174 return r |
|
175 |
|
176 def compile_keymap(keymap, empty=''): |
|
177 r = {} |
|
178 for key, value in keymap.items(): |
|
179 r.setdefault(key[0], {})[key[1:]] = value |
|
180 for key, value in r.items(): |
|
181 if empty in value: |
|
182 if len(value) <> 1: |
|
183 raise KeySpecError, \ |
|
184 "key definitions for %s clash"%(value.values(),) |
|
185 else: |
|
186 r[key] = value[empty] |
|
187 else: |
|
188 r[key] = compile_keymap(value, empty) |
|
189 return r |