summary refs log blame commit diff stats
path: root/ranger/container/keybuffer.py
blob: 23b82a162aa29b4f0cf44ea1d30197d482d6942e (plain) (tree)



















                                                                       
                                                                           

                        
                                       






                                                        
                                                                           



                                                    
                                               



























































                                                                                    
                                                           

















                                                                                 
                                                                                  









































                                                                                             
                                                                                



















                                                                        
# Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
#
# 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 <http://www.gnu.org/licenses/>.

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)