# Copyright (C) 2009, 2010 Roman Zimbelmann # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import curses.ascii from collections import deque from string import digits from ranger.ext.keybinding_parser import parse_keybinding, \ DIRKEY, ANYKEY, PASSIVE_ACTION from ranger.container.keymap import Binding, KeyMap # mainly for assertions MAX_ALIAS_RECURSION = 20 digitlist = set(ord(n) for n in digits) class KeyBuffer(object): """The evaluator and storage for pressed keys""" def __init__(self, keymap, direction_keys): self.assign(keymap, direction_keys) def assign(self, keymap, direction_keys): """Change the keymap and direction keys of the keybuffer""" self.keymap = keymap self.direction_keys = direction_keys def add(self, key): """Add a key and evaluate it""" assert isinstance(key, int) assert key >= 0 self.all_keys.append(key) self.key_queue.append(key) while self.key_queue: key = self.key_queue.popleft() # evaluate quantifiers if self.eval_quantifier and self._do_eval_quantifier(key): return # evaluate the command if self.eval_command and self._do_eval_command(key): return # evaluate (the first number of) the direction-quantifier if self.eval_quantifier and self._do_eval_quantifier(key): return # evaluate direction keys {j,k,gg,pagedown,...} if not self.eval_command: self._do_eval_direction(key) def _do_eval_direction(self, key): try: assert isinstance(self.dir_tree_pointer, dict) self.dir_tree_pointer = self.dir_tree_pointer[key] except KeyError: self.failure = True else: self._direction_try_to_finish() def _direction_try_to_finish(self): if self.max_alias_recursion <= 0: self.failure = True return None match = self.dir_tree_pointer assert isinstance(match, (Binding, dict, KeyMap)) if isinstance(match, KeyMap): self.dir_tree_pointer = self.dir_tree_pointer._tree match = self.dir_tree_pointer if isinstance(self.dir_tree_pointer, Binding): if match.alias: self.key_queue.extend(parse_keybinding(match.alias)) self.dir_tree_pointer = self.direction_keys._tree self.max_alias_recursion -= 1 else: direction = match.actions['dir'].copy() if self.direction_quant is not None: direction.multiply(self.direction_quant) self.directions.append(direction) self.direction_quant = None self.eval_command = True self._try_to_finish() def _do_eval_quantifier(self, key): if self.eval_command: tree = self.tree_pointer else: tree = self.dir_tree_pointer if key in digitlist and ANYKEY not in tree: attr = self.eval_command and 'quant' or 'direction_quant' if getattr(self, attr) is None: setattr(self, attr, 0) setattr(self, attr, getattr(self, attr) * 10 + key - 48) else: self.eval_quantifier = False return None return True def _do_eval_command(self, key): assert isinstance(self.tree_pointer, dict), self.tree_pointer try: self.tree_pointer = self.tree_pointer[key] except TypeError: self.failure = True return None except KeyError: try: key in digitlist or self.direction_keys._tree[key] self.tree_pointer = self.tree_pointer[DIRKEY] except KeyError: try: self.tree_pointer = self.tree_pointer[ANYKEY] except KeyError: self.failure = True return None else: self.matches.append(key) assert isinstance(self.tree_pointer, (Binding, dict)) self._try_to_finish() else: assert isinstance(self.tree_pointer, (Binding, dict)) self.eval_command = False self.eval_quantifier = True self.dir_tree_pointer = self.direction_keys._tree else: if isinstance(self.tree_pointer, dict): try: self.command = self.tree_pointer[PASSIVE_ACTION] except (KeyError, TypeError): self.command = None self._try_to_finish() def _try_to_finish(self): if self.max_alias_recursion <= 0: self.failure = True return None assert isinstance(self.tree_pointer, (Binding, dict, KeyMap)) if isinstance(self.tree_pointer, KeyMap): self.tree_pointer = self.tree_pointer._tree if isinstance(self.tree_pointer, Binding): if self.tree_pointer.alias: keys = parse_keybinding(self.tree_pointer.alias) self.key_queue.extend(keys) self.tree_pointer = self.keymap._tree self.max_alias_recursion -= 1 else: self.command = self.tree_pointer self.done = True def clear(self): """Reset the keybuffer. Do this once before the first usage.""" self.max_alias_recursion = MAX_ALIAS_RECURSION self.failure = False self.done = False self.quant = None self.matches = [] self.command = None self.direction_quant = None self.directions = [] self.all_keys = [] self.tree_pointer = self.keymap._tree self.dir_tree_pointer = self.direction_keys._tree self.key_queue = deque() self.eval_quantifier = True self.eval_command = True def __str__(self): """returns a concatenation of all characters""" return "".join("{0:c}".format(c) for c in self.all_keys)