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/>.
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)
27 'bs': curses
.KEY_BACKSPACE
,
28 'backspace': curses
.KEY_BACKSPACE
,
29 'backspace2': curses
.ascii
.DEL
,
30 'delete': curses
.KEY_DC
,
35 'esc': curses
.ascii
.ESC
,
36 'escape': curses
.ascii
.ESC
,
37 'down': curses
.KEY_DOWN
,
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
,
46 's-tab': curses
.KEY_BTAB
,
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
65 special_keys
['f' + str(n
)] = curses
.KEY_F0
+ n
67 special_keys
.update(very_special_keys
)
70 def parse_keybinding(obj
):
72 Translate a keybinding to a sequence of integers
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))
79 assert isinstance(obj
, (tuple, int, str))
80 if isinstance(obj
, tuple):
83 elif isinstance(obj
, int):
85 elif isinstance(obj
, str):
87 bracket_content
= None
92 string
= ''.join(bracket_content
).lower()
94 keys
= special_keys
[string
]
99 for c
in bracket_content
:
103 yield keys
# it was no tuple, just an int
105 bracket_content
.append(char
)
114 for c
in bracket_content
:
118 def construct_keybinding(iterable
):
120 Does the reverse of parse_keybinding
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
:
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:
141 del pointer
[keys
[pos
]]
147 def __init__(self
, keybuffer
=None):
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
):
160 pointer
= self
[context
]
162 self
[context
] = pointer
= dict()
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
)
172 for key
in keys
[:-1]:
174 if isinstance(pointer
[key
], dict):
175 pointer
= pointer
[key
]
177 pointer
[key
] = pointer
= dict()
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
)
186 for key
in clean_source
:
188 pointer
= pointer
[key
]
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
)
198 _unbind_traverse(pointer
, keys
)
201 class KeyBuffer(object):
203 passive_key
= PASSIVE_ACTION
204 quantifier_key
= QUANT_KEY
205 exclude_from_anykey
= [27]
207 def __init__(self
, keymap
=None):
214 self
.pointer
= self
.keymap
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
226 self
.keys
.append(key
)
228 if not self
.finished_parsing_quantifier
and key
in digits
:
229 if self
.quantifier
is None:
231 self
.quantifier
= self
.quantifier
* 10 + key
- 48 # (48 = ord(0))
233 self
.finished_parsing_quantifier
= 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
]
246 if isinstance(self
.pointer
, dict):
247 if self
.passive_key
in self
.pointer
:
248 self
.result
= self
.pointer
[self
.passive_key
]
250 self
.result
= self
.pointer
251 self
.finished_parsing
= True
253 self
.finished_parsing
= True
254 self
.parse_error
= True
257 return "".join("{0:c}".format(c
) for c
in self
.keys
)