summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ranger/api/keys.py2
-rw-r--r--ranger/container/keymap.py32
-rw-r--r--ranger/defaults/keys.py101
-rw-r--r--test/tc_newkeys.py30
4 files changed, 107 insertions, 58 deletions
diff --git a/ranger/api/keys.py b/ranger/api/keys.py
index d1011d22..97e07ae5 100644
--- a/ranger/api/keys.py
+++ b/ranger/api/keys.py
@@ -21,7 +21,7 @@ from inspect import getargspec, ismethod
 from ranger import RANGERDIR
 from ranger.gui.widgets import console_mode as cmode
 from ranger.container.bookmarks import ALLOWED_KEYS as ALLOWED_BOOKMARK_KEYS
-from ranger.container.keymap import KeyMap, Direction
+from ranger.container.keymap import KeyMap, Direction, KeyMapWithDirections
 
 class Wrapper(object):
 	def __init__(self, firstattr):
diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py
index 29d6e629..d39df381 100644
--- a/ranger/container/keymap.py
+++ b/ranger/container/keymap.py
@@ -89,6 +89,23 @@ class KeyMap(Tree):
 		return self.traverse(translate_keys(key))
 
 
+class KeyMapWithDirections(KeyMap):
+	def __init__(self, *args, **keywords):
+		Tree.__init__(self, *args, **keywords)
+		self.directions = KeyMap()
+
+	def merge(self, other):
+		assert hasattr(other, 'directions'), 'Merging with wrong type?'
+		Tree.merge(self, other)
+		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
@@ -96,28 +113,29 @@ class KeyManager(object):
 		self.clear()
 
 	def clear(self):
-		self._contexts = {
-			'directions': KeyMap(),
-		}
+		self._contexts = dict()
 		for context in self._list_of_contexts:
-			self._contexts[context] = KeyMap()
+			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 get_context(self, context):
 		assert isinstance(context, str)
 		assert context in self._contexts, "no such context!"
 		return self._contexts[context]
 	__getitem__ = get_context
 
-	def use_context(self, context, directions='directions'):
+	def use_context(self, context):
 		context = self.get_context(context)
 		if self._keybuffer.keymap is not context:
-			directions = self.get_context(directions)
-			self._keybuffer.assign(context, directions)
+			self._keybuffer.assign(context, context.directions)
 			self._keybuffer.clear()
 
+
 class Binding(object):
 	"""The keybinding object"""
 	def __init__(self, keys, actions):
diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py
index 57a0d415..061e9091 100644
--- a/ranger/defaults/keys.py
+++ b/ranger/defaults/keys.py
@@ -27,6 +27,25 @@ arg.wdg: the widget or ui instance
 arg.n: the number typed before the key combination (if allowed)
 arg.keys: the string representation of the used key combination
 arg.keybuffer: the keybuffer instance
+
+Direction keys are special.  They must be mapped with: map.dir(*keys, **args)
+where args is a dict of values such as up, left, down, right, absolute,
+relative, pages, etc...
+Example: map.dir('gg', to=0)
+Direction keys can be accessed in a mapping that contians "<dir>".
+
+Example scenario
+----------------
+If this keys are defined:
+map("dd", "d<dir>", fm.cut(foo=bar))
+map.dir("gg", to=0)
+
+Type in the keys on the left and the function on the right will be executed:
+dd        => fm.cut(foo=bar)
+5dd       => fm.cut(foo=bar, narg=5)
+dgg       => fm.cut(foo=bar, dirarg=Direction(to=0))
+5dgg      => fm.cut(foo=bar, narg=5, dirarg=Direction(to=0))
+5d3gg     => fm.cut(foo=bar, narg=5, dirarg=Direction(to=3))
 """
 
 from ranger.api.keys import *
@@ -35,36 +54,48 @@ from ranger import log
 # ===================================================================
 # == Define keys for everywhere:
 # ===================================================================
-map = global_keys = KeyMap()
+map = global_keys = KeyMapWithDirections()
 map('Q', fm.exit())
 map('<C-L>', fm.redraw_window())
 map('<backspace2>', alias='<backspace>')  # Backspace is bugged sometimes
 
+#map('<dir>', wdg.move())
 @map('<dir>') # move around with direction keys
 def move(arg):
 	arg.wdg.move(narg=arg.n, **arg.direction)
 
+# -------------------------------------------------- direction keys
+map.dir('<down>', down=1)
+map.dir('<up>', down=-1)
+map.dir('<left>', right=-1)
+map.dir('<right>', right=1)
+map.dir('<home>', down=0, absolute=True)
+map.dir('<end>', down=-1, absolute=True)
+map.dir('<pagedown>', down=1, pages=True)
+map.dir('<pageup>', down=-1, pages=True)
+map.dir('%', down=1, percentage=True, absolute=True)
+
 
 # ===================================================================
 # == Define aliases
 # ===================================================================
-map = vim_aliases = KeyMap()
-map('j', alias='<down>')
-map('k', alias='<up>')
-map('h', alias='<left>')
-map('l', alias='<right>')
-map('gg', alias='<home>')
-map('G', alias='<end>')
-map('<C-F>', alias='<pagedown>')
-map('<C-B>', alias='<pageup>')
-
-map = readline_aliases = KeyMap()
-map('<C-B>', alias='<left>')
-map('<C-F>', alias='<right>')
-map('<C-A>', alias='<home>')
-map('<C-E>', alias='<end>')
-map('<C-D>', alias='<delete>')
-map('<C-H>', alias='<backspace>')
+map = vim_aliases = KeyMapWithDirections()
+map.dir('j', alias='<down>')
+map.dir('k', alias='<up>')
+map.dir('h', alias='<left>')
+map.dir('l', alias='<right>')
+map.dir('gg', alias='<home>')
+map.dir('G', alias='<end>')
+map.dir('<C-F>', alias='<pagedown>')
+map.dir('<C-B>', alias='<pageup>')
+
+map = readline_aliases = KeyMapWithDirections()
+map.dir('<C-B>', alias='<left>')
+map.dir('<C-F>', alias='<right>')
+map.dir('<C-A>', alias='<home>')
+map.dir('<C-E>', alias='<end>')
+map.dir('<C-D>', alias='<delete>')
+map.dir('<C-H>', alias='<backspace>')
 
 
 # ===================================================================
@@ -74,6 +105,8 @@ map = keymanager['general']
 map.merge(global_keys)
 map.merge(vim_aliases)
 
+map('gg', fm.move(to=0))
+
 # --------------------------------------------------------- history
 map('H', fm.history_go(-1))
 map('L', fm.history_go(1))
@@ -87,8 +120,8 @@ map('v', fm.mark(all=True, toggle=True))
 map('V', fm.mark(all=True, val=False))
 
 # ------------------------------------------ file system operations
-map('yy', fm.copy())
-map('dd', fm.cut())
+map('yy', 'y<dir>', fm.copy())
+map('dd', 'd<dir>', fm.cut())
 map('pp', fm.paste())
 map('po', fm.paste(overwrite=True))
 map('pl', fm.paste_symlink())
@@ -215,7 +248,7 @@ map('r', fm.open_console(cmode.OPEN_QUICK))
 # ===================================================================
 # == Define keys for the pager
 # ===================================================================
-map = pager_keys = KeyMap()
+map = pager_keys = KeyMapWithDirections()
 map.merge(global_keys)
 map.merge(vim_aliases)
 
@@ -288,26 +321,6 @@ map('<C-Y>', wdg.paste())
 def type_key(arg):
 	arg.wdg.type_key(arg.match)
 
-# Override some global keys so we can type them:
-override = ('Q', '%')
-for key in override:
-	map(key, wdg.type_key(key))
-
-
-# ===================================================================
-# == Define direction keys
-# ===================================================================
-# Note that direction keys point to no functions, but Direction objects.
-# Direction keys are completely independent and can not be merged into
-# other keymaps.  You can't define or unmap direction keys on
-# a per-context-basis, instead use aliases.
-map = keymanager.get_context('directions')
-map('<down>', dir=Direction(down=1))
-map('<up>', dir=Direction(down=-1))
-map('<left>', dir=Direction(right=-1))
-map('<right>', dir=Direction(right=1))
-map('<home>', dir=Direction(down=0, absolute=True))
-map('<end>', dir=Direction(down=-1, absolute=True))
-map('<pagedown>', dir=Direction(down=1, pages=True))
-map('<pageup>', dir=Direction(down=-1, pages=True))
-map('%', dir=Direction(down=1, percentage=True, absolute=True))
+# Unmap some global keys so we can type them:
+map.unmap('Q')
+map.directions.unmap('%')
diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py
index 9a7f10c7..c06c2894 100644
--- a/test/tc_newkeys.py
+++ b/test/tc_newkeys.py
@@ -484,7 +484,8 @@ class Test(PressTestCase):
 		map('c', func)
 		map('<dir>', getdown)
 
-		keymanager.map('directions', 'j', dir=Direction(down=1))
+		keymanager.dir('foo', 'j', down=1)
+		keymanager.dir('bar', 'j', down=1)
 
 		keymanager.use_context('foo')
 		self.assertEqual(5, press('a'))
@@ -509,19 +510,24 @@ class Test(PressTestCase):
 		def func(arg):
 			return arg.direction.down()
 
-		km = KeyMap()
-		directions = KeyMap()
-		kb = KeyBuffer(km, directions)
+		km = KeyMapWithDirections()
+		kb = KeyBuffer(km, km.directions)
 		press = self._mkpress(kb)
 
 		km.map('<dir>', func)
 		km.map('d<dir>', func)
-		directions.map('j', dir=Direction(down=42))
+		km.dir('j', down=42)
+		km.dir('k', alias='j')
 		self.assertEqual(42, press('j'))
 
-		km.map('o', alias='j')
+		km.dir('o', alias='j')
+		km.dir('ick', alias='j')
 		self.assertEqual(42, press('o'))
+		self.assertEqual(42, press('dj'))
+		self.assertEqual(42, press('dk'))
 		self.assertEqual(42, press('do'))
+		self.assertEqual(42, press('dick'))
+		self.assertPressFails(kb, 'dioo')
 
 	def test_both_directory_and_any_key(self):
 		def func(arg):
@@ -572,5 +578,17 @@ class Test(PressTestCase):
 #		self.assertPressFails(kb, 'agh')
 		self.assertEqual(1, press('agg'))
 
+	def test_keymap_with_dir(self):
+		def func(arg):
+			return arg.direction.down()
+
+		km = KeyMapWithDirections()
+		kb = KeyBuffer(km, km.directions)
+
+		press = self._mkpress(kb)
+
+		km.map('abc<dir>', func)
+		km.dir('j', down=42)
+		self.assertEqual(42, press('abcj'))
 
 if __name__ == '__main__': main()