summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorhut <hut@lavabit.com>2011-09-30 04:19:16 +0200
committerhut <hut@lavabit.com>2011-09-30 04:55:07 +0200
commit41df56beff2785174b4b466c8cc39159a79eaedc (patch)
treec0fab60a14d969b2a6fc14bfb7668c67f04b2093
parentb1d25114617f41538f1e9e1740229f5e326c9f88 (diff)
downloadranger-41df56beff2785174b4b466c8cc39159a79eaedc.tar.gz
implemented a new keybinding parser
-rw-r--r--ranger/api/commands.py6
-rw-r--r--ranger/container/keybuffer.py180
-rw-r--r--ranger/container/keymap.py164
-rw-r--r--ranger/core/actions.py4
-rw-r--r--ranger/core/environment.py12
-rw-r--r--ranger/core/helper.py4
-rw-r--r--ranger/defaults/commands.py32
-rw-r--r--ranger/defaults/rc.conf6
-rw-r--r--ranger/ext/keybindings.py96
-rw-r--r--ranger/ext/tree.py136
-rw-r--r--ranger/gui/ui.py41
-rw-r--r--ranger/gui/widgets/console.py24
-rw-r--r--ranger/gui/widgets/pager.py24
-rw-r--r--ranger/gui/widgets/taskview.py24
14 files changed, 161 insertions, 592 deletions
diff --git a/ranger/api/commands.py b/ranger/api/commands.py
index 94b7f341..130906b6 100644
--- a/ranger/api/commands.py
+++ b/ranger/api/commands.py
@@ -39,7 +39,11 @@ class CommandContainer(object):
 		for varname, var in vars(module).items():
 			try:
 				if issubclass(var, Command) and var != Command:
-					self.commands[var.name or varname] = var
+					classdict = var.__mro__[0].__dict__
+					if 'name' in classdict and classdict['name']:
+						self.commands[var.name] = var
+					else:
+						self.commands[varname] = var
 			except TypeError:
 				pass
 		for new, old in self.aliases.items():
diff --git a/ranger/container/keybuffer.py b/ranger/container/keybuffer.py
deleted file mode 100644
index 23b82a16..00000000
--- a/ranger/container/keybuffer.py
+++ /dev/null
@@ -1,180 +0,0 @@
-# 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)
diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py
deleted file mode 100644
index 8739d22a..00000000
--- a/ranger/container/keymap.py
+++ /dev/null
@@ -1,164 +0,0 @@
-# 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/>.
-
-from ranger.ext.tree import Tree
-from ranger.ext.direction import Direction
-from ranger.ext.keybinding_parser import parse_keybinding, DIRKEY, ANYKEY
-
-FUNC = 'func'
-DIRARG = 'dir'
-ALIASARG = 'alias'
-
-class CommandArgs(object):
-	"""
-	A CommandArgs object is passed to the keybinding function.
-
-	This object simply aggregates information about the pressed keys
-	and the current environment.
-
-	Attributes:
-	fm: the FM instance
-	wdg: the currently focused widget (or fm, if none is focused)
-	keybuffer: the keybuffer object
-	n: the prefixed number, eg 5 in the command "5yy"
-	directions: a list of directions which are entered for "<dir>"
-	direction: the first direction object from that list
-	keys: a string representation of the keybuffer
-	matches: all keys which are entered for "<any>"
-	match: the first match
-	binding: the used Binding object
-	"""
-	def __init__(self, fm, widget, keybuf):
-		self.fm = fm
-		self.wdg = widget
-		self.keybuffer = keybuf
-		self.n = keybuf.quant
-		self.direction = keybuf.directions and keybuf.directions[0] or None
-		self.directions = keybuf.directions
-		self.keys = str(keybuf)
-		self.matches = keybuf.matches
-		self.match = keybuf.matches and keybuf.matches[0] or None
-		self.binding = keybuf.command
-
-	@staticmethod
-	def from_widget(widget):
-		return CommandArgs(widget.fm, \
-				widget, widget.env.keybuffer)
-
-
-class KeyMap(Tree):
-	"""Contains a tree with all the keybindings"""
-	def map(self, *args, **keywords):
-		if keywords:
-			return self._add_binding(*args, **keywords)
-		firstarg = args[-1]
-		if hasattr(firstarg, '__call__'):
-			keywords[FUNC] = firstarg
-			return self._add_binding(*args[:-1], **keywords)
-		def decorator_function(func):
-			keywords = {FUNC:func}
-			self.map(*args, **keywords)
-			return func
-		return decorator_function
-
-	__call__ = map
-
-	def _add_binding(self, *keys, **actions):
-		assert keys
-		bind = Binding(keys, actions)
-		for key in keys:
-			self.set(parse_keybinding(key), bind)
-
-	def unmap(self, *keys):
-		for key in keys:
-			self.unset(parse_keybinding(key))
-
-	def __getitem__(self, key):
-		return self.traverse(parse_keybinding(key))
-
-
-class KeyMapWithDirections(KeyMap):
-	def __init__(self, *args, **keywords):
-		Tree.__init__(self, *args, **keywords)
-		self.directions = KeyMap()
-
-	def merge(self, other):
-		Tree.merge(self, other)
-		if hasattr(other, 'directions'):
-			Tree.merge(self.directions, other.directions)
-
-	def dir(self, *args, **keywords):
-		if ALIASARG in keywords:
-			self.directions.map(*args, **keywords)
-		else:
-			self.directions.map(*args, dir=Direction(**keywords))
-
-
-class KeyManager(object):
-	def __init__(self, keybuffer, contexts):
-		self._keybuffer = keybuffer
-		self._list_of_contexts = contexts
-		self.clear()
-
-	def clear(self):
-		self.contexts = dict()
-		for context in self._list_of_contexts:
-			self.contexts[context] = KeyMapWithDirections()
-
-	def map(self, context, *args, **keywords):
-		self.get_context(context).map(*args, **keywords)
-
-	def dir(self, context, *args, **keywords):
-		self.get_context(context).dir(*args, **keywords)
-
-	def unmap(self, context, *args, **keywords):
-		self.get_context(context).unmap(*args, **keywords)
-
-	def merge_all(self, keymapwithdirection):
-		for context, keymap in self.contexts.items():
-			keymap.merge(keymapwithdirection)
-
-	def get_context(self, context):
-		assert isinstance(context, str)
-		assert context in self.contexts, "no such context: " + context
-		return self.contexts[context]
-
-	def use_context(self, context):
-		context = self.get_context(context)
-		if self._keybuffer.keymap is not context:
-			self._keybuffer.assign(context, context.directions)
-			self._keybuffer.clear()
-
-
-class Binding(object):
-	"""The keybinding object"""
-	def __init__(self, keys, actions):
-		assert hasattr(keys, '__iter__')
-		assert isinstance(actions, dict)
-		self.actions = actions
-		try:
-			self.function = self.actions[FUNC]
-		except KeyError:
-			self.function = None
-		try:
-			self.direction = self.actions[DIRARG]
-		except KeyError:
-			self.direction = None
-		try:
-			alias = self.actions[ALIASARG]
-		except KeyError:
-			self.alias = None
-		else:
-			self.alias = tuple(parse_keybinding(alias))
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 0314888d..3427af7f 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -98,7 +98,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		try:
 			cmd_class = self.commands.get_command(command_name)
 		except:
-			self.notify("Command not found: `%s'" % command_name)
+			self.notify("Command not found: `%s'" % command_name, bad=True)
 		else:
 			try:
 				cmd_class(string).execute()
@@ -170,6 +170,8 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	def source_cmdlist(self, filename, narg=None):
 		for line in open(filename, 'r'):
 			line = line.rstrip("\r\n")
+			if line.startswith("#") or not line.strip():
+				continue
 			try:
 				self.execute_console(line)
 			except Exception as e:
diff --git a/ranger/core/environment.py b/ranger/core/environment.py
index cf140410..90f0fefa 100644
--- a/ranger/core/environment.py
+++ b/ranger/core/environment.py
@@ -20,13 +20,11 @@ import socket
 from os.path import abspath, normpath, join, expanduser, isdir
 
 from ranger.fsobject import Directory
-from ranger.container import KeyBuffer, KeyManager, History
+from ranger.ext.keybindings import KeyBuffer, KeyMaps
+from ranger.container import History
 from ranger.ext.signals import SignalDispatcher
 from ranger.core.shared import SettingsAware
 
-ALLOWED_CONTEXTS = ('browser', 'pager', 'embedded_pager', 'taskview',
-		'console')
-
 class Environment(SettingsAware, SignalDispatcher):
 	"""
 	A collection of data which is relevant for more than one class.
@@ -42,8 +40,6 @@ class Environment(SettingsAware, SignalDispatcher):
 	last_search = None
 	pathway = None
 	path = None
-	keybuffer = None
-	keymanager = None
 
 	def __init__(self, path):
 		SignalDispatcher.__init__(self)
@@ -51,8 +47,8 @@ class Environment(SettingsAware, SignalDispatcher):
 		self._cf = None
 		self.pathway = ()
 		self.directories = {}
-		self.keybuffer = KeyBuffer(None, None)
-		self.keymanager = KeyManager(self.keybuffer, ALLOWED_CONTEXTS)
+		self.keybuffer = KeyBuffer()
+		self.keymaps = KeyMaps(self.keybuffer)
 		self.copy = set()
 		self.history = History(self.settings.max_history_size, unique=False)
 
diff --git a/ranger/core/helper.py b/ranger/core/helper.py
index 62ab9091..56b8e5ad 100644
--- a/ranger/core/helper.py
+++ b/ranger/core/helper.py
@@ -112,10 +112,6 @@ def load_settings(fm, clean):
 			from ranger.defaults import apps
 		fm.apps = apps.CustomApplications()
 
-		# Setup keymanager
-		keymanager = ranger.core.shared.EnvironmentAware.env.keymanager
-		ranger.api.keys.keymanager = keymanager
-
 		# Load rc.conf
 		conf = fm.confpath('rc.conf')
 		if os.access(conf, os.R_OK):
diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py
index d2c02865..a40d447d 100644
--- a/ranger/defaults/commands.py
+++ b/ranger/defaults/commands.py
@@ -778,12 +778,34 @@ class map_(Command):
 	name = 'map'
 	context = 'browser'
 	resolve_macros = False
+
 	def execute(self):
-		command = self.rest(2)
-		self.fm.env.keymanager.map(self.context, self.arg(1),
-			func=lambda arg: arg.fm.execute_console(command),
-#			func=lambda arg: arg.fm.cmd(command, n=arg.n, any=arg.matches),
-			help=command)
+		self.fm.env.keymaps.bind(self.context, self.arg(1), self.rest(2))
+
+
+class cmap(map_):
+	""":cmap <keysequence> <command>
+	Maps a command to a keysequence in the "console" context.
+
+	Example:
+	cmap <ESC> console_close
+	cmap <C-x> console_type test
+	"""
+	context = 'console'
+
+
+class tmap(map_):
+	""":tmap <keysequence> <command>
+	Maps a command to a keysequence in the "taskview" context.
+	"""
+	context = 'taskview'
+
+
+class pmap(map_):
+	""":pmap <keysequence> <command>
+	Maps a command to a keysequence in the "pager" context.
+	"""
+	context = 'pager'
 
 
 class filter(Command):
diff --git a/ranger/defaults/rc.conf b/ranger/defaults/rc.conf
index c462bb65..59401e3b 100644
--- a/ranger/defaults/rc.conf
+++ b/ranger/defaults/rc.conf
@@ -1,4 +1,8 @@
 # VIM
+map x break_shit
+map gg eval fm.move(to=0)
+map G  eval fm.move(to=-1)
+map : eval fm.open_console()
 map j eval fm.move(down=1)
 map k eval fm.move(up=1)
 map h eval fm.move(left=1)
@@ -6,6 +10,8 @@ map l eval fm.move(right=1)
 map q quit
 map Q quit
 
+cmap <ESC> quit
+
 map R eval fm.reload_cwd()
 map <c-r> eval fm.reset()
 map <c-l> eval fm.redraw_window()
diff --git a/ranger/ext/keybindings.py b/ranger/ext/keybindings.py
new file mode 100644
index 00000000..36b13ad0
--- /dev/null
+++ b/ranger/ext/keybindings.py
@@ -0,0 +1,96 @@
+# 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/>.
+
+from ranger.ext.keybinding_parser import (parse_keybinding,
+	ANYKEY, PASSIVE_ACTION)
+
+digits = set(range(ord('0'), ord('9')+1))
+
+class KeyMaps(dict):
+	def __init__(self, keybuffer=None):
+		dict.__init__(self)
+		self.keybuffer = keybuffer
+		self.used_keymap = None
+
+	def bind(self, context, keys, leaf):
+		try:
+			pointer = self[context]
+		except:
+			self[context] = pointer = dict()
+		keys = list(parse_keybinding(keys))
+		if not keys:
+			return
+		last_key = keys[-1]
+		for key in keys[:-1]:
+			try:
+				pointer = pointer[key]
+			except:
+				pointer[key] = pointer = dict()
+		pointer[last_key] = leaf
+
+	def use_keymap(self, keymap_name):
+		self.keybuffer.keymap = self.get(keymap_name, dict())
+		if self.used_keymap != keymap_name:
+			self.used_keymap = keymap_name
+			self.keybuffer.clear()
+
+
+class KeyBuffer(object):
+	any_key     = ANYKEY
+	passive_key = PASSIVE_ACTION
+
+	def __init__(self, keymap=None):
+		self.keymap = keymap
+		self.clear()
+
+	def clear(self):
+		self.keys = []
+		self.pointer = self.keymap
+		self.result = None
+		self.quantifier = None
+		self.finished_parsing_quantifier = False
+		self.finished_parsing = False
+		self.parse_error = False
+
+	def add(self, key):
+		self.keys.append(key)
+		if not self.finished_parsing_quantifier and key in digits:
+			if self.quantifier is None:
+				self.quantifier = 0
+			self.quantifier = self.quantifier * 10 + key - 48 # (48 = ord(0))
+		else:
+			self.finished_parsing_quantifier = True
+
+			moved = True
+			if key in self.pointer:
+				self.pointer = self.pointer[key]
+			elif self.any_key in self.pointer:
+				self.pointer = self.pointer[self.any_key]
+			else:
+				moved = False
+
+			if moved:
+				if isinstance(self.pointer, dict):
+					if self.passive_key in self.pointer:
+						self.result = self.pointer[self.passive_key]
+				else:
+					self.result = self.pointer
+					self.finished_parsing = True
+			else:
+				self.finished_parsing = True
+				self.parse_error = True
+
+	def __str__(self):
+		return repr(self.keys)
diff --git a/ranger/ext/tree.py b/ranger/ext/tree.py
deleted file mode 100644
index a954136b..00000000
--- a/ranger/ext/tree.py
+++ /dev/null
@@ -1,136 +0,0 @@
-# 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/>.
-
-class Tree(object):
-	def __init__(self, dictionary=None, parent=None, key=None):
-		if dictionary is None:
-			self._tree = dict()
-		else:
-			self._tree = dictionary
-		self.key = key
-		self.parent = parent
-
-	def copy(self):
-		"""Create a deep copy"""
-		def deep_copy_dict(dct):
-			dct = dct.copy()
-			for key, val in dct.items():
-				if isinstance(val, dict):
-					dct[key] = deep_copy_dict(val)
-			return dct
-		newtree = Tree()
-		if isinstance(self._tree, dict):
-			newtree._tree = deep_copy_dict(self._tree)
-		else:
-			newtree._tree = self._tree
-		return newtree
-
-	def merge(self, other, copy=False):
-		"""Merge another Tree into a copy of self"""
-		def deep_merge(branch, otherbranch):
-			assert isinstance(otherbranch, dict)
-			if not isinstance(branch, dict):
-				branch = dict()
-			elif copy:
-				branch = branch.copy()
-			for key, val in otherbranch.items():
-				if isinstance(val, dict):
-					if key not in branch:
-						branch[key] = None
-					branch[key] = deep_merge(branch[key], val)
-				else:
-					branch[key] = val
-			return branch
-
-		if isinstance(self._tree, dict) and isinstance(other._tree, dict):
-			content = deep_merge(self._tree, other._tree)
-		elif copy and hasattr(other._tree, 'copy'):
-			content = other._tree.copy()
-		else:
-			content = other._tree
-		return type(self)(content)
-
-	def set(self, keys, value, force=True):
-		"""Sets the element at the end of the path to <value>."""
-		if not isinstance(keys, (list, tuple)):
-			keys = tuple(keys)
-		if len(keys) == 0:
-			self.replace(value)
-		else:
-			fnc = force and self.plow or self.traverse
-			subtree = fnc(keys)
-			subtree.replace(value)
-
-	def unset(self, iterable):
-		chars = list(iterable)
-		first = True
-
-		while chars:
-			if first or isinstance(subtree, Tree) and subtree.empty():
-				top = chars.pop()
-				subtree = self.traverse(chars)
-				assert top in subtree._tree, "no such key: " + chr(top)
-				del subtree._tree[top]
-			else:
-				break
-			first = False
-
-	def empty(self):
-		return len(self._tree) == 0
-
-	def replace(self, value):
-		if self.parent:
-			self.parent[self.key] = value
-		self._tree = value
-
-	def plow(self, iterable):
-		"""Move along a path, creating nonexistant subtrees"""
-		tree = self._tree
-		last_tree = None
-		char = None
-		for char in iterable:
-			try:
-				newtree = tree[char]
-				if not isinstance(newtree, dict):
-					raise KeyError()
-			except KeyError:
-				newtree = dict()
-				tree[char] = newtree
-			last_tree = tree
-			tree = newtree
-		if isinstance(tree, dict):
-			return type(self)(tree, parent=last_tree, key=char)
-		else:
-			return tree
-
-	def traverse(self, iterable):
-		"""Move along a path, raising exceptions when failed"""
-		tree = self._tree
-		last_tree = tree
-		char = None
-		for char in iterable:
-			last_tree = tree
-			try:
-				tree = tree[char]
-			except TypeError:
-				raise KeyError("trying to enter leaf")
-			except KeyError:
-				raise KeyError(repr(char) + " not in tree " + str(tree))
-		if isinstance(tree, dict):
-			return type(self)(tree, parent=last_tree, key=char)
-		else:
-			return tree
-
-	__getitem__ = traverse
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index cd11a0d0..a2c5c9a1 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -20,7 +20,6 @@ import _curses
 
 from .displayable import DisplayableContainer
 from ranger.gui.curses_shortcuts import ascii_only
-from ranger.container.keymap import CommandArgs
 from .mouse_event import MouseEvent
 from ranger.ext.keybinding_parser import ALT_KEY
 
@@ -59,8 +58,7 @@ class UI(DisplayableContainer):
 			self.fm = fm
 
 		self.win = curses.initscr()
-		self.env.keymanager.use_context('browser')
-		self.env.keybuffer.clear()
+		self.env.keymaps.use_keymap('browser')
 
 		DisplayableContainer.__init__(self, None)
 
@@ -138,37 +136,26 @@ class UI(DisplayableContainer):
 
 		if key < 0:
 			self.env.keybuffer.clear()
-			return
 
-		if DisplayableContainer.press(self, key):
-			return
+		elif not DisplayableContainer.press(self, key):
+			self.env.keymaps.use_keymap('browser')
+			self.press(key)
 
+	def press(self, key):
+		keybuffer = self.env.keybuffer
 		self.status.clear_message()
-
-		self.env.keymanager.use_context('browser')
-		self.env.key_append(key)
-		kbuf = self.env.keybuffer
-		cmd = kbuf.command
-
 		self.fm.hide_bookmarks()
 
-		if kbuf.failure:
-			kbuf.clear()
-			return
-		elif not cmd:
-			return
+		keybuffer.add(key)
 
-		self.env.cmd = cmd
-
-		if cmd.function:
+		if keybuffer.result is not None:
 			try:
-				cmd.function(CommandArgs.from_widget(self.fm))
-			except Exception as error:
-				self.fm.notify(error)
-			if kbuf.done:
-				kbuf.clear()
-		else:
-			kbuf.clear()
+				self.fm.execute_console(keybuffer.result)
+			finally:
+				if keybuffer.finished_parsing:
+					keybuffer.clear()
+		elif keybuffer.finished_parsing:
+			keybuffer.clear()
 
 	def handle_keys(self, *keys):
 		for key in keys:
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 00d6828b..f50a5f3c 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -151,28 +151,8 @@ class Console(Widget):
 		self.line = ''
 
 	def press(self, key):
-		self.env.keymanager.use_context('console')
-		self.env.key_append(key)
-		kbuf = self.env.keybuffer
-		cmd = kbuf.command
-
-		if kbuf.failure:
-			kbuf.clear()
-			return
-		elif not cmd:
-			return
-
-		self.env.cmd = cmd
-
-		if cmd.function:
-			try:
-				cmd.function(CommandArgs.from_widget(self))
-			except Exception as error:
-				self.fm.notify(error)
-			if kbuf.done:
-				kbuf.clear()
-		else:
-			kbuf.clear()
+		self.env.keymaps.use_keymap('console')
+		self.fm.ui.press(key)
 
 	def type_key(self, key):
 		self.tab_deque = None
diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py
index cf8f91e4..3c11d38c 100644
--- a/ranger/gui/widgets/pager.py
+++ b/ranger/gui/widgets/pager.py
@@ -110,28 +110,8 @@ class Pager(Widget):
 					offset=-self.hei + 1)
 
 	def press(self, key):
-		self.env.keymanager.use_context(self.embedded and 'embedded_pager' or 'pager')
-		self.env.key_append(key)
-		kbuf = self.env.keybuffer
-		cmd = kbuf.command
-
-		if kbuf.failure:
-			kbuf.clear()
-			return
-		elif not cmd:
-			return
-
-		self.env.cmd = cmd
-
-		if cmd.function:
-			try:
-				cmd.function(CommandArgs.from_widget(self))
-			except Exception as error:
-				self.fm.notify(error)
-			if kbuf.done:
-				kbuf.clear()
-		else:
-			kbuf.clear()
+		self.env.keymaps.use_keymap(self.embedded and 'embedded_pager' or 'pager')
+		self.fm.ui.press(key)
 
 	def set_source(self, source, strip=False):
 		if self.source and self.source_is_stream:
diff --git a/ranger/gui/widgets/taskview.py b/ranger/gui/widgets/taskview.py
index e988b08c..2dcace92 100644
--- a/ranger/gui/widgets/taskview.py
+++ b/ranger/gui/widgets/taskview.py
@@ -96,28 +96,8 @@ class TaskView(Widget, Accumulator):
 		self.fm.loader.move(_from=i, to=to)
 
 	def press(self, key):
-		self.env.keymanager.use_context('taskview')
-		self.env.key_append(key)
-		kbuf = self.env.keybuffer
-		cmd = kbuf.command
-
-		if kbuf.failure:
-			kbuf.clear()
-			return
-		elif not cmd:
-			return
-
-		self.env.cmd = cmd
-
-		if cmd.function:
-			try:
-				cmd.function(CommandArgs.from_widget(self))
-			except Exception as error:
-				self.fm.notify(error)
-			if kbuf.done:
-				kbuf.clear()
-		else:
-			kbuf.clear()
+		self.env.keymaps.use_keymap('taskview')
+		self.fm.ui.press(key)
 
 	def get_list(self):
 		return self.fm.loader.queue