merged ext.keybindings.py into ext.keybindings_parser.py
[ranger.git] / ranger / ext / keybinding_parser.py
blob27600a4e25b0b46e7c81c5c97e1169db1a9cd16d
1 # Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com>
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 import sys
17 import copy
18 import curses.ascii
20 PY3 = sys.version > '3'
21 digits = set(range(ord('0'), ord('9')+1))
23 # Arbitrary numbers which are not used with curses.KEY_XYZ
24 ANYKEY, PASSIVE_ACTION, ALT_KEY, QUANT_KEY = range(9001, 9005)
26 special_keys = {
27 'bs': curses.KEY_BACKSPACE,
28 'backspace': curses.KEY_BACKSPACE,
29 'backspace2': curses.ascii.DEL,
30 'delete': curses.KEY_DC,
31 'cr': ord("\n"),
32 'enter': ord("\n"),
33 'return': ord("\n"),
34 'space': ord(" "),
35 'esc': curses.ascii.ESC,
36 'escape': curses.ascii.ESC,
37 'down': curses.KEY_DOWN,
38 'up': curses.KEY_UP,
39 'left': curses.KEY_LEFT,
40 'right': curses.KEY_RIGHT,
41 'pagedown': curses.KEY_NPAGE,
42 'pageup': curses.KEY_PPAGE,
43 'home': curses.KEY_HOME,
44 'end': curses.KEY_END,
45 'tab': ord('\t'),
46 's-tab': curses.KEY_BTAB,
49 very_special_keys = {
50 'any': ANYKEY,
51 'bg': PASSIVE_ACTION,
52 'allow_quantifiers': QUANT_KEY,
55 for key, val in tuple(special_keys.items()):
56 special_keys['a-' + key] = (ALT_KEY, val)
58 for char in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789':
59 special_keys['a-' + char] = (ALT_KEY, ord(char))
61 for char in 'abcdefghijklmnopqrstuvwxyz':
62 special_keys['c-' + char] = ord(char) - 96
64 for n in range(64):
65 special_keys['f' + str(n)] = curses.KEY_F0 + n
67 special_keys.update(very_special_keys)
70 def parse_keybinding(obj):
71 """
72 Translate a keybinding to a sequence of integers
74 Example:
75 lol<CR> => (ord('l'), ord('o'), ord('l'), ord('\\n'))
76 => (108, 111, 108, 10)
77 x<A-Left> => (120, (27, curses.KEY_LEFT))
78 """
79 assert isinstance(obj, (tuple, int, str))
80 if isinstance(obj, tuple):
81 for char in obj:
82 yield char
83 elif isinstance(obj, int):
84 yield obj
85 elif isinstance(obj, str):
86 in_brackets = False
87 bracket_content = None
88 for char in obj:
89 if in_brackets:
90 if char == '>':
91 in_brackets = False
92 string = ''.join(bracket_content).lower()
93 try:
94 keys = special_keys[string]
95 for key in keys:
96 yield key
97 except KeyError:
98 yield ord('<')
99 for c in bracket_content:
100 yield ord(c)
101 yield ord('>')
102 except TypeError:
103 yield keys # it was no tuple, just an int
104 else:
105 bracket_content.append(char)
106 else:
107 if char == '<':
108 in_brackets = True
109 bracket_content = []
110 else:
111 yield ord(char)
112 if in_brackets:
113 yield ord('<')
114 for c in bracket_content:
115 yield ord(c)
118 def construct_keybinding(iterable):
120 Does the reverse of parse_keybinding
122 result = []
123 for c in iterable:
124 result.append(key_to_string(c))
125 return ''.join(result)
128 def key_to_string(key):
129 return chr(key) if key in range(32, 127) else '?'
132 def _unbind_traverse(pointer, keys, pos=0):
133 if keys[pos] not in pointer:
134 return
135 if len(keys) > pos+1 and isinstance(pointer, dict):
136 _unbind_traverse(pointer[keys[pos]], keys, pos=pos+1)
137 if not pointer[keys[pos]]:
138 del pointer[keys[pos]]
139 elif len(keys) == pos+1:
140 try:
141 del pointer[keys[pos]]
142 keys.pop()
143 except:
144 pass
146 class KeyMaps(dict):
147 def __init__(self, keybuffer=None):
148 dict.__init__(self)
149 self.keybuffer = keybuffer
150 self.used_keymap = None
152 def use_keymap(self, keymap_name):
153 self.keybuffer.keymap = self.get(keymap_name, dict())
154 if self.used_keymap != keymap_name:
155 self.used_keymap = keymap_name
156 self.keybuffer.clear()
158 def _clean_input(self, context, keys):
159 try:
160 pointer = self[context]
161 except:
162 self[context] = pointer = dict()
163 if PY3:
164 keys = keys.encode('utf-8').decode('latin-1')
165 return list(parse_keybinding(keys)), pointer
167 def bind(self, context, keys, leaf):
168 keys, pointer = self._clean_input(context, keys)
169 if not keys:
170 return
171 last_key = keys[-1]
172 for key in keys[:-1]:
173 try:
174 if isinstance(pointer[key], dict):
175 pointer = pointer[key]
176 else:
177 pointer[key] = pointer = dict()
178 except:
179 pointer[key] = pointer = dict()
180 pointer[last_key] = leaf
182 def copy(self, context, source, target):
183 clean_source, pointer = self._clean_input(context, source)
184 if not source:
185 return
186 for key in clean_source:
187 try:
188 pointer = pointer[key]
189 except:
190 raise KeyError("Tried to copy the keybinding `%s',"
191 " but it was not found." % source)
192 self.bind(context, target, copy.deepcopy(pointer))
194 def unbind(self, context, keys):
195 keys, pointer = self._clean_input(context, keys)
196 if not keys:
197 return
198 _unbind_traverse(pointer, keys)
201 class KeyBuffer(object):
202 any_key = ANYKEY
203 passive_key = PASSIVE_ACTION
204 quantifier_key = QUANT_KEY
205 exclude_from_anykey = [27]
207 def __init__(self, keymap=None):
208 self.keymap = keymap
209 self.clear()
211 def clear(self):
212 self.keys = []
213 self.wildcards = []
214 self.pointer = self.keymap
215 self.result = None
216 self.quantifier = None
217 self.finished_parsing_quantifier = False
218 self.finished_parsing = False
219 self.parse_error = False
221 if self.keymap and self.quantifier_key in self.keymap:
222 if self.keymap[self.quantifier_key] == 'false':
223 self.finished_parsing_quantifier = True
225 def add(self, key):
226 self.keys.append(key)
227 self.result = None
228 if not self.finished_parsing_quantifier and key in digits:
229 if self.quantifier is None:
230 self.quantifier = 0
231 self.quantifier = self.quantifier * 10 + key - 48 # (48 = ord(0))
232 else:
233 self.finished_parsing_quantifier = True
235 moved = True
236 if key in self.pointer:
237 self.pointer = self.pointer[key]
238 elif self.any_key in self.pointer and \
239 key not in self.exclude_from_anykey:
240 self.wildcards.append(key)
241 self.pointer = self.pointer[self.any_key]
242 else:
243 moved = False
245 if moved:
246 if isinstance(self.pointer, dict):
247 if self.passive_key in self.pointer:
248 self.result = self.pointer[self.passive_key]
249 else:
250 self.result = self.pointer
251 self.finished_parsing = True
252 else:
253 self.finished_parsing = True
254 self.parse_error = True
256 def __str__(self):
257 return "".join("{0:c}".format(c) for c in self.keys)