| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | #   Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com> | 
					
						
							|  |  |  | #                       Armin Rigo | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | #                        All Rights Reserved | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Permission to use, copy, modify, and distribute this software and | 
					
						
							|  |  |  | # its documentation for any purpose is hereby granted without fee, | 
					
						
							|  |  |  | # provided that the above copyright notice appear in all copies and | 
					
						
							|  |  |  | # that both that copyright notice and this permission notice appear in | 
					
						
							|  |  |  | # supporting documentation. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO | 
					
						
							|  |  |  | # THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | 
					
						
							|  |  |  | # AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, | 
					
						
							|  |  |  | # INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER | 
					
						
							|  |  |  | # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF | 
					
						
							|  |  |  | # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | 
					
						
							|  |  |  | # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2024-05-22 17:13:01 -04:00
										 |  |  | Keymap contains functions for parsing keyspecs and turning keyspecs into | 
					
						
							|  |  |  | appropriate sequences. | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-22 17:13:01 -04:00
										 |  |  | A keyspec is a string representing a sequence of key presses that can | 
					
						
							|  |  |  | be bound to a command. All characters other than the backslash represent | 
					
						
							|  |  |  | themselves. In the traditional manner, a backslash introduces an escape | 
					
						
							|  |  |  | sequence. | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-22 17:13:01 -04:00
										 |  |  | pyrepl uses its own keyspec format that is meant to be a strict superset of | 
					
						
							|  |  |  | readline's KEYSEQ format. This means that if a spec is found that readline | 
					
						
							|  |  |  | accepts that this doesn't, it should be logged as a bug. Note that this means | 
					
						
							|  |  |  | we're using the `\\C-o' style of readline's keyspec, not the `Control-o' sort. | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | The extension to readline is that the sequence \\<KEY> denotes the | 
					
						
							| 
									
										
										
										
											2024-05-22 17:13:01 -04:00
										 |  |  | sequence of characters produced by hitting KEY. | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | Examples: | 
					
						
							| 
									
										
										
										
											2024-05-22 17:13:01 -04:00
										 |  |  | `a'      - what you get when you hit the `a' key | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | `\\EOA'  - Escape - O - A (up, on my terminal) | 
					
						
							|  |  |  | `\\<UP>' - the up arrow key | 
					
						
							| 
									
										
										
										
											2024-05-22 17:13:01 -04:00
										 |  |  | `\\<up>' - ditto (keynames are case-insensitive) | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | `\\C-o', `\\c-o'  - control-o | 
					
						
							|  |  |  | `\\M-.'  - meta-period | 
					
						
							|  |  |  | `\\E.'   - ditto (that's how meta works for pyrepl) | 
					
						
							|  |  |  | `\\<tab>', `\\<TAB>', `\\t', `\\011', '\\x09', '\\X09', '\\C-i', '\\C-I' | 
					
						
							| 
									
										
										
										
											2024-05-22 17:13:01 -04:00
										 |  |  |    - all of these are the tab character. | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | _escapes = { | 
					
						
							|  |  |  |     "\\": "\\", | 
					
						
							|  |  |  |     "'": "'", | 
					
						
							|  |  |  |     '"': '"', | 
					
						
							|  |  |  |     "a": "\a", | 
					
						
							|  |  |  |     "b": "\b", | 
					
						
							|  |  |  |     "e": "\033", | 
					
						
							|  |  |  |     "f": "\f", | 
					
						
							|  |  |  |     "n": "\n", | 
					
						
							|  |  |  |     "r": "\r", | 
					
						
							|  |  |  |     "t": "\t", | 
					
						
							|  |  |  |     "v": "\v", | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | _keynames = { | 
					
						
							|  |  |  |     "backspace": "backspace", | 
					
						
							|  |  |  |     "delete": "delete", | 
					
						
							|  |  |  |     "down": "down", | 
					
						
							|  |  |  |     "end": "end", | 
					
						
							|  |  |  |     "enter": "\r", | 
					
						
							|  |  |  |     "escape": "\033", | 
					
						
							|  |  |  |     "f1": "f1", | 
					
						
							|  |  |  |     "f2": "f2", | 
					
						
							|  |  |  |     "f3": "f3", | 
					
						
							|  |  |  |     "f4": "f4", | 
					
						
							|  |  |  |     "f5": "f5", | 
					
						
							|  |  |  |     "f6": "f6", | 
					
						
							|  |  |  |     "f7": "f7", | 
					
						
							|  |  |  |     "f8": "f8", | 
					
						
							|  |  |  |     "f9": "f9", | 
					
						
							|  |  |  |     "f10": "f10", | 
					
						
							|  |  |  |     "f11": "f11", | 
					
						
							|  |  |  |     "f12": "f12", | 
					
						
							|  |  |  |     "f13": "f13", | 
					
						
							|  |  |  |     "f14": "f14", | 
					
						
							|  |  |  |     "f15": "f15", | 
					
						
							|  |  |  |     "f16": "f16", | 
					
						
							|  |  |  |     "f17": "f17", | 
					
						
							|  |  |  |     "f18": "f18", | 
					
						
							|  |  |  |     "f19": "f19", | 
					
						
							|  |  |  |     "f20": "f20", | 
					
						
							|  |  |  |     "home": "home", | 
					
						
							|  |  |  |     "insert": "insert", | 
					
						
							|  |  |  |     "left": "left", | 
					
						
							|  |  |  |     "page down": "page down", | 
					
						
							|  |  |  |     "page up": "page up", | 
					
						
							|  |  |  |     "return": "\r", | 
					
						
							|  |  |  |     "right": "right", | 
					
						
							|  |  |  |     "space": " ", | 
					
						
							|  |  |  |     "tab": "\t", | 
					
						
							|  |  |  |     "up": "up", | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class KeySpecError(Exception): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-22 17:13:01 -04:00
										 |  |  | def parse_keys(keys: str) -> list[str]: | 
					
						
							|  |  |  |     """Parse keys in keyspec format to a sequence of keys.""" | 
					
						
							|  |  |  |     s = 0 | 
					
						
							|  |  |  |     r: list[str] = [] | 
					
						
							|  |  |  |     while s < len(keys): | 
					
						
							|  |  |  |         k, s = _parse_single_key_sequence(keys, s) | 
					
						
							|  |  |  |         r.extend(k) | 
					
						
							|  |  |  |     return r | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _parse_single_key_sequence(key: str, s: int) -> tuple[list[str], int]: | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  |     ctrl = 0 | 
					
						
							|  |  |  |     meta = 0 | 
					
						
							|  |  |  |     ret = "" | 
					
						
							|  |  |  |     while not ret and s < len(key): | 
					
						
							|  |  |  |         if key[s] == "\\": | 
					
						
							|  |  |  |             c = key[s + 1].lower() | 
					
						
							|  |  |  |             if c in _escapes: | 
					
						
							|  |  |  |                 ret = _escapes[c] | 
					
						
							|  |  |  |                 s += 2 | 
					
						
							|  |  |  |             elif c == "c": | 
					
						
							|  |  |  |                 if key[s + 2] != "-": | 
					
						
							|  |  |  |                     raise KeySpecError( | 
					
						
							|  |  |  |                         "\\C must be followed by `-' (char %d of %s)" | 
					
						
							|  |  |  |                         % (s + 2, repr(key)) | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 if ctrl: | 
					
						
							|  |  |  |                     raise KeySpecError( | 
					
						
							|  |  |  |                         "doubled \\C- (char %d of %s)" % (s + 1, repr(key)) | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 ctrl = 1 | 
					
						
							|  |  |  |                 s += 3 | 
					
						
							|  |  |  |             elif c == "m": | 
					
						
							|  |  |  |                 if key[s + 2] != "-": | 
					
						
							|  |  |  |                     raise KeySpecError( | 
					
						
							|  |  |  |                         "\\M must be followed by `-' (char %d of %s)" | 
					
						
							|  |  |  |                         % (s + 2, repr(key)) | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 if meta: | 
					
						
							|  |  |  |                     raise KeySpecError( | 
					
						
							|  |  |  |                         "doubled \\M- (char %d of %s)" % (s + 1, repr(key)) | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 meta = 1 | 
					
						
							|  |  |  |                 s += 3 | 
					
						
							|  |  |  |             elif c.isdigit(): | 
					
						
							|  |  |  |                 n = key[s + 1 : s + 4] | 
					
						
							|  |  |  |                 ret = chr(int(n, 8)) | 
					
						
							|  |  |  |                 s += 4 | 
					
						
							|  |  |  |             elif c == "x": | 
					
						
							|  |  |  |                 n = key[s + 2 : s + 4] | 
					
						
							|  |  |  |                 ret = chr(int(n, 16)) | 
					
						
							|  |  |  |                 s += 4 | 
					
						
							|  |  |  |             elif c == "<": | 
					
						
							|  |  |  |                 t = key.find(">", s) | 
					
						
							|  |  |  |                 if t == -1: | 
					
						
							|  |  |  |                     raise KeySpecError( | 
					
						
							|  |  |  |                         "unterminated \\< starting at char %d of %s" | 
					
						
							|  |  |  |                         % (s + 1, repr(key)) | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 ret = key[s + 2 : t].lower() | 
					
						
							|  |  |  |                 if ret not in _keynames: | 
					
						
							|  |  |  |                     raise KeySpecError( | 
					
						
							|  |  |  |                         "unrecognised keyname `%s' at char %d of %s" | 
					
						
							|  |  |  |                         % (ret, s + 2, repr(key)) | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 ret = _keynames[ret] | 
					
						
							|  |  |  |                 s = t + 1 | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 raise KeySpecError( | 
					
						
							|  |  |  |                     "unknown backslash escape %s at char %d of %s" | 
					
						
							|  |  |  |                     % (repr(c), s + 2, repr(key)) | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             ret = key[s] | 
					
						
							|  |  |  |             s += 1 | 
					
						
							|  |  |  |     if ctrl: | 
					
						
							| 
									
										
										
										
											2024-05-21 23:16:06 +02:00
										 |  |  |         if len(ret) == 1: | 
					
						
							|  |  |  |             ret = chr(ord(ret) & 0x1F)  # curses.ascii.ctrl() | 
					
						
							|  |  |  |         elif ret in {"left", "right"}: | 
					
						
							|  |  |  |             ret = f"ctrl {ret}" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise KeySpecError("\\C- followed by invalid key") | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-22 17:13:01 -04:00
										 |  |  |     result = [ret], s | 
					
						
							|  |  |  |     if meta: | 
					
						
							|  |  |  |         result[0].insert(0, "\033") | 
					
						
							|  |  |  |     return result | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def compile_keymap(keymap, empty=b""): | 
					
						
							|  |  |  |     r = {} | 
					
						
							|  |  |  |     for key, value in keymap.items(): | 
					
						
							|  |  |  |         if isinstance(key, bytes): | 
					
						
							|  |  |  |             first = key[:1] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             first = key[0] | 
					
						
							|  |  |  |         r.setdefault(first, {})[key[1:]] = value | 
					
						
							|  |  |  |     for key, value in r.items(): | 
					
						
							|  |  |  |         if empty in value: | 
					
						
							|  |  |  |             if len(value) != 1: | 
					
						
							|  |  |  |                 raise KeySpecError("key definitions for %s clash" % (value.values(),)) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 r[key] = value[empty] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             r[key] = compile_keymap(value, empty) | 
					
						
							|  |  |  |     return r |