From d8cd2b7573a43d94ac28a83d3ff4ffca14cde11a Mon Sep 17 00:00:00 2001 From: hut Date: Tue, 9 Feb 2010 10:23:08 +0100 Subject: started new key parser --- test/tc_newkeys.py | 331 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 test/tc_newkeys.py (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py new file mode 100644 index 00000000..4b46b0f8 --- /dev/null +++ b/test/tc_newkeys.py @@ -0,0 +1,331 @@ +if __name__ == '__main__': from __init__ import init; init() +from unittest import TestCase, main + +from inspect import isfunction, getargspec +import inspect +from sys import intern + +FUNC = 'func' +DIRECTION = 'direction' +QUANTIFIER = 'n' +MATCH = intern('!!') + +def to_string(i): + """convert a ord'd integer to a string""" + try: + return chr(i) + except ValueError: + return '?' + +def is_ascii_digit(n): + return n >= 48 and n <= 57 + +class Direction(object): + """An object with a down and right method""" + def __init__(self, down=0, right=0): + self.down = down + self.right = right + + def copy(self): + new = type(self)() + new.__dict__.update(self.__dict__) + return new + + def __mul__(self, other): + copy = self.copy() + if other is not None: + copy.down *= other + copy.right *= other + return copy + +class CommandArgs(object): + """The arguments which are passed to a keybinding function""" + def __init__(self, fm, widget, keybuffer): + self.fm = fm + self.wdg = widget + self.keybuffer = keybuffer + self.n = keybuffer.quant1 + self.direction = keybuffer.direction + self.keys = str(keybuffer) + +class KeyBuffer(object): + """The evaluator and storage for pressed keys""" + def __init__(self, keymap, direction_keys): + self.keymap = keymap + self.direction_keys = direction_keys + self.clear() + + def add(self, key): + if self.failure: + return None + assert isinstance(key, int) + assert key >= 0 + + # evaluate first quantifier + if self.level == 0: + if is_ascii_digit(key): + if self.quant1 is None: + self.quant1 = 0 + self.quant1 = self.quant1 * 10 + key - 48 + else: + self.level = 1 + + # evaluate the command and the second quantifier. + # it's possible to jump between them. "x3xj" is equivalent to "xx3j" + if self.level == 1: + try: + self.tree_pointer = self.tree_pointer[key] + except KeyError: + try: + match = self.tree_pointer[MATCH] + except KeyError: + self.failure = True + return None + # self.command = match + if is_ascii_digit(key): + if self.quant2 is None: + self.quant2 = 0 + self.quant2 = self.quant2 * 10 + key - 48 + else: + self.level = 2 + self.tree_pointer = self.direction_keys._tree + else: + try: + match = self.tree_pointer[MATCH] + except KeyError: + pass + else: + self.command = match + if not match.has_direction: + if self.quant2 is not None: + self.direction = self.direction * self.quant2 + self.done = True + + # evaluate direction keys {j,k,gg,pagedown,...} + if self.level == 2: + try: + self.tree_pointer = self.tree_pointer[key] + except KeyError: + self.failure = True + else: + try: + match = self.tree_pointer[MATCH] + except KeyError: + pass + else: + self.direction = match.actions['dir'] * self.quant2 + self.done = True + + + def clear(self): + self.failure = False + self.done = False + self.quant1 = None + self.quant2 = None + self.command = None + self.direction = Direction(down=1) + self.all_keys = [] + self.tree_pointer = self.keymap._tree + self.direction_tree_pointer = self.direction_keys._tree + self.level = 0 + # level 0 = parsing quantifier 1 + # 1 = parsing command or quantifier 2 + # 2 = parsing direction + + def __str__(self): + """returns a concatenation of all characters""" + return "".join(to_string(c) for c in self.all_keys) + + def simulate_press(self, string): + for char in string: + self.add(ord(char)) + if self.done: + return self.command + if self.failure: + break + +class Keymap(object): + """Contains a tree with all the keybindings""" + def __init__(self): + self._tree = dict() + + def add(self, *args, **keywords): + if keywords: + return self.add_binding(*args, **keywords) + firstarg = args[0] + if isfunction(firstarg): + keywords[FUNC] = firstarg + return self.add_binding(*args[1:], **keywords) + def decorator_function(func): + keywords = {FUNC:func} + self.add(*args, **keywords) + return func + return decorator_function + + def _split(self, key): + assert isinstance(key, (tuple, int, str)) + if isinstance(key, tuple): + return key + if isinstance(key, str): + return (ord(k) for k in key) + if isinstance(key, int): + return (key, ) + raise TypeError(key) + + def add_binding(self, *keys, **actions): + assert keys + bind = binding(keys, actions) + + for key in keys: + assert key + chars = tuple(self._split(key)) + tree = self.traverse_tree(chars) + tree[MATCH] = bind + + def traverse_tree(self, generator): + tree = self._tree + for char in generator: + try: + tree = tree[char] + except KeyError: + tree[char] = dict() + tree = tree[char] + except TypeError: + raise TypeError("Attempting to override existing entry") + return tree + + def __getitem__(self, key): + tree = self._tree + for char in self._split(key): + try: + tree = tree[char] + except TypeError: + raise KeyError("trying to enter leaf") + except KeyError: + raise KeyError(str(char) + " not in tree " + str(tree)) + try: + return tree[MATCH] + except KeyError: + raise KeyError(str(char) + " not in tree " + str(tree)) + +class binding(object): + """The keybinding object""" + def __init__(self, keys, actions): + assert hasattr(keys, '__iter__') + assert isinstance(actions, dict) + self.keys = set(keys) + self.actions = actions + try: + self.function = self.actions[FUNC] + except KeyError: + self.function = None + self.has_direction = False + else: + argnames = getargspec(self.function)[0] + try: + self.has_direction = actions['with_direction'] + except KeyError: + self.has_direction = DIRECTION in argnames + + def add_keys(self, keys): + assert isinstance(keys, set) + self.keys |= keys + + def has(self, action): + return action in self.actions + + def action(self, key): + return self.actions[key] + +def n(value): + """ return n or value """ + def fnc(n=None): + if n is None: + return value + return n + return fnc + +def nd(n=1, direction=Direction()): + """ n * direction """ + if n is None: + n = 1 + return n * direction.down + +class Test(TestCase): + """The test cases""" + def test_add(self): + c = Keymap() + c.add(lambda *_: 'lolz', 'aa', 'b') + self.assert_(c['aa'].actions[FUNC](), 'lolz') + @c.add('a', 'c') + def test(): + return 5 + self.assert_(c['b'].actions[FUNC](), 'lolz') + self.assert_(c['c'].actions[FUNC](), 5) + self.assert_(c['a'].actions[FUNC](), 5) + + def test_quantifier(self): + km = Keymap() + directions = Keymap() + kb = KeyBuffer(km, directions) + km.add(n(5), 'd') + match = kb.simulate_press('3d') + self.assertEqual(3, match.function(kb.quant1)) + kb.clear() + match = kb.simulate_press('6223d') + self.assertEqual(6223, match.function(kb.quant1)) + kb.clear() + + def test_direction(self): + km = Keymap() + directions = Keymap() + kb = KeyBuffer(km, directions) + directions.add('j', dir=Direction(down=1)) + directions.add('k', dir=Direction(down=-1)) + km.add(nd, 'd') + km.add('dd', func=nd, with_direction=False) + + match = kb.simulate_press('3d5j') + self.assertEqual(15, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + match = kb.simulate_press('3d5k') + self.assertEqual(-15, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + match = kb.simulate_press('3d5d') + self.assertEqual(15, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + match = kb.simulate_press('3dd') + self.assertEqual(3, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + match = kb.simulate_press('dd') + self.assertEqual(1, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + km.add(nd, 'x') + km.add('xxxx', func=nd, with_direction=False) + + match = kb.simulate_press('xxxxj') + self.assertEqual(1, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + match = kb.simulate_press('xxxxjsomeinvalidchars') + self.assertEqual(1, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + self.assertEqual(None, kb.simulate_press('xxxj')) + kb.clear() + self.assertEqual(None, kb.simulate_press('xxj')) + kb.clear() + self.assertEqual(None, kb.simulate_press('xxkldfjalksdjklsfsldkj')) + kb.clear() + self.assertEqual(None, kb.simulate_press('xyj')) + kb.clear() + self.assertEqual(None, kb.simulate_press('x')) #direction missing + kb.clear() + + +if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0 From 8d56a585f6c0cbda42b8b3c2278309b67e46ad7d Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 10 Feb 2010 00:20:41 +0100 Subject: keyparser: some improvements --- test/tc_newkeys.py | 103 +++++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 51 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 4b46b0f8..e32ddbd5 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -1,5 +1,6 @@ if __name__ == '__main__': from __init__ import init; init() from unittest import TestCase, main +from pprint import pprint as print from inspect import isfunction, getargspec import inspect @@ -7,6 +8,8 @@ from sys import intern FUNC = 'func' DIRECTION = 'direction' +DIRKEY = 9999 +ANYKEY = 'any' QUANTIFIER = 'n' MATCH = intern('!!') @@ -75,26 +78,24 @@ class KeyBuffer(object): if self.level == 1: try: self.tree_pointer = self.tree_pointer[key] + except TypeError: + self.failure = True + return None except KeyError: - try: - match = self.tree_pointer[MATCH] - except KeyError: - self.failure = True - return None - # self.command = match if is_ascii_digit(key): if self.quant2 is None: self.quant2 = 0 self.quant2 = self.quant2 * 10 + key - 48 - else: + elif DIRKEY in self.tree_pointer: self.level = 2 + self.command = self.tree_pointer[DIRKEY] self.tree_pointer = self.direction_keys._tree - else: - try: - match = self.tree_pointer[MATCH] - except KeyError: - pass else: + self.failure = True + return None + else: + if not isinstance(self.tree_pointer, dict): + match = self.tree_pointer self.command = match if not match.has_direction: if self.quant2 is not None: @@ -108,11 +109,8 @@ class KeyBuffer(object): except KeyError: self.failure = True else: - try: - match = self.tree_pointer[MATCH] - except KeyError: - pass - else: + if not isinstance(self.tree_pointer, dict): + match = self.tree_pointer self.direction = match.actions['dir'] * self.quant2 self.done = True @@ -165,12 +163,20 @@ class Keymap(object): def _split(self, key): assert isinstance(key, (tuple, int, str)) if isinstance(key, tuple): - return key - if isinstance(key, str): - return (ord(k) for k in key) - if isinstance(key, int): - return (key, ) - raise TypeError(key) + for char in key: + yield char + elif isinstance(key, str): + for char in key: + if char == '.': + yield ANYKEY + elif char == '}': + yield DIRKEY + else: + yield ord(char) + elif isinstance(key, int): + yield key + else: + raise TypeError(key) def add_binding(self, *keys, **actions): assert keys @@ -179,8 +185,9 @@ class Keymap(object): for key in keys: assert key chars = tuple(self._split(key)) - tree = self.traverse_tree(chars) - tree[MATCH] = bind + tree = self.traverse_tree(chars[:-1]) + if isinstance(tree, dict): + tree[chars[-1]] = bind def traverse_tree(self, generator): tree = self._tree @@ -204,7 +211,7 @@ class Keymap(object): except KeyError: raise KeyError(str(char) + " not in tree " + str(tree)) try: - return tree[MATCH] + return tree except KeyError: raise KeyError(str(char) + " not in tree " + str(tree)) @@ -282,40 +289,34 @@ class Test(TestCase): kb = KeyBuffer(km, directions) directions.add('j', dir=Direction(down=1)) directions.add('k', dir=Direction(down=-1)) - km.add(nd, 'd') + km.add(nd, 'd}') km.add('dd', func=nd, with_direction=False) - match = kb.simulate_press('3d5j') - self.assertEqual(15, match.function(n=kb.quant1, direction=kb.direction)) - kb.clear() - - match = kb.simulate_press('3d5k') - self.assertEqual(-15, match.function(n=kb.quant1, direction=kb.direction)) - kb.clear() - - match = kb.simulate_press('3d5d') - self.assertEqual(15, match.function(n=kb.quant1, direction=kb.direction)) - kb.clear() - match = kb.simulate_press('3dd') - self.assertEqual(3, match.function(n=kb.quant1, direction=kb.direction)) - kb.clear() + def press(keys): + kb.clear() + match = kb.simulate_press(keys) + self.assertFalse(kb.failure, "parsing keys '"+keys+"' did fail!") + self.assertTrue(kb.done, "parsing keys '"+keys+ \ + "' did not complete!") + dic = {QUANTIFIER:kb.quant1, DIRECTION:kb.direction} + return match.function(**dic) - match = kb.simulate_press('dd') - self.assertEqual(1, match.function(n=kb.quant1, direction=kb.direction)) - kb.clear() + self.assertEqual( 3, press('3ddj')) + self.assertEqual( 15, press('3d5j')) + self.assertEqual(-15, press('3d5k')) + self.assertEqual( 15, press('3d5d')) + self.assertEqual( 3, press('3dd')) + self.assertEqual( 1, press('dd')) - km.add(nd, 'x') + km.add(nd, 'x}') km.add('xxxx', func=nd, with_direction=False) - match = kb.simulate_press('xxxxj') - self.assertEqual(1, match.function(n=kb.quant1, direction=kb.direction)) - kb.clear() + self.assertEqual(1, press('xxxxj')) + self.assertEqual(1, press('xxxxjsomeinvalitchars')) - match = kb.simulate_press('xxxxjsomeinvalidchars') - self.assertEqual(1, match.function(n=kb.quant1, direction=kb.direction)) + # these combinations should break: kb.clear() - self.assertEqual(None, kb.simulate_press('xxxj')) kb.clear() self.assertEqual(None, kb.simulate_press('xxj')) -- cgit 1.4.1-2-gfad0 From 39db95d920dbd42c082d57f9ce9f299fba70802c Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 10 Feb 2010 01:25:34 +0100 Subject: keyparser: "." matches any character --- test/tc_newkeys.py | 94 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 23 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index e32ddbd5..39d6b23a 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -8,8 +8,8 @@ from sys import intern FUNC = 'func' DIRECTION = 'direction' -DIRKEY = 9999 -ANYKEY = 'any' +DIRKEY = 9001 +ANYKEY = 9002 QUANTIFIER = 'n' MATCH = intern('!!') @@ -50,6 +50,7 @@ class CommandArgs(object): self.n = keybuffer.quant1 self.direction = keybuffer.direction self.keys = str(keybuffer) + self.moo = keybuffer.moo class KeyBuffer(object): """The evaluator and storage for pressed keys""" @@ -66,7 +67,7 @@ class KeyBuffer(object): # evaluate first quantifier if self.level == 0: - if is_ascii_digit(key): + if is_ascii_digit(key) and ANYKEY not in self.tree_pointer: if self.quant1 is None: self.quant1 = 0 self.quant1 = self.quant1 * 10 + key - 48 @@ -82,7 +83,7 @@ class KeyBuffer(object): self.failure = True return None except KeyError: - if is_ascii_digit(key): + if is_ascii_digit(key) and ANYKEY not in self.tree_pointer: if self.quant2 is None: self.quant2 = 0 self.quant2 = self.quant2 * 10 + key - 48 @@ -90,17 +91,15 @@ class KeyBuffer(object): self.level = 2 self.command = self.tree_pointer[DIRKEY] self.tree_pointer = self.direction_keys._tree + elif ANYKEY in self.tree_pointer: + self.moo.append(key) + self.tree_pointer = self.tree_pointer[ANYKEY] + self._try_to_finish() else: self.failure = True return None else: - if not isinstance(self.tree_pointer, dict): - match = self.tree_pointer - self.command = match - if not match.has_direction: - if self.quant2 is not None: - self.direction = self.direction * self.quant2 - self.done = True + self._try_to_finish() # evaluate direction keys {j,k,gg,pagedown,...} if self.level == 2: @@ -114,11 +113,20 @@ class KeyBuffer(object): self.direction = match.actions['dir'] * self.quant2 self.done = True + def _try_to_finish(self): + if not isinstance(self.tree_pointer, dict): + match = self.tree_pointer + self.command = match + if not match.has_direction: + if self.quant2 is not None: + self.direction = self.direction * self.quant2 + self.done = True def clear(self): self.failure = False self.done = False self.quant1 = None + self.moo = [] self.quant2 = None self.command = None self.direction = Direction(down=1) @@ -252,14 +260,32 @@ def n(value): return n return fnc -def nd(n=1, direction=Direction()): +def nd(arg): """ n * direction """ - if n is None: + if arg.n is None: n = 1 - return n * direction.down + else: + n = arg.n + if arg.direction is None: + dir = Direction(down=1) + else: + dir = arg.direction + return n * dir.down class Test(TestCase): """The test cases""" + def _mkpress(self, keybuffer, keymap): + def press(keys): + keybuffer.clear() + match = keybuffer.simulate_press(keys) + self.assertFalse(keybuffer.failure, + "parsing keys '"+keys+"' did fail!") + self.assertTrue(keybuffer.done, + "parsing keys '"+keys+"' did not complete!") + arg = CommandArgs(None, None, keybuffer) + return match.function(arg) + return press + def test_add(self): c = Keymap() c.add(lambda *_: 'lolz', 'aa', 'b') @@ -292,15 +318,7 @@ class Test(TestCase): km.add(nd, 'd}') km.add('dd', func=nd, with_direction=False) - - def press(keys): - kb.clear() - match = kb.simulate_press(keys) - self.assertFalse(kb.failure, "parsing keys '"+keys+"' did fail!") - self.assertTrue(kb.done, "parsing keys '"+keys+ \ - "' did not complete!") - dic = {QUANTIFIER:kb.quant1, DIRECTION:kb.direction} - return match.function(**dic) + press = self._mkpress(kb, km) self.assertEqual( 3, press('3ddj')) self.assertEqual( 15, press('3d5j')) @@ -328,5 +346,35 @@ class Test(TestCase): self.assertEqual(None, kb.simulate_press('x')) #direction missing kb.clear() + def test_any_key(self): + km = Keymap() + directions = Keymap() + kb = KeyBuffer(km, directions) + directions.add('j', dir=Direction(down=1)) + directions.add('k', dir=Direction(down=-1)) + + def cat(arg): + n = arg.n is None and 1 or arg.n + return ''.join(chr(c) for c in arg.moo) * n + + km.add(cat, 'return.') + km.add(cat, 'cat4....') + + press = self._mkpress(kb, km) + + self.assertEqual('x', press('returnx')) + self.assertEqual('abcd', press('cat4abcd')) + self.assertEqual('abcdabcd', press('2cat4abcd')) + self.assertEqual('55555', press('5return5')) + + km.add(lambda _: Ellipsis, '.') + self.assertEqual('x', press('returnx')) + self.assertEqual('abcd', press('cat4abcd')) + self.assertEqual(Ellipsis, press('2cat4abcd')) + self.assertEqual(Ellipsis, press('5return5')) + self.assertEqual(Ellipsis, press('f')) + self.assertEqual(Ellipsis, press('ß')) + self.assertEqual(Ellipsis, press('ア')) + self.assertEqual(Ellipsis, press('9')) if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0 From 95e3dfb1d6cdbb4b3501236d44be6ba1afe47462 Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 10 Feb 2010 02:16:48 +0100 Subject: keyparser: reworked --- test/tc_newkeys.py | 112 +++++++++++++++++++++++++++++------------------------ 1 file changed, 61 insertions(+), 51 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 39d6b23a..2db9b160 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -47,8 +47,9 @@ class CommandArgs(object): self.fm = fm self.wdg = widget self.keybuffer = keybuffer - self.n = keybuffer.quant1 - self.direction = keybuffer.direction + self.n = keybuffer.quant + self.direction = keybuffer.directions and keybuffer.directions[0] or None + self.directions = keybuffer.directions self.keys = str(keybuffer) self.moo = keybuffer.moo @@ -65,32 +66,24 @@ class KeyBuffer(object): assert isinstance(key, int) assert key >= 0 - # evaluate first quantifier - if self.level == 0: - if is_ascii_digit(key) and ANYKEY not in self.tree_pointer: - if self.quant1 is None: - self.quant1 = 0 - self.quant1 = self.quant1 * 10 + key - 48 - else: - self.level = 1 + # evaluate quantifiers + if self.eval_quantifier and self._do_eval_quantifier(key): + return - # evaluate the command and the second quantifier. - # it's possible to jump between them. "x3xj" is equivalent to "xx3j" - if self.level == 1: + # evaluate the command + if self.eval_command: try: self.tree_pointer = self.tree_pointer[key] except TypeError: + print(self.tree_pointer) self.failure = True return None except KeyError: - if is_ascii_digit(key) and ANYKEY not in self.tree_pointer: - if self.quant2 is None: - self.quant2 = 0 - self.quant2 = self.quant2 * 10 + key - 48 - elif DIRKEY in self.tree_pointer: - self.level = 2 - self.command = self.tree_pointer[DIRKEY] - self.tree_pointer = self.direction_keys._tree + if DIRKEY in self.tree_pointer: + self.eval_command = False + self.eval_quantifier = True + self.tree_pointer = self.tree_pointer[DIRKEY] + self.dir_tree_pointer = self.direction_keys._tree elif ANYKEY in self.tree_pointer: self.moo.append(key) self.tree_pointer = self.tree_pointer[ANYKEY] @@ -101,42 +94,58 @@ class KeyBuffer(object): else: self._try_to_finish() + if self.eval_quantifier and self._do_eval_quantifier(key): + return + # evaluate direction keys {j,k,gg,pagedown,...} - if self.level == 2: + if not self.eval_command: try: - self.tree_pointer = self.tree_pointer[key] + self.dir_tree_pointer = self.dir_tree_pointer[key] except KeyError: self.failure = True else: - if not isinstance(self.tree_pointer, dict): - match = self.tree_pointer - self.direction = match.actions['dir'] * self.quant2 - self.done = True + if not isinstance(self.dir_tree_pointer, dict): + match = self.dir_tree_pointer + direction = match.actions['dir'] * self.direction_quant + self.directions.append(direction) + self.direction_quant = None + 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 is_ascii_digit(key) 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 False + return True def _try_to_finish(self): if not isinstance(self.tree_pointer, dict): - match = self.tree_pointer - self.command = match - if not match.has_direction: - if self.quant2 is not None: - self.direction = self.direction * self.quant2 - self.done = True + self.command = self.tree_pointer + self.done = True def clear(self): self.failure = False self.done = False - self.quant1 = None + self.quant = None self.moo = [] self.quant2 = None self.command = None - self.direction = Direction(down=1) + self.direction_quant = None + self.directions = [] self.all_keys = [] self.tree_pointer = self.keymap._tree - self.direction_tree_pointer = self.direction_keys._tree - self.level = 0 - # level 0 = parsing quantifier 1 - # 1 = parsing command or quantifier 2 - # 2 = parsing direction + self.dir_tree_pointer = self.direction_keys._tree + + self.eval_quantifier = True + self.eval_command = True def __str__(self): """returns a concatenation of all characters""" @@ -254,10 +263,10 @@ class binding(object): def n(value): """ return n or value """ - def fnc(n=None): - if n is None: + def fnc(arg=None): + if arg is None or arg.n is None: return value - return n + return arg.n return fnc def nd(arg): @@ -283,6 +292,7 @@ class Test(TestCase): self.assertTrue(keybuffer.done, "parsing keys '"+keys+"' did not complete!") arg = CommandArgs(None, None, keybuffer) + self.assert_(match.function, match.__dict__) return match.function(arg) return press @@ -301,13 +311,10 @@ class Test(TestCase): km = Keymap() directions = Keymap() kb = KeyBuffer(km, directions) - km.add(n(5), 'd') - match = kb.simulate_press('3d') - self.assertEqual(3, match.function(kb.quant1)) - kb.clear() - match = kb.simulate_press('6223d') - self.assertEqual(6223, match.function(kb.quant1)) - kb.clear() + km.add(n(5), 'p') + press = self._mkpress(kb, km) + self.assertEqual(3, press('3p')) + self.assertEqual(6223, press('6223p')) def test_direction(self): km = Keymap() @@ -320,11 +327,14 @@ class Test(TestCase): press = self._mkpress(kb, km) + self.assertEqual( 1, press('dj')) self.assertEqual( 3, press('3ddj')) self.assertEqual( 15, press('3d5j')) self.assertEqual(-15, press('3d5k')) - self.assertEqual( 15, press('3d5d')) + # supporting this kind of key combination would be too confusing: + # self.assertEqual( 15, press('3d5d')) self.assertEqual( 3, press('3dd')) + self.assertEqual( 33, press('33dd')) self.assertEqual( 1, press('dd')) km.add(nd, 'x}') -- cgit 1.4.1-2-gfad0 From 4ba2112b6db4cd556708324473f34f12f16063ec Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 10 Feb 2010 02:30:11 +0100 Subject: keyparser: allow arbitrary number/order of directions --- test/tc_newkeys.py | 100 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 34 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 2db9b160..8f6be3f5 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -71,45 +71,30 @@ class KeyBuffer(object): return # evaluate the command - if self.eval_command: - try: - self.tree_pointer = self.tree_pointer[key] - except TypeError: - print(self.tree_pointer) - self.failure = True - return None - except KeyError: - if DIRKEY in self.tree_pointer: - self.eval_command = False - self.eval_quantifier = True - self.tree_pointer = self.tree_pointer[DIRKEY] - self.dir_tree_pointer = self.direction_keys._tree - elif ANYKEY in self.tree_pointer: - self.moo.append(key) - self.tree_pointer = self.tree_pointer[ANYKEY] - self._try_to_finish() - else: - self.failure = True - return None - else: - self._try_to_finish() + 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: - try: - self.dir_tree_pointer = self.dir_tree_pointer[key] - except KeyError: - self.failure = True - else: - if not isinstance(self.dir_tree_pointer, dict): - match = self.dir_tree_pointer - direction = match.actions['dir'] * self.direction_quant - self.directions.append(direction) - self.direction_quant = None - self._try_to_finish() + self._do_eval_direction(key) + + def _do_eval_direction(self, key): + try: + self.dir_tree_pointer = self.dir_tree_pointer[key] + except KeyError: + self.failure = True + else: + if not isinstance(self.dir_tree_pointer, dict): + match = self.dir_tree_pointer + direction = match.actions['dir'] * 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: @@ -123,9 +108,32 @@ class KeyBuffer(object): setattr(self, attr, getattr(self, attr) * 10 + key - 48) else: self.eval_quantifier = False - return False + return None return True + def _do_eval_command(self, key): + try: + self.tree_pointer = self.tree_pointer[key] + except TypeError: + print(self.tree_pointer) + self.failure = True + return None + except KeyError: + if DIRKEY in self.tree_pointer: + self.eval_command = False + self.eval_quantifier = True + self.tree_pointer = self.tree_pointer[DIRKEY] + self.dir_tree_pointer = self.direction_keys._tree + elif ANYKEY in self.tree_pointer: + self.moo.append(key) + self.tree_pointer = self.tree_pointer[ANYKEY] + self._try_to_finish() + else: + self.failure = True + return None + else: + self._try_to_finish() + def _try_to_finish(self): if not isinstance(self.tree_pointer, dict): self.command = self.tree_pointer @@ -387,4 +395,28 @@ class Test(TestCase): self.assertEqual(Ellipsis, press('ア')) self.assertEqual(Ellipsis, press('9')) + def test_multiple_directions(self): + km = Keymap() + directions = Keymap() + kb = KeyBuffer(km, directions) + directions.add('j', dir=Direction(down=1)) + directions.add('k', dir=Direction(down=-1)) + + def add_dirs(arg): + n = 0 + for dir in arg.directions: + n += dir.down + return n + + km.add(add_dirs, 'x}y}') + km.add(add_dirs, 'four}}}}') + + press = self._mkpress(kb, km) + + self.assertEqual(2, press('xjyj')) + self.assertEqual(0, press('fourjkkj')) + self.assertEqual(2, press('four2j4k2j2j')) + self.assertEqual(10, press('four1j2j3j4j')) + self.assertEqual(10, press('four1j2j3j4jafslkdfjkldj')) + if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0 From ca909b9a3b638d25092bd3990919f61fb677a502 Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 10 Feb 2010 15:33:50 +0100 Subject: keyparser: some additions --- test/tc_newkeys.py | 76 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 16 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 8f6be3f5..1326677f 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -1,17 +1,20 @@ +# coding=utf-8 if __name__ == '__main__': from __init__ import init; init() from unittest import TestCase, main -from pprint import pprint as print +#from pprint import pprint as print from inspect import isfunction, getargspec import inspect -from sys import intern +try: + from sys import intern +except: + pass FUNC = 'func' DIRECTION = 'direction' DIRKEY = 9001 ANYKEY = 9002 QUANTIFIER = 'n' -MATCH = intern('!!') def to_string(i): """convert a ord'd integer to a string""" @@ -40,6 +43,7 @@ class Direction(object): copy.down *= other copy.right *= other return copy + __rmul__ = __mul__ class CommandArgs(object): """The arguments which are passed to a keybinding function""" @@ -84,12 +88,14 @@ class KeyBuffer(object): 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: if not isinstance(self.dir_tree_pointer, dict): match = self.dir_tree_pointer + assert isinstance(match, binding) direction = match.actions['dir'] * self.direction_quant self.directions.append(direction) self.direction_quant = None @@ -113,6 +119,7 @@ class KeyBuffer(object): def _do_eval_command(self, key): try: + assert isinstance(self.tree_pointer, dict) self.tree_pointer = self.tree_pointer[key] except TypeError: print(self.tree_pointer) @@ -123,10 +130,12 @@ class KeyBuffer(object): self.eval_command = False self.eval_quantifier = True self.tree_pointer = self.tree_pointer[DIRKEY] + assert isinstance(self.tree_pointer, (binding, dict)) self.dir_tree_pointer = self.direction_keys._tree elif ANYKEY in self.tree_pointer: self.moo.append(key) self.tree_pointer = self.tree_pointer[ANYKEY] + assert isinstance(self.tree_pointer, (binding, dict)) self._try_to_finish() else: self.failure = True @@ -135,6 +144,7 @@ class KeyBuffer(object): self._try_to_finish() def _try_to_finish(self): + assert isinstance(self.tree_pointer, (binding, dict)) if not isinstance(self.tree_pointer, dict): self.command = self.tree_pointer self.done = True @@ -289,8 +299,8 @@ def nd(arg): dir = arg.direction return n * dir.down -class Test(TestCase): - """The test cases""" +class PressTestCase(TestCase): + """Some useful methods for the actual test""" def _mkpress(self, keybuffer, keymap): def press(keys): keybuffer.clear() @@ -304,7 +314,23 @@ class Test(TestCase): return match.function(arg) return press + def assertPressFails(self, kb, keys): + kb.clear() + kb.simulate_press(keys) + self.assertTrue(kb.failure, "Keypress did not fail as expected") + kb.clear() + + def assertPressIncomplete(self, kb, keys): + kb.clear() + kb.simulate_press(keys) + self.assertFalse(kb.failure, "Keypress failed, expected incomplete") + self.assertFalse(kb.done, "Keypress done which was unexpected") + kb.clear() + +class Test(PressTestCase): + """The test cases""" def test_add(self): + # depends on internals c = Keymap() c.add(lambda *_: 'lolz', 'aa', 'b') self.assert_(c['aa'].actions[FUNC](), 'lolz') @@ -335,6 +361,7 @@ class Test(TestCase): press = self._mkpress(kb, km) + self.assertPressIncomplete(kb, 'd') self.assertEqual( 1, press('dj')) self.assertEqual( 3, press('3ddj')) self.assertEqual( 15, press('3d5j')) @@ -352,17 +379,11 @@ class Test(TestCase): self.assertEqual(1, press('xxxxjsomeinvalitchars')) # these combinations should break: - kb.clear() - self.assertEqual(None, kb.simulate_press('xxxj')) - kb.clear() - self.assertEqual(None, kb.simulate_press('xxj')) - kb.clear() - self.assertEqual(None, kb.simulate_press('xxkldfjalksdjklsfsldkj')) - kb.clear() - self.assertEqual(None, kb.simulate_press('xyj')) - kb.clear() - self.assertEqual(None, kb.simulate_press('x')) #direction missing - kb.clear() + self.assertPressFails(kb, 'xxxj') + self.assertPressFails(kb, 'xxj') + self.assertPressFails(kb, 'xxkldfjalksdjklsfsldkj') + self.assertPressFails(kb, 'xyj') + self.assertPressIncomplete(kb, 'x') # direction missing def test_any_key(self): km = Keymap() @@ -419,4 +440,27 @@ class Test(TestCase): self.assertEqual(10, press('four1j2j3j4j')) self.assertEqual(10, press('four1j2j3j4jafslkdfjkldj')) + def test_corruptions(self): + km = Keymap() + directions = Keymap() + kb = KeyBuffer(km, directions) + press = self._mkpress(kb, km) + directions.add('j', dir=Direction(down=1)) + directions.add('k', dir=Direction(down=-1)) + km.add('xxx', func=lambda _: 1) + + self.assertEqual(1, press('xxx')) + + # corrupt the tree + tup = tuple(km._split('xxx')) + subtree = km.traverse_tree(tup[:-1]) + subtree[tup[-1]] = "Boo" + + self.assertPressFails(kb, 'xxy') + self.assertPressFails(kb, 'xzy') + self.assertPressIncomplete(kb, 'xx') + self.assertPressIncomplete(kb, 'x') + self.assertRaises(AssertionError, kb.simulate_press, 'xxx') + kb.clear() + if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0 From 2feb26dea6e9e55cf6bc0933baefd9fad99f5507 Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 10 Feb 2010 18:27:41 +0100 Subject: keyparser: cleanups --- test/tc_newkeys.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 1326677f..56a3303e 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -55,7 +55,7 @@ class CommandArgs(object): self.direction = keybuffer.directions and keybuffer.directions[0] or None self.directions = keybuffer.directions self.keys = str(keybuffer) - self.moo = keybuffer.moo + self.matches = keybuffer.matches class KeyBuffer(object): """The evaluator and storage for pressed keys""" @@ -133,7 +133,7 @@ class KeyBuffer(object): assert isinstance(self.tree_pointer, (binding, dict)) self.dir_tree_pointer = self.direction_keys._tree elif ANYKEY in self.tree_pointer: - self.moo.append(key) + self.matches.append(key) self.tree_pointer = self.tree_pointer[ANYKEY] assert isinstance(self.tree_pointer, (binding, dict)) self._try_to_finish() @@ -153,8 +153,7 @@ class KeyBuffer(object): self.failure = False self.done = False self.quant = None - self.moo = [] - self.quant2 = None + self.matches = [] self.command = None self.direction_quant = None self.directions = [] @@ -210,8 +209,6 @@ class Keymap(object): yield ord(char) elif isinstance(key, int): yield key - else: - raise TypeError(key) def add_binding(self, *keys, **actions): assert keys @@ -394,7 +391,7 @@ class Test(PressTestCase): def cat(arg): n = arg.n is None and 1 or arg.n - return ''.join(chr(c) for c in arg.moo) * n + return ''.join(chr(c) for c in arg.matches) * n km.add(cat, 'return.') km.add(cat, 'cat4....') -- cgit 1.4.1-2-gfad0 From e5c4477536790b8cd10d5ffd204f226ea4d2f73e Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 11 Feb 2010 00:07:29 +0100 Subject: keyparser: added test for directions as functions, cleanups --- test/tc_newkeys.py | 93 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 27 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 56a3303e..613def5c 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -1,7 +1,6 @@ # coding=utf-8 if __name__ == '__main__': from __init__ import init; init() from unittest import TestCase, main -#from pprint import pprint as print from inspect import isfunction, getargspec import inspect @@ -12,9 +11,9 @@ except: FUNC = 'func' DIRECTION = 'direction' +DIRARG = 'dir' DIRKEY = 9001 ANYKEY = 9002 -QUANTIFIER = 'n' def to_string(i): """convert a ord'd integer to a string""" @@ -56,6 +55,7 @@ class CommandArgs(object): self.directions = keybuffer.directions self.keys = str(keybuffer) self.matches = keybuffer.matches + self.binding = keybuffer.command class KeyBuffer(object): """The evaluator and storage for pressed keys""" @@ -87,6 +87,12 @@ class KeyBuffer(object): self._do_eval_direction(key) def _do_eval_direction(self, key): + # swap quant and direction_quant in bindings like '' + if self.quant is not None and self.command is None \ + and self.direction_quant is None: + self.direction_quant = self.quant + self.quant = None + try: assert isinstance(self.dir_tree_pointer, dict) self.dir_tree_pointer = self.dir_tree_pointer[key] @@ -225,12 +231,13 @@ class Keymap(object): tree = self._tree for char in generator: try: - tree = tree[char] + newtree = tree[char] + if not isinstance(newtree, dict): + raise KeyError() except KeyError: - tree[char] = dict() - tree = tree[char] - except TypeError: - raise TypeError("Attempting to override existing entry") + newtree = dict() + tree[char] = newtree + tree = newtree return tree def __getitem__(self, key): @@ -265,6 +272,10 @@ class binding(object): self.has_direction = actions['with_direction'] except KeyError: self.has_direction = DIRECTION in argnames + try: + self.direction = self.actions[DIRARG] + except KeyError: + self.direction = None def add_keys(self, keys): assert isinstance(keys, set) @@ -276,25 +287,6 @@ class binding(object): def action(self, key): return self.actions[key] -def n(value): - """ return n or value """ - def fnc(arg=None): - if arg is None or arg.n is None: - return value - return arg.n - return fnc - -def nd(arg): - """ n * direction """ - if arg.n is None: - n = 1 - else: - n = arg.n - if arg.direction is None: - dir = Direction(down=1) - else: - dir = arg.direction - return n * dir.down class PressTestCase(TestCase): """Some useful methods for the actual test""" @@ -342,6 +334,13 @@ class Test(PressTestCase): km = Keymap() directions = Keymap() kb = KeyBuffer(km, directions) + def n(value): + """return n or value""" + def fnc(arg=None): + if arg is None or arg.n is None: + return value + return arg.n + return fnc km.add(n(5), 'p') press = self._mkpress(kb, km) self.assertEqual(3, press('3p')) @@ -353,6 +352,12 @@ class Test(PressTestCase): kb = KeyBuffer(km, directions) directions.add('j', dir=Direction(down=1)) directions.add('k', dir=Direction(down=-1)) + def nd(arg): + """ n * direction """ + n = arg.n is None and 1 or arg.n + dir = arg.direction is None and Direction(down=1) \ + or arg.direction + return n * dir.down km.add(nd, 'd}') km.add('dd', func=nd, with_direction=False) @@ -389,12 +394,15 @@ class Test(PressTestCase): directions.add('j', dir=Direction(down=1)) directions.add('k', dir=Direction(down=-1)) + directions.add('g.', dir=Direction(down=-1)) + def cat(arg): n = arg.n is None and 1 or arg.n return ''.join(chr(c) for c in arg.matches) * n km.add(cat, 'return.') km.add(cat, 'cat4....') + km.add(cat, 'foo}.') press = self._mkpress(kb, km) @@ -403,12 +411,15 @@ class Test(PressTestCase): self.assertEqual('abcdabcd', press('2cat4abcd')) self.assertEqual('55555', press('5return5')) + self.assertEqual('x', press('foojx')) + self.assertPressFails(kb, 'fooggx') # ANYKEY forbidden in DIRECTION + km.add(lambda _: Ellipsis, '.') self.assertEqual('x', press('returnx')) self.assertEqual('abcd', press('cat4abcd')) self.assertEqual(Ellipsis, press('2cat4abcd')) self.assertEqual(Ellipsis, press('5return5')) - self.assertEqual(Ellipsis, press('f')) + self.assertEqual(Ellipsis, press('g')) self.assertEqual(Ellipsis, press('ß')) self.assertEqual(Ellipsis, press('ア')) self.assertEqual(Ellipsis, press('9')) @@ -460,4 +471,32 @@ class Test(PressTestCase): self.assertRaises(AssertionError, kb.simulate_press, 'xxx') kb.clear() + def test_directions_as_functions(self): + km = Keymap() + directions = Keymap() + kb = KeyBuffer(km, directions) + press = self._mkpress(kb, km) + + def move(arg): + return arg.direction.down + + directions.add('j', dir=Direction(down=1)) + directions.add('k', dir=Direction(down=-1)) + km.add('}', func=move) + + self.assertEqual(1, press('j')) + self.assertEqual(-1, press('k')) + + km.add('k', func=lambda _: 'love') + + self.assertEqual(1, press('j')) + self.assertEqual('love', press('k')) + + self.assertEqual(40, press('40j')) + + km.add('}}..', func=move) + + self.assertEqual(40, press('40jkhl')) + + if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0 From cfe081ec178da88d6adb7edc651a05dd83810f70 Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 11 Feb 2010 03:04:02 +0100 Subject: keyparser: proper parsing of in keybindings --- test/tc_newkeys.py | 169 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 118 insertions(+), 51 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 613def5c..c8b560ee 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -4,6 +4,8 @@ from unittest import TestCase, main from inspect import isfunction, getargspec import inspect +import sys +from string import ascii_lowercase try: from sys import intern except: @@ -101,7 +103,7 @@ class KeyBuffer(object): else: if not isinstance(self.dir_tree_pointer, dict): match = self.dir_tree_pointer - assert isinstance(match, binding) + assert isinstance(match, Binding) direction = match.actions['dir'] * self.direction_quant self.directions.append(direction) self.direction_quant = None @@ -136,12 +138,12 @@ class KeyBuffer(object): self.eval_command = False self.eval_quantifier = True self.tree_pointer = self.tree_pointer[DIRKEY] - assert isinstance(self.tree_pointer, (binding, dict)) + assert isinstance(self.tree_pointer, (Binding, dict)) self.dir_tree_pointer = self.direction_keys._tree elif ANYKEY in self.tree_pointer: self.matches.append(key) self.tree_pointer = self.tree_pointer[ANYKEY] - assert isinstance(self.tree_pointer, (binding, dict)) + assert isinstance(self.tree_pointer, (Binding, dict)) self._try_to_finish() else: self.failure = True @@ -150,7 +152,7 @@ class KeyBuffer(object): self._try_to_finish() def _try_to_finish(self): - assert isinstance(self.tree_pointer, (binding, dict)) + assert isinstance(self.tree_pointer, (Binding, dict)) if not isinstance(self.tree_pointer, dict): self.command = self.tree_pointer self.done = True @@ -182,7 +184,54 @@ class KeyBuffer(object): if self.failure: break -class Keymap(object): +key_map = { + 'dir': DIRKEY, + 'any': ANYKEY, + 'cr': ord("\n"), + 'enter': ord("\n"), + 'space': ord(" "), + 'space': ord(" "), + 'tab': ord('\t'), +} +for char in ascii_lowercase: + key_map['c-' + char] = ord(char) - 96 + +def translate_keys(obj): + assert isinstance(obj, (tuple, int, str)) + if isinstance(obj, tuple): + for char in obj: + yield char + elif isinstance(obj, int): + yield obj + elif isinstance(obj, str): + in_brackets = False + bracket_content = None + for char in obj: + if in_brackets: + if char == '>': + in_brackets = False + string = ''.join(bracket_content).lower() + try: + yield key_map[string] + except KeyError: + yield ord('<') + for c in bracket_content: + yield ord(c) + yield ord('>') + else: + bracket_content.append(char) + else: + if char == '<': + in_brackets = True + bracket_content = [] + else: + yield ord(char) + if in_brackets: + yield ord('<') + for c in bracket_content: + yield ord(c) + +class KeyMap(object): """Contains a tree with all the keybindings""" def __init__(self): self._tree = dict() @@ -200,29 +249,13 @@ class Keymap(object): return func return decorator_function - def _split(self, key): - assert isinstance(key, (tuple, int, str)) - if isinstance(key, tuple): - for char in key: - yield char - elif isinstance(key, str): - for char in key: - if char == '.': - yield ANYKEY - elif char == '}': - yield DIRKEY - else: - yield ord(char) - elif isinstance(key, int): - yield key - def add_binding(self, *keys, **actions): assert keys - bind = binding(keys, actions) + bind = Binding(keys, actions) for key in keys: assert key - chars = tuple(self._split(key)) + chars = tuple(translate_keys(key)) tree = self.traverse_tree(chars[:-1]) if isinstance(tree, dict): tree[chars[-1]] = bind @@ -242,7 +275,7 @@ class Keymap(object): def __getitem__(self, key): tree = self._tree - for char in self._split(key): + for char in translate_keys(key): try: tree = tree[char] except TypeError: @@ -254,7 +287,7 @@ class Keymap(object): except KeyError: raise KeyError(str(char) + " not in tree " + str(tree)) -class binding(object): +class Binding(object): """The keybinding object""" def __init__(self, keys, actions): assert hasattr(keys, '__iter__') @@ -318,9 +351,41 @@ class PressTestCase(TestCase): class Test(PressTestCase): """The test cases""" + def test_translate_keys(self): + def test(string, *args): + if not args: + args = (string, ) + self.assertEqual(ordtuple(*args), tuple(translate_keys(string))) + + def ordtuple(*args): + lst = [] + for arg in args: + if isinstance(arg, str): + lst.extend(ord(c) for c in arg) + else: + lst.append(arg) + return tuple(lst) + + test('k') + test('kj') + test('k', 'k', DIRKEY) + test('kz', 'k', ANYKEY, 'z', ANYKEY) + test('kz', 'k', ANYKEY, 'z', DIRKEY) + test('', "\n") + test('', "\t\t\n") + test('<') + test('>') + test('', 1) + test('k') + test('k') + test('k') + test('knz>') + test('>nz>') + def test_add(self): # depends on internals - c = Keymap() + c = KeyMap() c.add(lambda *_: 'lolz', 'aa', 'b') self.assert_(c['aa'].actions[FUNC](), 'lolz') @c.add('a', 'c') @@ -331,8 +396,8 @@ class Test(PressTestCase): self.assert_(c['a'].actions[FUNC](), 5) def test_quantifier(self): - km = Keymap() - directions = Keymap() + km = KeyMap() + directions = KeyMap() kb = KeyBuffer(km, directions) def n(value): """return n or value""" @@ -343,12 +408,13 @@ class Test(PressTestCase): return fnc km.add(n(5), 'p') press = self._mkpress(kb, km) + self.assertEqual(5, press('p')) self.assertEqual(3, press('3p')) self.assertEqual(6223, press('6223p')) def test_direction(self): - km = Keymap() - directions = Keymap() + km = KeyMap() + directions = KeyMap() kb = KeyBuffer(km, directions) directions.add('j', dir=Direction(down=1)) directions.add('k', dir=Direction(down=-1)) @@ -358,7 +424,7 @@ class Test(PressTestCase): dir = arg.direction is None and Direction(down=1) \ or arg.direction return n * dir.down - km.add(nd, 'd}') + km.add(nd, 'd') km.add('dd', func=nd, with_direction=False) press = self._mkpress(kb, km) @@ -374,7 +440,7 @@ class Test(PressTestCase): self.assertEqual( 33, press('33dd')) self.assertEqual( 1, press('dd')) - km.add(nd, 'x}') + km.add(nd, 'x') km.add('xxxx', func=nd, with_direction=False) self.assertEqual(1, press('xxxxj')) @@ -388,21 +454,21 @@ class Test(PressTestCase): self.assertPressIncomplete(kb, 'x') # direction missing def test_any_key(self): - km = Keymap() - directions = Keymap() + km = KeyMap() + directions = KeyMap() kb = KeyBuffer(km, directions) directions.add('j', dir=Direction(down=1)) directions.add('k', dir=Direction(down=-1)) - directions.add('g.', dir=Direction(down=-1)) + directions.add('g', dir=Direction(down=-1)) def cat(arg): n = arg.n is None and 1 or arg.n return ''.join(chr(c) for c in arg.matches) * n - km.add(cat, 'return.') - km.add(cat, 'cat4....') - km.add(cat, 'foo}.') + km.add(cat, 'return') + km.add(cat, 'cat4') + km.add(cat, 'foo') press = self._mkpress(kb, km) @@ -414,7 +480,7 @@ class Test(PressTestCase): self.assertEqual('x', press('foojx')) self.assertPressFails(kb, 'fooggx') # ANYKEY forbidden in DIRECTION - km.add(lambda _: Ellipsis, '.') + km.add(lambda _: Ellipsis, '') self.assertEqual('x', press('returnx')) self.assertEqual('abcd', press('cat4abcd')) self.assertEqual(Ellipsis, press('2cat4abcd')) @@ -425,8 +491,8 @@ class Test(PressTestCase): self.assertEqual(Ellipsis, press('9')) def test_multiple_directions(self): - km = Keymap() - directions = Keymap() + km = KeyMap() + directions = KeyMap() kb = KeyBuffer(km, directions) directions.add('j', dir=Direction(down=1)) directions.add('k', dir=Direction(down=-1)) @@ -437,8 +503,8 @@ class Test(PressTestCase): n += dir.down return n - km.add(add_dirs, 'x}y}') - km.add(add_dirs, 'four}}}}') + km.add(add_dirs, 'xy') + km.add(add_dirs, 'four') press = self._mkpress(kb, km) @@ -449,8 +515,8 @@ class Test(PressTestCase): self.assertEqual(10, press('four1j2j3j4jafslkdfjkldj')) def test_corruptions(self): - km = Keymap() - directions = Keymap() + km = KeyMap() + directions = KeyMap() kb = KeyBuffer(km, directions) press = self._mkpress(kb, km) directions.add('j', dir=Direction(down=1)) @@ -460,7 +526,7 @@ class Test(PressTestCase): self.assertEqual(1, press('xxx')) # corrupt the tree - tup = tuple(km._split('xxx')) + tup = tuple(translate_keys('xxx')) subtree = km.traverse_tree(tup[:-1]) subtree[tup[-1]] = "Boo" @@ -468,12 +534,13 @@ class Test(PressTestCase): self.assertPressFails(kb, 'xzy') self.assertPressIncomplete(kb, 'xx') self.assertPressIncomplete(kb, 'x') - self.assertRaises(AssertionError, kb.simulate_press, 'xxx') + if not sys.flags.optimize: + self.assertRaises(AssertionError, kb.simulate_press, 'xxx') kb.clear() def test_directions_as_functions(self): - km = Keymap() - directions = Keymap() + km = KeyMap() + directions = KeyMap() kb = KeyBuffer(km, directions) press = self._mkpress(kb, km) @@ -482,7 +549,7 @@ class Test(PressTestCase): directions.add('j', dir=Direction(down=1)) directions.add('k', dir=Direction(down=-1)) - km.add('}', func=move) + km.add('', func=move) self.assertEqual(1, press('j')) self.assertEqual(-1, press('k')) @@ -494,7 +561,7 @@ class Test(PressTestCase): self.assertEqual(40, press('40j')) - km.add('}}..', func=move) + km.add('', func=move) self.assertEqual(40, press('40jkhl')) -- cgit 1.4.1-2-gfad0 From 0975223399cb7559659f3c04f4f46defe70865cc Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 11 Feb 2010 20:27:49 +0100 Subject: keyparser: added seperate Tree class --- test/tc_newkeys.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index c8b560ee..5c40e260 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -231,6 +231,54 @@ def translate_keys(obj): for c in bracket_content: yield ord(c) +Nothing = type('nothing', (object, ), {}) + +class Tree(object): + def __init__(self): + self._tree = dict() + + def plow(self, iterable, append=Nothing): + """ + Move along a path, creating nonexistant subtrees + The additional argument allows you to define + one element which will be appended at the end + """ + tree = self._tree + last_tree = tree + char = Nothing + 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 append is not Nothing: + if char is not Nothing: + last_tree[char] = append + else: + self._tree = append + return tree + + def traverse(self, iterable): + """Move along a path, raising exceptions when failed""" + tree = self._tree + for char in iterable: + try: + tree = tree[char] + except TypeError: + raise KeyError("trying to enter leaf") + except KeyError: + raise KeyError(str(char) + " not in tree " + str(tree)) + try: + return tree + except KeyError: + raise KeyError(str(char) + " not in tree " + str(tree)) + + class KeyMap(object): """Contains a tree with all the keybindings""" def __init__(self): @@ -383,6 +431,11 @@ class Test(PressTestCase): test('knz>') test('>nz>') + def test_tree(self): + t = Tree() + subtree = t.plow('abcd', "Yes") + self.assertEqual("Yes", t.traverse('abcd')) + def test_add(self): # depends on internals c = KeyMap() -- cgit 1.4.1-2-gfad0 From d6429e27ffb57df0240572588704969afd492b3c Mon Sep 17 00:00:00 2001 From: hut Date: Fri, 12 Feb 2010 00:15:00 +0100 Subject: keyparser: turned KeyMap into a tree --- test/tc_newkeys.py | 135 +++++++++++++++++++++++------------------------------ 1 file changed, 59 insertions(+), 76 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 5c40e260..d5c4e18f 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -231,21 +231,37 @@ def translate_keys(obj): for c in bracket_content: yield ord(c) -Nothing = type('nothing', (object, ), {}) - class Tree(object): - def __init__(self): - self._tree = dict() - - def plow(self, iterable, append=Nothing): - """ - Move along a path, creating nonexistant subtrees - The additional argument allows you to define - one element which will be appended at the end - """ + def __init__(self, dictionary=None, parent=None, key=None): + assert dictionary is None or isinstance(dictionary, dict) + if dictionary is None: + self._tree = dict() + else: + self._tree = dictionary + self.key = key + self.parent = parent + + def set(self, keys, value, force=True): + """Sets the element at the end of the path to .""" + 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 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 = tree - char = Nothing + last_tree = None + char = None for char in iterable: try: newtree = tree[char] @@ -256,34 +272,32 @@ class Tree(object): tree[char] = newtree last_tree = tree tree = newtree - if append is not Nothing: - if char is not Nothing: - last_tree[char] = append - else: - self._tree = append - return tree + if isinstance(tree, dict): + return Tree(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(str(char) + " not in tree " + str(tree)) - try: + if isinstance(tree, dict): + return Tree(tree, parent=last_tree, key=char) + else: return tree - except KeyError: - raise KeyError(str(char) + " not in tree " + str(tree)) -class KeyMap(object): +class KeyMap(Tree): """Contains a tree with all the keybindings""" - def __init__(self): - self._tree = dict() - def add(self, *args, **keywords): if keywords: return self.add_binding(*args, **keywords) @@ -300,47 +314,17 @@ class KeyMap(object): def add_binding(self, *keys, **actions): assert keys bind = Binding(keys, actions) - for key in keys: - assert key - chars = tuple(translate_keys(key)) - tree = self.traverse_tree(chars[:-1]) - if isinstance(tree, dict): - tree[chars[-1]] = bind - - def traverse_tree(self, generator): - tree = self._tree - for char in generator: - try: - newtree = tree[char] - if not isinstance(newtree, dict): - raise KeyError() - except KeyError: - newtree = dict() - tree[char] = newtree - tree = newtree - return tree + self.set(translate_keys(key), bind) def __getitem__(self, key): - tree = self._tree - for char in translate_keys(key): - try: - tree = tree[char] - except TypeError: - raise KeyError("trying to enter leaf") - except KeyError: - raise KeyError(str(char) + " not in tree " + str(tree)) - try: - return tree - except KeyError: - raise KeyError(str(char) + " not in tree " + str(tree)) + return self.traverse(translate_keys(key)) class Binding(object): """The keybinding object""" def __init__(self, keys, actions): assert hasattr(keys, '__iter__') assert isinstance(actions, dict) - self.keys = set(keys) self.actions = actions try: self.function = self.actions[FUNC] @@ -358,16 +342,6 @@ class Binding(object): except KeyError: self.direction = None - def add_keys(self, keys): - assert isinstance(keys, set) - self.keys |= keys - - def has(self, action): - return action in self.actions - - def action(self, key): - return self.actions[key] - class PressTestCase(TestCase): """Some useful methods for the actual test""" @@ -433,20 +407,29 @@ class Test(PressTestCase): def test_tree(self): t = Tree() - subtree = t.plow('abcd', "Yes") + t.set('abcd', "Yes") + self.assertEqual("Yes", t.traverse('abcd')) + self.assertRaises(KeyError, t.traverse, 'abcde') + self.assertRaises(KeyError, t.traverse, 'xyz') + self.assert_(isinstance(t.traverse('abc'), Tree)) + + t2 = Tree() + self.assertRaises(KeyError, t2.set, 'axy', "Lol", force=False) + subtree = t2.set('axy', "Lol") self.assertEqual("Yes", t.traverse('abcd')) + self.assertRaises(KeyError, t2.traverse, 'abcd') + self.assertEqual("Lol", t2.traverse('axy')) def test_add(self): - # depends on internals c = KeyMap() c.add(lambda *_: 'lolz', 'aa', 'b') - self.assert_(c['aa'].actions[FUNC](), 'lolz') + self.assert_(c['aa'].function(), 'lolz') @c.add('a', 'c') def test(): return 5 - self.assert_(c['b'].actions[FUNC](), 'lolz') - self.assert_(c['c'].actions[FUNC](), 5) - self.assert_(c['a'].actions[FUNC](), 5) + self.assert_(c['b'].function(), 'lolz') + self.assert_(c['c'].function(), 5) + self.assert_(c['a'].function(), 5) def test_quantifier(self): km = KeyMap() @@ -580,8 +563,8 @@ class Test(PressTestCase): # corrupt the tree tup = tuple(translate_keys('xxx')) - subtree = km.traverse_tree(tup[:-1]) - subtree[tup[-1]] = "Boo" + x = ord('x') + km._tree[x][x][x] = "Boo" self.assertPressFails(kb, 'xxy') self.assertPressFails(kb, 'xzy') -- cgit 1.4.1-2-gfad0 From 705eabe249d07dd569ea4c5b6af134a718a65db0 Mon Sep 17 00:00:00 2001 From: hut Date: Fri, 12 Feb 2010 20:33:01 +0100 Subject: keyparser: minor changes --- test/tc_newkeys.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index d5c4e18f..d29450d9 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -273,7 +273,7 @@ class Tree(object): last_tree = tree tree = newtree if isinstance(tree, dict): - return Tree(tree, parent=last_tree, key=char) + return type(self)(tree, parent=last_tree, key=char) else: return tree @@ -291,7 +291,7 @@ class Tree(object): except KeyError: raise KeyError(str(char) + " not in tree " + str(tree)) if isinstance(tree, dict): - return Tree(tree, parent=last_tree, key=char) + return type(self)(tree, parent=last_tree, key=char) else: return tree @@ -342,7 +342,6 @@ class Binding(object): except KeyError: self.direction = None - class PressTestCase(TestCase): """Some useful methods for the actual test""" def _mkpress(self, keybuffer, keymap): @@ -398,6 +397,9 @@ class Test(PressTestCase): test('<') test('>') test('', 1) + test('', 2) + for i in range(1, 26): + test('', i) test('k') test('k') -- cgit 1.4.1-2-gfad0 From c70c915b5ec5a03f0f5b8641322d5c60a1235df5 Mon Sep 17 00:00:00 2001 From: hut Date: Sat, 13 Feb 2010 16:52:54 +0100 Subject: keyparser: added tree.copy() for deep copies --- test/tc_newkeys.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index d29450d9..7bdbccad 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -241,6 +241,21 @@ class Tree(object): 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 set(self, keys, value, force=True): """Sets the element at the end of the path to .""" if not isinstance(keys, (list, tuple)): @@ -603,5 +618,15 @@ class Test(PressTestCase): self.assertEqual(40, press('40jkhl')) + def test_tree_deep_copy(self): + t = Tree() + s = t.plow('abc') + s['d'] = "X" + u = t.copy() + self.assertEqual(t._tree, u._tree) + s = t.traverse('ab') + s['c'] = 'Y' + self.assertNotEqual(t._tree, u._tree) + if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0 From 933463fd93425228e2c1e0439fde75b8167cdbc9 Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 17 Feb 2010 22:44:28 +0100 Subject: keyparser: added test case for tree emrge --- test/tc_newkeys.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 7bdbccad..e8a8dbee 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -437,6 +437,42 @@ class Test(PressTestCase): self.assertRaises(KeyError, t2.traverse, 'abcd') self.assertEqual("Lol", t2.traverse('axy')) + def test_merge_trees(self): + t = Tree() + t.set('aaaX', 1) + t.set('aaaY', 2) + t.set('aaaZ', 3) + t.set('bbbA', 11) + t.set('bbbB', 12) + t.set('bbbC', 13) + t.set('bbbD', 14) + t.set('bP', 21) + t.set('bQ', 22) + + u = Tree() + u.set('aaaX', 0) + u.set('bbbC', 'Yes') + u.set('bbbD', 14) + u.set('bbbE', 15) + u.set('bbbF', 16) + u.set('bQ', 22) + u.set('bR', 23) + + v = t.merge(u) + + self.assertEqual(0, t['aaaX']) + self.assertEqual(1, t['aaaY']) + self.assertEqual(2, t['aaaZ']) + self.assertEqual(11, t['bbbA']) + self.assertEqual('Yes', t['bbbC']) + self.assertEqual(14, t['bbbD']) + self.assertEqual(15, t['bbbE']) + self.assertEqual(16, t['bbbF']) + self.assertRaises(KeyError, t.__getitem__, 'bbbG') + self.assertEqual(21, t['bP']) + self.assertEqual(22, t['bQ']) + self.assertEqual(21, t['bR']) + def test_add(self): c = KeyMap() c.add(lambda *_: 'lolz', 'aa', 'b') -- cgit 1.4.1-2-gfad0 From e975b52f9e83bf2ca3fbb6cf066ccd662518a7df Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 17 Feb 2010 22:56:19 +0100 Subject: keyparser: updated test_tree_deep_copy --- test/tc_newkeys.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index e8a8dbee..10519dbf 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -310,7 +310,6 @@ class Tree(object): else: return tree - class KeyMap(Tree): """Contains a tree with all the keybindings""" def add(self, *args, **keywords): @@ -656,12 +655,12 @@ class Test(PressTestCase): def test_tree_deep_copy(self): t = Tree() - s = t.plow('abc') - s['d'] = "X" + s = t.plow('abcd') + s.replace('X') u = t.copy() self.assertEqual(t._tree, u._tree) - s = t.traverse('ab') - s['c'] = 'Y' + s = t.traverse('abc') + s.replace('Y') self.assertNotEqual(t._tree, u._tree) -- cgit 1.4.1-2-gfad0 From 895e04b312544515549fe953e0264d572837ae55 Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 18 Feb 2010 01:05:36 +0100 Subject: keyparser: Remove the restriction that a root can't be a leaf --- test/tc_newkeys.py | 1 - 1 file changed, 1 deletion(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 10519dbf..df32c14a 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -233,7 +233,6 @@ def translate_keys(obj): class Tree(object): def __init__(self, dictionary=None, parent=None, key=None): - assert dictionary is None or isinstance(dictionary, dict) if dictionary is None: self._tree = dict() else: -- cgit 1.4.1-2-gfad0 From 228f3f571f571b93065210ca5db930beb9731b20 Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 18 Feb 2010 01:09:04 +0100 Subject: keyparse: implemented Tree.merge --- test/tc_newkeys.py | 123 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 31 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index df32c14a..cd9430db 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -255,6 +255,31 @@ class Tree(object): newtree._tree = self._tree return newtree + def merge(self, other, copy=True): + """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 .""" if not isinstance(keys, (list, tuple)): @@ -309,6 +334,8 @@ class Tree(object): else: return tree + __getitem__ = traverse + class KeyMap(Tree): """Contains a tree with all the keybindings""" def add(self, *args, **keywords): @@ -436,40 +463,74 @@ class Test(PressTestCase): self.assertEqual("Lol", t2.traverse('axy')) def test_merge_trees(self): - t = Tree() - t.set('aaaX', 1) - t.set('aaaY', 2) - t.set('aaaZ', 3) - t.set('bbbA', 11) - t.set('bbbB', 12) - t.set('bbbC', 13) - t.set('bbbD', 14) - t.set('bP', 21) - t.set('bQ', 22) - - u = Tree() - u.set('aaaX', 0) - u.set('bbbC', 'Yes') - u.set('bbbD', 14) - u.set('bbbE', 15) - u.set('bbbF', 16) - u.set('bQ', 22) - u.set('bR', 23) - + def makeTreeA(): + t = Tree() + t.set('aaaX', 1) + t.set('aaaY', 2) + t.set('aaaZ', 3) + t.set('bbbA', 11) + t.set('bbbB', 12) + t.set('bbbC', 13) + t.set('bbbD', 14) + t.set('bP', 21) + t.set('bQ', 22) + return t + + def makeTreeB(): + u = Tree() + u.set('aaaX', 0) + u.set('bbbC', 'Yes') + u.set('bbbD', 14) + u.set('bbbE', 15) + u.set('bbbF', 16) + u.set('bQ', 22) + u.set('bR', 23) + u.set('ffff', 1337) + return u + + # test 1 + t = Tree('a') + u = Tree('b') + merged = t.merge(u) + self.assertEqual('b', merged._tree) + + # test 2 + t = Tree('a') + u = makeTreeA() + merged = t.merge(u) + self.assertEqual(u._tree, merged._tree) + + # test 3 + t = makeTreeA() + u = makeTreeB() v = t.merge(u) - self.assertEqual(0, t['aaaX']) - self.assertEqual(1, t['aaaY']) - self.assertEqual(2, t['aaaZ']) - self.assertEqual(11, t['bbbA']) - self.assertEqual('Yes', t['bbbC']) - self.assertEqual(14, t['bbbD']) - self.assertEqual(15, t['bbbE']) - self.assertEqual(16, t['bbbF']) + self.assertEqual(0, v['aaaX']) + self.assertEqual(2, v['aaaY']) + self.assertEqual(3, v['aaaZ']) + self.assertEqual(11, v['bbbA']) + self.assertEqual('Yes', v['bbbC']) + self.assertEqual(14, v['bbbD']) + self.assertEqual(15, v['bbbE']) + self.assertEqual(16, v['bbbF']) self.assertRaises(KeyError, t.__getitem__, 'bbbG') - self.assertEqual(21, t['bP']) - self.assertEqual(22, t['bQ']) - self.assertEqual(21, t['bR']) + self.assertEqual(21, v['bP']) + self.assertEqual(22, v['bQ']) + self.assertEqual(23, v['bR']) + self.assertEqual(1337, v['ffff']) + + # merge shouldn't be destructive + self.assertEqual(makeTreeA()._tree, t._tree) + self.assertEqual(makeTreeB()._tree, u._tree) + + v['fff'].replace('Lolz') + self.assertEqual('Lolz', v['fff']) + + v['aaa'].replace('Very bad') + v.plow('qqqqqqq').replace('eww.') + + self.assertEqual(makeTreeA()._tree, t._tree) + self.assertEqual(makeTreeB()._tree, u._tree) def test_add(self): c = KeyMap() -- cgit 1.4.1-2-gfad0 From 3f388ce51c55b242e46824808c858aa28ece5ce2 Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 18 Feb 2010 13:06:02 +0100 Subject: keyparser: minor updates --- test/tc_newkeys.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index cd9430db..aca6ba98 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -197,6 +197,12 @@ for char in ascii_lowercase: key_map['c-' + char] = ord(char) - 96 def translate_keys(obj): + """ + Translate a keybinding to a sequence of integers + + Example: + lol => (108, 111, 108, 10) + """ assert isinstance(obj, (tuple, int, str)) if isinstance(obj, tuple): for char in obj: @@ -480,7 +486,7 @@ class Test(PressTestCase): u = Tree() u.set('aaaX', 0) u.set('bbbC', 'Yes') - u.set('bbbD', 14) + u.set('bbbD', None) u.set('bbbE', 15) u.set('bbbF', 16) u.set('bQ', 22) @@ -510,7 +516,7 @@ class Test(PressTestCase): self.assertEqual(3, v['aaaZ']) self.assertEqual(11, v['bbbA']) self.assertEqual('Yes', v['bbbC']) - self.assertEqual(14, v['bbbD']) + self.assertEqual(None, v['bbbD']) self.assertEqual(15, v['bbbE']) self.assertEqual(16, v['bbbF']) self.assertRaises(KeyError, t.__getitem__, 'bbbG') -- cgit 1.4.1-2-gfad0 From 9588a0fb1fd45951eb8e640ad6c8bccaf7707586 Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 18 Feb 2010 15:20:43 +0100 Subject: keyparser: implemented aliases --- test/tc_newkeys.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 11 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index aca6ba98..2b040954 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -14,8 +14,10 @@ except: FUNC = 'func' DIRECTION = 'direction' DIRARG = 'dir' +ALIASARG = 'alias' DIRKEY = 9001 ANYKEY = 9002 +MAX_ALIAS_RECURSION = 20 def to_string(i): """convert a ord'd integer to a string""" @@ -101,9 +103,20 @@ class KeyBuffer(object): except KeyError: self.failure = True else: - if not isinstance(self.dir_tree_pointer, dict): - match = self.dir_tree_pointer - assert isinstance(match, Binding) + self._direction_try_to_finish() + + def _direction_try_to_finish(self, rec=MAX_ALIAS_RECURSION): + if rec <= 0: + self.failure = True + return None + if not isinstance(self.dir_tree_pointer, dict): + match = self.dir_tree_pointer + assert isinstance(match, Binding) + if 'alias' in match.actions: + self.dir_tree_pointer = self.direction_keys.traverse( + match.alias) + self._direction_try_to_finish(rec - 1) + else: direction = match.actions['dir'] * self.direction_quant self.directions.append(direction) self.direction_quant = None @@ -151,11 +164,21 @@ class KeyBuffer(object): else: self._try_to_finish() - def _try_to_finish(self): - assert isinstance(self.tree_pointer, (Binding, dict)) - if not isinstance(self.tree_pointer, dict): - self.command = self.tree_pointer - self.done = True + def _try_to_finish(self, rec=MAX_ALIAS_RECURSION): + if rec <= 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 'alias' in self.tree_pointer.actions: + self.tree_pointer = self.keymap.traverse( + translate_keys(self.tree_pointer.actions['alias'])) + self._try_to_finish(rec - 1) + else: + self.command = self.tree_pointer + self.done = True def clear(self): self.failure = False @@ -177,8 +200,8 @@ class KeyBuffer(object): return "".join(to_string(c) for c in self.all_keys) def simulate_press(self, string): - for char in string: - self.add(ord(char)) + for char in translate_keys(string): + self.add(char) if self.done: return self.command if self.failure: @@ -387,6 +410,12 @@ class Binding(object): self.direction = self.actions[DIRARG] except KeyError: self.direction = None + try: + alias = self.actions[ALIASARG] + except KeyError: + self.alias = None + else: + self.alias = translate_keys(alias) class PressTestCase(TestCase): """Some useful methods for the actual test""" @@ -399,7 +428,8 @@ class PressTestCase(TestCase): self.assertTrue(keybuffer.done, "parsing keys '"+keys+"' did not complete!") arg = CommandArgs(None, None, keybuffer) - self.assert_(match.function, match.__dict__) + self.assert_(match.function, "No function found! " + \ + str(match.__dict__)) return match.function(arg) return press @@ -453,6 +483,52 @@ class Test(PressTestCase): test('knz>') test('>nz>') + def test_alias(self): + def add_dirs(arg): + n = 0 + for dir in arg.directions: + n += dir.down + return n + def return5(_): + return 5 + + directions = KeyMap() + directions.add('j', dir=Direction(down=1)) + directions.add('k', dir=Direction(down=-1)) + directions.add('', alias='j') + + base = KeyMap() + base.add(add_dirs, 'a') + base.add(add_dirs, 'b') + base.add(add_dirs, 'xx') + base.add(return5, 'f') + base.add('yy', alias='y') + base.add('!', alias='!') + + other = KeyMap() + other.add('bb', alias='xx') + other.add(add_dirs, 'c') + other.add('g', alias='f') + + km = base.merge(other) + kb = KeyBuffer(km, directions) + + press = self._mkpress(kb, km) + + self.assertEqual(1, press('aj')) + self.assertEqual(2, press('bjbj')) + self.assertEqual(1, press('cj')) + self.assertEqual(1, press('c')) + + self.assertEqual(5, press('f')) + self.assertEqual(5, press('g')) + + for n in range(1, 50): + self.assertPressIncomplete(kb, 'y' * n) + + for n in range(1, 5): + self.assertPressFails(kb, '!' * n) + def test_tree(self): t = Tree() t.set('abcd', "Yes") -- cgit 1.4.1-2-gfad0 From dfd2ef35060ab1e4a6a3dab91db25e48696114da Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 18 Feb 2010 16:42:51 +0100 Subject: keyparser: moved classes from test/ to ranger/ --- ranger/container/__init__.py | 2 +- ranger/container/commandlist.py | 208 ------------------- ranger/container/keybuffer.py | 71 ------- ranger/container/keymap.py | 323 ++++++++++++++++++++++++++++++ ranger/ext/tree.py | 134 +++++++++++++ test/tc_newkeys.py | 432 ++-------------------------------------- 6 files changed, 475 insertions(+), 695 deletions(-) delete mode 100644 ranger/container/commandlist.py delete mode 100644 ranger/container/keybuffer.py create mode 100644 ranger/container/keymap.py create mode 100644 ranger/ext/tree.py (limited to 'test') diff --git a/ranger/container/__init__.py b/ranger/container/__init__.py index 51122291..b3fe9aff 100644 --- a/ranger/container/__init__.py +++ b/ranger/container/__init__.py @@ -18,5 +18,5 @@ used to manage stored data """ from ranger.container.history import History from ranger.container.keybuffer import KeyBuffer -from .commandlist import CommandList +from .keymap import KeyMap from .bookmarks import Bookmarks diff --git a/ranger/container/commandlist.py b/ranger/container/commandlist.py deleted file mode 100644 index f50603f2..00000000 --- a/ranger/container/commandlist.py +++ /dev/null @@ -1,208 +0,0 @@ -# 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 . - -from ranger.ext.openstruct import OpenStruct - -class CommandArgument(object): - def __init__(self, fm, displayable, keybuffer): - self.fm = fm - self.wdg = displayable - self.keybuffer = keybuffer - self.n = keybuffer.number - self.keys = str(keybuffer) - -def cmdarg(displayable): - return CommandArgument(displayable.fm, \ - displayable, displayable.env.keybuffer) - -class CommandList(object): - """ - CommandLists are dictionary-like objects which give you a command - for a given key combination. CommandLists must be filled before use. - """ - - dummy_object = None - dummies_in_paths = False - paths = {} - commandlist = [] - - def __init__(self): - self.commandlist = [] - self.paths = {} - - def __getitem__(self, key): - """Returns the command with the given key combination""" - if isinstance(key, str): - key = self._str_to_tuple(key) - return self.paths[key] - - def rebuild_paths(self): - """ - Fill the path dictionary with dummie objects. - We need to know when to clear the keybuffer (when a wrong key is pressed) - and when to wait for the rest of the key combination. For "gg" we - will assign "g" to a dummy which tells the program to do the latter - and wait. - """ - if self.dummies_in_paths: - self.remove_dummies() - - for cmd in self.commandlist: - for key in cmd.keys: - for path in self._keypath(key): - if path not in self.paths: - self.paths[path] = self.dummy_object - - self.dummies_in_paths = True - - def _keypath(self, tup): - """split a tuple like (a,b,c,d) into [(a,), (a,b), (a,b,c)]""" - length = len(tup) - - if length == 0: - return () - if length == 1: - return (tup, ) - - current = [] - all = [] - - for i in range(len(tup) - 1): - current.append(tup[i]) - all.append(tuple(current)) - - return all - - def remove_dummies(self): - """ - Remove dummie objects in case you have to rebuild a path dictionary - which already contains dummie objects. - """ - for k in tuple(self.paths.keys()): - if self.paths[k] == self.dummy_object: del self.paths[k] - self.dummies_in_paths = False - - - def _str_to_tuple(self, obj): - """splits a string into a tuple of integers""" - if isinstance(obj, tuple): - return obj - elif isinstance(obj, str): - return tuple(map(ord, obj)) - elif isinstance(obj, int): - return (obj, ) - else: - raise TypeError('need a str, int or tuple for str_to_tuple') - - def bind(self, fnc, *keys): - """create a Command object and assign it to the given key combinations.""" - if len(keys) == 0: return - - keys = tuple(map(self._str_to_tuple, keys)) - - cmd = Command(fnc, keys) - - self.commandlist.append(cmd) - for key in cmd.keys: - self.paths[key] = cmd - - def show(self, *keys, **keywords): - """create a Show object and assign it to the given key combinations.""" - if len(keys) == 0: return - - keys = tuple(map(self._str_to_tuple, keys)) - - obj = Show(keywords, keys) - - self.commandlist.append(obj) - for key in obj.keys: - self.paths[key] = obj - - def alias(self, existing, *new): - """bind the keys to the command of the key""" - existing = self._str_to_tuple(existing) - new = tuple(map(self._str_to_tuple, new)) - - obj = AliasedCommand(_make_getter(self.paths, existing), new) - - self.commandlist.append(obj) - for key in new: - self.paths[key] = obj - - def unbind(self, *keys): - i = len(self.commandlist) - keys = set(map(self._str_to_tuple, keys)) - - while i > 0: - i -= 1 - cmd = self.commandlist[i] - cmd.keys -= keys - if not cmd.keys: - del self.commandlist[i] - - for k in keys: - del self.paths[k] - - def clear(self): - """remove all bindings""" - self.paths.clear() - del self.commandlist[:] - - -class Command(object): - """Command objects store information about a command""" - - keys = [] - - def __init__(self, fnc, keys): - self.keys = set(keys) - self.execute = fnc - - def execute(self, *args): - """Execute the command""" - - def execute_wrap(self, displayable): - self.execute(cmdarg(displayable)) - - -class AliasedCommand(Command): - def __init__(self, getter, keys): - self.getter = getter - self.keys = set(keys) - - def get_execute(self): - return self.getter() - - execute = property(get_execute) - - -class Show(object): - """Show objects do things without clearing the keybuffer""" - - keys = [] - text = '' - - def __init__(self, dictionary, keys): - self.keys = set(keys) - self.show_obj = OpenStruct(dictionary) - - -def _make_getter(paths, key): - def getter(): - try: - return paths[key].execute - except: - return lambda: None - return getter diff --git a/ranger/container/keybuffer.py b/ranger/container/keybuffer.py deleted file mode 100644 index 2992aea2..00000000 --- a/ranger/container/keybuffer.py +++ /dev/null @@ -1,71 +0,0 @@ -# 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 . - -def to_string(i): - try: - return chr(i) - except ValueError: - return '?' - -from collections import deque -from curses.ascii import ascii - -ZERO = ord('0') -NINE = ord('9') - -class KeyBuffer(object): - def __init__(self): - self.number = None - self.queue = deque() - self.queue_with_numbers = deque() - - def clear(self): - """Clear the keybuffer and restore the initial state""" - self.number = None - self.queue.clear() - self.queue_with_numbers.clear() - - def append(self, key): - """ - Append a key to the keybuffer, or initial numbers to - the number attribute. - """ - self.queue_with_numbers.append(key) - - if not self.queue and key >= ZERO and key <= NINE: - if self.number is None: - self.number = 0 - try: - self.number = self.number * 10 + int(chr(key)) - except ValueError: - return - else: - self.queue.append(key) - - def tuple_with_numbers(self): - """Get a tuple of ascii codes.""" - return tuple(self.queue_with_numbers) - - def tuple_without_numbers(self): - """ - Get a tuple of ascii codes. - If the keybuffer starts with numbers, those will - be left out. To access them, use keybuffer.number - """ - return tuple(self.queue) - - def __str__(self): - """returns a concatenation of all characters""" - return "".join( map( to_string, self.queue_with_numbers ) ) diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py new file mode 100644 index 00000000..c2aa344f --- /dev/null +++ b/ranger/container/keymap.py @@ -0,0 +1,323 @@ +# Copyright (c) 2009, 2010 hut +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR 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, DIRECT, 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. + +from string import ascii_lowercase +from inspect import isfunction, getargspec +from ranger.ext.tree import Tree + +MAX_ALIAS_RECURSION = 20 +DIRKEY = 9001 +ANYKEY = 9002 +FUNC = 'func' +DIRECTION = 'direction' +DIRARG = 'dir' +ALIASARG = 'alias' + +class Direction(object): + """An object with a down and right method""" + def __init__(self, down=0, right=0): + self.down = down + self.right = right + + def copy(self): + new = type(self)() + new.__dict__.update(self.__dict__) + return new + + def __mul__(self, other): + copy = self.copy() + if other is not None: + copy.down *= other + copy.right *= other + return copy + __rmul__ = __mul__ + +def to_string(i): + """convert a ord'd integer to a string""" + try: + return chr(i) + except ValueError: + return '?' + +def is_ascii_digit(n): + return n >= 48 and n <= 57 + +class CommandArgs(object): + """The arguments which are passed to a keybinding function""" + def __init__(self, fm, widget, keybuffer): + self.fm = fm + self.wdg = widget + self.keybuffer = keybuffer + self.n = keybuffer.quant + self.direction = keybuffer.directions and keybuffer.directions[0] or None + self.directions = keybuffer.directions + self.keys = str(keybuffer) + self.matches = keybuffer.matches + self.binding = keybuffer.command + + @staticmethod + def from_widget(self, widget): + return CommandArgs(displayable.fm, \ + displayable, displayable.env.keybuffer) + +class KeyMap(Tree): + """Contains a tree with all the keybindings""" + def add(self, *args, **keywords): + if keywords: + return self.add_binding(*args, **keywords) + firstarg = args[0] + if isfunction(firstarg): + keywords[FUNC] = firstarg + return self.add_binding(*args[1:], **keywords) + def decorator_function(func): + keywords = {FUNC:func} + self.add(*args, **keywords) + return func + return decorator_function + + def add_binding(self, *keys, **actions): + assert keys + bind = Binding(keys, actions) + for key in keys: + self.set(translate_keys(key), bind) + + def __getitem__(self, key): + return self.traverse(translate_keys(key)) + +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 + self.has_direction = False + else: + argnames = getargspec(self.function)[0] + try: + self.has_direction = actions['with_direction'] + except KeyError: + self.has_direction = DIRECTION in argnames + try: + self.direction = self.actions[DIRARG] + except KeyError: + self.direction = None + try: + alias = self.actions[ALIASARG] + except KeyError: + self.alias = None + else: + self.alias = translate_keys(alias) + +class KeyBuffer(object): + """The evaluator and storage for pressed keys""" + def __init__(self, keymap, direction_keys): + self.keymap = keymap + self.direction_keys = direction_keys + self.clear() + + def add(self, key): + if self.failure: + return None + assert isinstance(key, int) + assert key >= 0 + + # 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): + # swap quant and direction_quant in bindings like '' + if self.quant is not None and self.command is None \ + and self.direction_quant is None: + self.direction_quant = self.quant + self.quant = None + + 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, rec=MAX_ALIAS_RECURSION): + if rec <= 0: + self.failure = True + return None + if not isinstance(self.dir_tree_pointer, dict): + match = self.dir_tree_pointer + assert isinstance(match, Binding) + if 'alias' in match.actions: + self.dir_tree_pointer = self.direction_keys.traverse( + match.alias) + self._direction_try_to_finish(rec - 1) + else: + direction = match.actions['dir'] * 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 is_ascii_digit(key) 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): + try: + assert isinstance(self.tree_pointer, dict) + self.tree_pointer = self.tree_pointer[key] + except TypeError: + print(self.tree_pointer) + self.failure = True + return None + except KeyError: + if DIRKEY in self.tree_pointer: + self.eval_command = False + self.eval_quantifier = True + self.tree_pointer = self.tree_pointer[DIRKEY] + assert isinstance(self.tree_pointer, (Binding, dict)) + self.dir_tree_pointer = self.direction_keys._tree + elif ANYKEY in self.tree_pointer: + self.matches.append(key) + self.tree_pointer = self.tree_pointer[ANYKEY] + assert isinstance(self.tree_pointer, (Binding, dict)) + self._try_to_finish() + else: + self.failure = True + return None + else: + self._try_to_finish() + + def _try_to_finish(self, rec=MAX_ALIAS_RECURSION): + if rec <= 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 'alias' in self.tree_pointer.actions: + self.tree_pointer = self.keymap.traverse( + translate_keys(self.tree_pointer.actions['alias'])) + self._try_to_finish(rec - 1) + else: + self.command = self.tree_pointer + self.done = True + + def clear(self): + 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.eval_quantifier = True + self.eval_command = True + + def __str__(self): + """returns a concatenation of all characters""" + return "".join(to_string(c) for c in self.all_keys) + + def simulate_press(self, string): + for char in translate_keys(string): + self.add(char) + if self.done: + return self.command + if self.failure: + break + +key_map = { + 'dir': DIRKEY, + 'any': ANYKEY, + 'cr': ord("\n"), + 'enter': ord("\n"), + 'space': ord(" "), + 'space': ord(" "), + 'tab': ord('\t'), +} +for char in ascii_lowercase: + key_map['c-' + char] = ord(char) - 96 + +def translate_keys(obj): + """ + Translate a keybinding to a sequence of integers + + Example: + lol => (108, 111, 108, 10) + """ + assert isinstance(obj, (tuple, int, str)) + if isinstance(obj, tuple): + for char in obj: + yield char + elif isinstance(obj, int): + yield obj + elif isinstance(obj, str): + in_brackets = False + bracket_content = None + for char in obj: + if in_brackets: + if char == '>': + in_brackets = False + string = ''.join(bracket_content).lower() + try: + yield key_map[string] + except KeyError: + yield ord('<') + for c in bracket_content: + yield ord(c) + yield ord('>') + else: + bracket_content.append(char) + else: + if char == '<': + in_brackets = True + bracket_content = [] + else: + yield ord(char) + if in_brackets: + yield ord('<') + for c in bracket_content: + yield ord(c) diff --git a/ranger/ext/tree.py b/ranger/ext/tree.py new file mode 100644 index 00000000..d7b08cd7 --- /dev/null +++ b/ranger/ext/tree.py @@ -0,0 +1,134 @@ +# Copyright (c) 2009, 2010 hut +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR 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, DIRECT, 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. + +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=True): + """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 .""" + 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) + 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(str(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/test/tc_newkeys.py b/test/tc_newkeys.py index 2b040954..8f5422a0 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -2,420 +2,10 @@ if __name__ == '__main__': from __init__ import init; init() from unittest import TestCase, main -from inspect import isfunction, getargspec -import inspect +from ranger.ext.tree import Tree +from ranger.container.keymap import * + import sys -from string import ascii_lowercase -try: - from sys import intern -except: - pass - -FUNC = 'func' -DIRECTION = 'direction' -DIRARG = 'dir' -ALIASARG = 'alias' -DIRKEY = 9001 -ANYKEY = 9002 -MAX_ALIAS_RECURSION = 20 - -def to_string(i): - """convert a ord'd integer to a string""" - try: - return chr(i) - except ValueError: - return '?' - -def is_ascii_digit(n): - return n >= 48 and n <= 57 - -class Direction(object): - """An object with a down and right method""" - def __init__(self, down=0, right=0): - self.down = down - self.right = right - - def copy(self): - new = type(self)() - new.__dict__.update(self.__dict__) - return new - - def __mul__(self, other): - copy = self.copy() - if other is not None: - copy.down *= other - copy.right *= other - return copy - __rmul__ = __mul__ - -class CommandArgs(object): - """The arguments which are passed to a keybinding function""" - def __init__(self, fm, widget, keybuffer): - self.fm = fm - self.wdg = widget - self.keybuffer = keybuffer - self.n = keybuffer.quant - self.direction = keybuffer.directions and keybuffer.directions[0] or None - self.directions = keybuffer.directions - self.keys = str(keybuffer) - self.matches = keybuffer.matches - self.binding = keybuffer.command - -class KeyBuffer(object): - """The evaluator and storage for pressed keys""" - def __init__(self, keymap, direction_keys): - self.keymap = keymap - self.direction_keys = direction_keys - self.clear() - - def add(self, key): - if self.failure: - return None - assert isinstance(key, int) - assert key >= 0 - - # 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): - # swap quant and direction_quant in bindings like '' - if self.quant is not None and self.command is None \ - and self.direction_quant is None: - self.direction_quant = self.quant - self.quant = None - - 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, rec=MAX_ALIAS_RECURSION): - if rec <= 0: - self.failure = True - return None - if not isinstance(self.dir_tree_pointer, dict): - match = self.dir_tree_pointer - assert isinstance(match, Binding) - if 'alias' in match.actions: - self.dir_tree_pointer = self.direction_keys.traverse( - match.alias) - self._direction_try_to_finish(rec - 1) - else: - direction = match.actions['dir'] * 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 is_ascii_digit(key) 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): - try: - assert isinstance(self.tree_pointer, dict) - self.tree_pointer = self.tree_pointer[key] - except TypeError: - print(self.tree_pointer) - self.failure = True - return None - except KeyError: - if DIRKEY in self.tree_pointer: - self.eval_command = False - self.eval_quantifier = True - self.tree_pointer = self.tree_pointer[DIRKEY] - assert isinstance(self.tree_pointer, (Binding, dict)) - self.dir_tree_pointer = self.direction_keys._tree - elif ANYKEY in self.tree_pointer: - self.matches.append(key) - self.tree_pointer = self.tree_pointer[ANYKEY] - assert isinstance(self.tree_pointer, (Binding, dict)) - self._try_to_finish() - else: - self.failure = True - return None - else: - self._try_to_finish() - - def _try_to_finish(self, rec=MAX_ALIAS_RECURSION): - if rec <= 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 'alias' in self.tree_pointer.actions: - self.tree_pointer = self.keymap.traverse( - translate_keys(self.tree_pointer.actions['alias'])) - self._try_to_finish(rec - 1) - else: - self.command = self.tree_pointer - self.done = True - - def clear(self): - 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.eval_quantifier = True - self.eval_command = True - - def __str__(self): - """returns a concatenation of all characters""" - return "".join(to_string(c) for c in self.all_keys) - - def simulate_press(self, string): - for char in translate_keys(string): - self.add(char) - if self.done: - return self.command - if self.failure: - break - -key_map = { - 'dir': DIRKEY, - 'any': ANYKEY, - 'cr': ord("\n"), - 'enter': ord("\n"), - 'space': ord(" "), - 'space': ord(" "), - 'tab': ord('\t'), -} -for char in ascii_lowercase: - key_map['c-' + char] = ord(char) - 96 - -def translate_keys(obj): - """ - Translate a keybinding to a sequence of integers - - Example: - lol => (108, 111, 108, 10) - """ - assert isinstance(obj, (tuple, int, str)) - if isinstance(obj, tuple): - for char in obj: - yield char - elif isinstance(obj, int): - yield obj - elif isinstance(obj, str): - in_brackets = False - bracket_content = None - for char in obj: - if in_brackets: - if char == '>': - in_brackets = False - string = ''.join(bracket_content).lower() - try: - yield key_map[string] - except KeyError: - yield ord('<') - for c in bracket_content: - yield ord(c) - yield ord('>') - else: - bracket_content.append(char) - else: - if char == '<': - in_brackets = True - bracket_content = [] - else: - yield ord(char) - if in_brackets: - yield ord('<') - for c in bracket_content: - yield ord(c) - -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=True): - """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 .""" - 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 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(str(char) + " not in tree " + str(tree)) - if isinstance(tree, dict): - return type(self)(tree, parent=last_tree, key=char) - else: - return tree - - __getitem__ = traverse - -class KeyMap(Tree): - """Contains a tree with all the keybindings""" - def add(self, *args, **keywords): - if keywords: - return self.add_binding(*args, **keywords) - firstarg = args[0] - if isfunction(firstarg): - keywords[FUNC] = firstarg - return self.add_binding(*args[1:], **keywords) - def decorator_function(func): - keywords = {FUNC:func} - self.add(*args, **keywords) - return func - return decorator_function - - def add_binding(self, *keys, **actions): - assert keys - bind = Binding(keys, actions) - for key in keys: - self.set(translate_keys(key), bind) - - def __getitem__(self, key): - return self.traverse(translate_keys(key)) - -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 - self.has_direction = False - else: - argnames = getargspec(self.function)[0] - try: - self.has_direction = actions['with_direction'] - except KeyError: - self.has_direction = DIRECTION in argnames - try: - self.direction = self.actions[DIRARG] - except KeyError: - self.direction = None - try: - alias = self.actions[ALIASARG] - except KeyError: - self.alias = None - else: - self.alias = translate_keys(alias) class PressTestCase(TestCase): """Some useful methods for the actual test""" @@ -539,10 +129,22 @@ class Test(PressTestCase): t2 = Tree() self.assertRaises(KeyError, t2.set, 'axy', "Lol", force=False) - subtree = t2.set('axy', "Lol") + t2.set('axx', 'ololol') + t2.set('axyy', "Lol") self.assertEqual("Yes", t.traverse('abcd')) self.assertRaises(KeyError, t2.traverse, 'abcd') - self.assertEqual("Lol", t2.traverse('axy')) + self.assertEqual("Lol", t2.traverse('axyy')) + self.assertEqual("ololol", t2.traverse('axx')) + + t2.unset('axyy') + self.assertEqual("ololol", t2.traverse('axx')) + self.assertRaises(KeyError, t2.traverse, 'axyy') + self.assertRaises(KeyError, t2.traverse, 'axy') + + t2.unset('a') + self.assertRaises(KeyError, t2.traverse, 'abcd') + self.assertRaises(KeyError, t2.traverse, 'a') + self.assert_(t2.empty()) def test_merge_trees(self): def makeTreeA(): -- cgit 1.4.1-2-gfad0 From 6710b7e18c7a74477375f4425fa4ca1bd2ab0927 Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 18 Feb 2010 17:39:19 +0100 Subject: deleted old testcase --- test/tc_commandlist.py | 100 ------------------------------------------------- 1 file changed, 100 deletions(-) delete mode 100644 test/tc_commandlist.py (limited to 'test') diff --git a/test/tc_commandlist.py b/test/tc_commandlist.py deleted file mode 100644 index 9af2cf05..00000000 --- a/test/tc_commandlist.py +++ /dev/null @@ -1,100 +0,0 @@ -# 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 . - -if __name__ == '__main__': from __init__ import init; init() - -from unittest import TestCase, main -from ranger.container.commandlist import CommandList as CL - -class Test(TestCase): - def assertKeyError(self, obj, key): - self.assertRaises(KeyError, obj.__getitem__, key) - - def test_commandist(self): - cl = CL() - fnc = lambda arg: 1 - fnc2 = lambda arg: 2 - dmy = cl.dummy_object - - cl.bind(fnc, 'aaaa') - cl.rebuild_paths() - - self.assertEqual(dmy, cl['a']) - self.assertEqual(dmy, cl['aa']) - self.assertEqual(dmy, cl['aaa']) - self.assertEqual(fnc, cl['aaaa'].execute) - self.assertKeyError(cl, 'aabb') - self.assertKeyError(cl, 'aaaaa') - - cl.bind(fnc, 'aabb') - cl.rebuild_paths() - - self.assertEqual(dmy, cl['a']) - self.assertEqual(dmy, cl['aa']) - self.assertEqual(dmy, cl['aab']) - self.assertEqual(fnc, cl['aabb'].execute) - self.assertEqual(dmy, cl['aaa']) - self.assertEqual(fnc, cl['aaaa'].execute) - - cl.unbind('aabb') - cl.rebuild_paths() - - self.assertEqual(dmy, cl['a']) - self.assertEqual(dmy, cl['aa']) - self.assertKeyError(cl, 'aabb') - self.assertKeyError(cl, 'aab') - self.assertEqual(dmy, cl['aaa']) - self.assertEqual(fnc, cl['aaaa'].execute) - - # Hints work different now. Since a rework of this system - # is planned anyway, there is no need to fix the test. - # hint_text = 'some tip blablablba' - # cl.hint(hint_text, 'aa') - # cl.rebuild_paths() - - self.assertEqual(dmy, cl['a']) - # self.assertEqual(hint_text, cl['aa'].text) - self.assertEqual(dmy, cl['aaa']) - self.assertEqual(fnc, cl['aaaa'].execute) - - # ------------------------ test aliases - cl.alias('aaaa', 'cc') - cl.rebuild_paths() - - self.assertEqual(dmy, cl['c']) - self.assertEqual(cl['cc'].execute, cl['aaaa'].execute) - - cl.bind(fnc2, 'aaaa') - cl.rebuild_paths() - - self.assertEqual(cl['cc'].execute, cl['aaaa'].execute) - - cl.unbind('cc') - cl.rebuild_paths() - - self.assertEqual(fnc2, cl['aaaa'].execute) - self.assertKeyError(cl, 'cc') - - # ----------------------- test clearing - cl.clear() - self.assertKeyError(cl, 'a') - self.assertKeyError(cl, 'aa') - self.assertKeyError(cl, 'aaa') - self.assertKeyError(cl, 'aaaa') - self.assertKeyError(cl, 'aab') - self.assertKeyError(cl, 'aabb') - - -if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0 From 13ecffe7ffa5c80cd69d55419f230c97d06ab23e Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 18 Feb 2010 20:47:46 +0100 Subject: integrating keyparser... --- ranger/container/keymap.py | 22 +++++++++++++++---- ranger/defaults/keys.py | 53 +++++++++++++++++++++++++++++++++++++++++----- test/tc_newkeys.py | 12 +++++++++++ 3 files changed, 78 insertions(+), 9 deletions(-) (limited to 'test') diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py index e1ff06da..70faa0d0 100644 --- a/ranger/container/keymap.py +++ b/ranger/container/keymap.py @@ -12,6 +12,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import curses from string import ascii_lowercase from inspect import isfunction, getargspec from ranger.ext.tree import Tree @@ -121,7 +122,7 @@ class Binding(object): except KeyError: self.alias = None else: - self.alias = translate_keys(alias) + self.alias = tuple(translate_keys(alias)) class KeyBuffer(object): """The evaluator and storage for pressed keys""" @@ -173,9 +174,12 @@ class KeyBuffer(object): if rec <= 0: self.failure = True return None - if not isinstance(self.dir_tree_pointer, dict): + 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 - assert isinstance(match, Binding) + if isinstance(self.dir_tree_pointer, Binding): if 'alias' in match.actions: self.dir_tree_pointer = self.direction_keys.traverse( match.alias) @@ -204,7 +208,7 @@ class KeyBuffer(object): def _do_eval_command(self, key): try: - assert isinstance(self.tree_pointer, dict) + assert isinstance(self.tree_pointer, dict), self.tree_pointer self.tree_pointer = self.tree_pointer[key] except TypeError: print(self.tree_pointer) @@ -278,6 +282,16 @@ key_map = { 'enter': ord("\n"), 'space': ord(" "), 'space': ord(" "), + 'down': curses.KEY_DOWN, + 'up': curses.KEY_UP, + 'left': curses.KEY_LEFT, + 'right': curses.KEY_RIGHT, + 'mouse': curses.KEY_MOUSE, + 'resize': curses.KEY_RESIZE, + 'pagedown': curses.KEY_NPAGE, + 'pageup': curses.KEY_PPAGE, + 'home': curses.KEY_HOME, + 'end': curses.KEY_END, 'tab': ord('\t'), } for char in ascii_lowercase: diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index 6c24d6d7..e6d2b0cc 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -338,10 +338,15 @@ def get_directions(): k = KeyMap() map = k.add - map('j', dir=Direction(down=1)) - map('k', dir=Direction(down=-1)) - map('h', dir=Direction(right=-1)) - map('l', dir=Direction(right=1)) + map('', dir=Direction(down=1)) + map('', dir=Direction(down=-1)) + map('', dir=Direction(right=-1)) + map('', dir=Direction(right=1)) + + map('j', alias='') + map('k', alias='') + map('h', alias='') + map('l', alias='') return k def move(arg): @@ -349,12 +354,50 @@ def move(arg): def get_ui_keys(): k = KeyMap() + k.merge(system_keys()) map = k.add map('', func=move) - map('', func=fm.exit()) + map('', 'Q', func=fm.exit()) + + # --------------------------------------------------------- history + map('H', func=fm.history_go(-1)) + map('L', func=fm.history_go(1)) + + # ----------------------------------------------- tagging / marking + map('t', func=fm.tag_toggle()) + map('T', func=fm.tag_remove()) + + map(' ', func=fm.mark(toggle=True)) + map('v', func=fm.mark(all=True, toggle=True)) + map('V', func=fm.mark(all=True, val=False)) + + # ------------------------------------------ file system operations + map('yy', func=fm.copy()) + map('dd', func=fm.cut()) + map('pp', func=fm.paste()) + map('po', func=fm.paste(overwrite=True)) + map('pl', func=fm.paste_symlink()) + map('p', hint='press //p// once again to confirm pasting' \ + ', func=or //l// to create symlinks') + + # ---------------------------------------------------- run programs + map('s', func=fm.execute_command(os.environ['SHELL'])) + map('E', func=fm.edit_file()) + map('term', func=fm.execute_command('x-terminal-emulator', flags='d')) + map('du', func=fm.execute_command('du --max-depth=1 -h | less')) + return k +def system_keys(): + k = KeyMap() + k.map(fm.exit(), 'Q') + k.map(fm.handle_mouse(), '') + k.map(fm.redraw_window(), '') + k.map(fm.resize(), '') + return k + + ui_keys = get_ui_keys() taskview_keys = ui_keys pager_keys = ui_keys diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 8f5422a0..0922029e 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -380,10 +380,22 @@ class Test(PressTestCase): return arg.direction.down directions.add('j', dir=Direction(down=1)) + directions.add('s', alias='j') directions.add('k', dir=Direction(down=-1)) km.add('', func=move) self.assertEqual(1, press('j')) + self.assertEqual(1, press('j')) + self.assertEqual(1, press('j')) + self.assertEqual(1, press('j')) + self.assertEqual(1, press('j')) + self.assertEqual(1, press('s')) + self.assertEqual(1, press('s')) + self.assertEqual(1, press('s')) + self.assertEqual(1, press('s')) + self.assertEqual(1, press('s')) + self.assertEqual(-1, press('k')) + self.assertEqual(-1, press('k')) self.assertEqual(-1, press('k')) km.add('k', func=lambda _: 'love') -- cgit 1.4.1-2-gfad0 From ea3b13663ea3bbf42cd3472750ee7e00e9093ca4 Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 18 Feb 2010 21:58:37 +0100 Subject: keyparser: allow passive actions (for hints, show bookmarks) --- ranger/container/keymap.py | 10 +++++++++- test/tc_newkeys.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py index 70faa0d0..23f26448 100644 --- a/ranger/container/keymap.py +++ b/ranger/container/keymap.py @@ -18,6 +18,7 @@ from inspect import isfunction, getargspec from ranger.ext.tree import Tree MAX_ALIAS_RECURSION = 20 +PASSIVE_ACTION = 9003 DIRKEY = 9001 ANYKEY = 9002 FUNC = 'func' @@ -207,8 +208,8 @@ class KeyBuffer(object): return True def _do_eval_command(self, key): + assert isinstance(self.tree_pointer, dict), self.tree_pointer try: - assert isinstance(self.tree_pointer, dict), self.tree_pointer self.tree_pointer = self.tree_pointer[key] except TypeError: print(self.tree_pointer) @@ -230,6 +231,11 @@ class KeyBuffer(object): self.failure = True return None 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, rec=MAX_ALIAS_RECURSION): @@ -274,10 +280,12 @@ class KeyBuffer(object): return self.command if self.failure: break + return self.command key_map = { 'dir': DIRKEY, 'any': ANYKEY, + 'psv': PASSIVE_ACTION, 'cr': ord("\n"), 'enter': ord("\n"), 'space': ord(" "), diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 0922029e..697bfdcb 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -38,6 +38,36 @@ class PressTestCase(TestCase): class Test(PressTestCase): """The test cases""" + def test_passive_action(self): + km = KeyMap() + directions = KeyMap() + kb = KeyBuffer(km, directions) + def n(value): + """return n or value""" + def fnc(arg=None): + if arg is None or arg.n is None: + return value + return arg.n + return fnc + + km.add(n(5), 'ppp') + km.add(n(8), 'pp') + km.add(n(2), 'pp') + directions.add('j', dir=Direction(down=1)) + + press = self._mkpress(kb, km) + self.assertEqual(5, press('ppp')) + self.assertEqual(3, press('3ppp')) + + self.assertEqual(2, press('ppj')) + + kb.clear() + match = kb.simulate_press('pp') + args = CommandArgs(0, 0, kb) + self.assert_(match) + self.assert_(match.function) + self.assertEqual(8, match.function(args)) + def test_translate_keys(self): def test(string, *args): if not args: -- cgit 1.4.1-2-gfad0 From 6ae8cb053b78effdeda1d9ba5966086e02befc9a Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 18 Feb 2010 22:11:49 +0100 Subject: keyparser: swapped argument order in KeyMap.add() --- ranger/container/keymap.py | 4 ++-- test/tc_newkeys.py | 42 +++++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 23 deletions(-) (limited to 'test') diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py index 23f26448..abc73d4d 100644 --- a/ranger/container/keymap.py +++ b/ranger/container/keymap.py @@ -78,10 +78,10 @@ class KeyMap(Tree): def add(self, *args, **keywords): if keywords: return self.add_binding(*args, **keywords) - firstarg = args[0] + firstarg = args[-1] if isfunction(firstarg): keywords[FUNC] = firstarg - return self.add_binding(*args[1:], **keywords) + return self.add_binding(*args[:-1], **keywords) def decorator_function(func): keywords = {FUNC:func} self.add(*args, **keywords) diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 697bfdcb..0bc99de1 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -50,9 +50,9 @@ class Test(PressTestCase): return arg.n return fnc - km.add(n(5), 'ppp') - km.add(n(8), 'pp') - km.add(n(2), 'pp') + km.add('ppp', n(5)) + km.add('pp', n(8)) + km.add('pp', n(2)) directions.add('j', dir=Direction(down=1)) press = self._mkpress(kb, km) @@ -118,16 +118,16 @@ class Test(PressTestCase): directions.add('', alias='j') base = KeyMap() - base.add(add_dirs, 'a') - base.add(add_dirs, 'b') - base.add(add_dirs, 'xx') - base.add(return5, 'f') + base.add('a', add_dirs) + base.add('b', add_dirs) + base.add('xx', add_dirs) + base.add('f', return5) base.add('yy', alias='y') base.add('!', alias='!') other = KeyMap() other.add('bb', alias='xx') - other.add(add_dirs, 'c') + other.add('c', add_dirs) other.add('g', alias='f') km = base.merge(other) @@ -248,7 +248,7 @@ class Test(PressTestCase): def test_add(self): c = KeyMap() - c.add(lambda *_: 'lolz', 'aa', 'b') + c.add('aa', 'b', lambda *_: 'lolz') self.assert_(c['aa'].function(), 'lolz') @c.add('a', 'c') def test(): @@ -268,7 +268,7 @@ class Test(PressTestCase): return value return arg.n return fnc - km.add(n(5), 'p') + km.add('p', n(5)) press = self._mkpress(kb, km) self.assertEqual(5, press('p')) self.assertEqual(3, press('3p')) @@ -286,8 +286,8 @@ class Test(PressTestCase): dir = arg.direction is None and Direction(down=1) \ or arg.direction return n * dir.down - km.add(nd, 'd') - km.add('dd', func=nd, with_direction=False) + km.add('d', nd) + km.add('dd', func=nd) press = self._mkpress(kb, km) @@ -302,8 +302,8 @@ class Test(PressTestCase): self.assertEqual( 33, press('33dd')) self.assertEqual( 1, press('dd')) - km.add(nd, 'x') - km.add('xxxx', func=nd, with_direction=False) + km.add('x', nd) + km.add('xxxx', func=nd) self.assertEqual(1, press('xxxxj')) self.assertEqual(1, press('xxxxjsomeinvalitchars')) @@ -328,9 +328,9 @@ class Test(PressTestCase): n = arg.n is None and 1 or arg.n return ''.join(chr(c) for c in arg.matches) * n - km.add(cat, 'return') - km.add(cat, 'cat4') - km.add(cat, 'foo') + km.add('return', cat) + km.add('cat4', cat) + km.add('foo', cat) press = self._mkpress(kb, km) @@ -342,7 +342,7 @@ class Test(PressTestCase): self.assertEqual('x', press('foojx')) self.assertPressFails(kb, 'fooggx') # ANYKEY forbidden in DIRECTION - km.add(lambda _: Ellipsis, '') + km.add('', lambda _: Ellipsis) self.assertEqual('x', press('returnx')) self.assertEqual('abcd', press('cat4abcd')) self.assertEqual(Ellipsis, press('2cat4abcd')) @@ -365,8 +365,8 @@ class Test(PressTestCase): n += dir.down return n - km.add(add_dirs, 'xy') - km.add(add_dirs, 'four') + km.add('xy', add_dirs) + km.add('four', add_dirs) press = self._mkpress(kb, km) @@ -383,7 +383,7 @@ class Test(PressTestCase): press = self._mkpress(kb, km) directions.add('j', dir=Direction(down=1)) directions.add('k', dir=Direction(down=-1)) - km.add('xxx', func=lambda _: 1) + km.add('xxx', lambda _: 1) self.assertEqual(1, press('xxx')) -- cgit 1.4.1-2-gfad0 From ee4687c32e105a69d49c023e8f3a695b4b407ce2 Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 18 Feb 2010 22:41:15 +0100 Subject: keyparser: renamed KeyMap.add to KeyMap.map --- ranger/api/keys.py | 28 --------------- ranger/container/keymap.py | 4 +-- ranger/defaults/keys.py | 44 +++++++++++------------ ranger/gui/ui.py | 10 +++--- test/tc_newkeys.py | 90 +++++++++++++++++++++++----------------------- 5 files changed, 74 insertions(+), 102 deletions(-) (limited to 'test') diff --git a/ranger/api/keys.py b/ranger/api/keys.py index a08c57b3..86911569 100644 --- a/ranger/api/keys.py +++ b/ranger/api/keys.py @@ -23,34 +23,6 @@ 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 -def make_abbreviations(command_list): - def bind(*args, **keywords): - if keywords: - command_list.show(*args, **keywords) - else: - lastarg = args[-1] - if hasattr(lastarg, '__call__'): - # do the binding - command_list.bind(lastarg, *args[:-1]) - else: - # act as a decorator. eg: - # @bind('a') - # def do_stuff(arg): - # arg.fm.ui.do_stuff() - # - # is equivalent to: - # bind('a', lambda arg: arg.fm.ui.do_stuff()) - return lambda fnc: command_list.bind(fnc, *args) - - def show(*args, **keywords): - command_list.show(*args, **keywords) - - def alias(*args): - command_list.alias(*args) - - return bind, alias - - class Wrapper(object): def __init__(self, firstattr): self.__firstattr__ = firstattr diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py index abc73d4d..dae8955a 100644 --- a/ranger/container/keymap.py +++ b/ranger/container/keymap.py @@ -75,7 +75,7 @@ class CommandArgs(object): class KeyMap(Tree): """Contains a tree with all the keybindings""" - def add(self, *args, **keywords): + def map(self, *args, **keywords): if keywords: return self.add_binding(*args, **keywords) firstarg = args[-1] @@ -84,7 +84,7 @@ class KeyMap(Tree): return self.add_binding(*args[:-1], **keywords) def decorator_function(func): keywords = {FUNC:func} - self.add(*args, **keywords) + self.map(*args, **keywords) return func return decorator_function diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index e6d2b0cc..aaa332b9 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -336,7 +336,7 @@ def _basic_movement(command_list): def get_directions(): k = KeyMap() - map = k.add + map = k.map map('', dir=Direction(down=1)) map('', dir=Direction(down=-1)) @@ -355,37 +355,37 @@ def move(arg): def get_ui_keys(): k = KeyMap() k.merge(system_keys()) - map = k.add + map = k.map - map('', func=move) - map('', 'Q', func=fm.exit()) + map('', move) + map('', 'Q', fm.exit()) # --------------------------------------------------------- history - map('H', func=fm.history_go(-1)) - map('L', func=fm.history_go(1)) + map('H', fm.history_go(-1)) + map('L', fm.history_go(1)) # ----------------------------------------------- tagging / marking - map('t', func=fm.tag_toggle()) - map('T', func=fm.tag_remove()) + map('t', fm.tag_toggle()) + map('T', fm.tag_remove()) - map(' ', func=fm.mark(toggle=True)) - map('v', func=fm.mark(all=True, toggle=True)) - map('V', func=fm.mark(all=True, val=False)) + map(' ', fm.mark(toggle=True)) + map('v', fm.mark(all=True, toggle=True)) + map('V', fm.mark(all=True, val=False)) # ------------------------------------------ file system operations - map('yy', func=fm.copy()) - map('dd', func=fm.cut()) - map('pp', func=fm.paste()) - map('po', func=fm.paste(overwrite=True)) - map('pl', func=fm.paste_symlink()) - map('p', hint='press //p// once again to confirm pasting' \ - ', func=or //l// to create symlinks') + map('yy', fm.copy()) + map('dd', fm.cut()) + map('pp', fm.paste()) + map('po', fm.paste(overwrite=True)) + map('pl', fm.paste_symlink()) + map('p', fm.notify('press //p// once again to confirm pasting' \ + ', or //l// to create symlinks')) # ---------------------------------------------------- run programs - map('s', func=fm.execute_command(os.environ['SHELL'])) - map('E', func=fm.edit_file()) - map('term', func=fm.execute_command('x-terminal-emulator', flags='d')) - map('du', func=fm.execute_command('du --max-depth=1 -h | less')) + map('s', fm.execute_command(os.environ['SHELL'])) + map('E', fm.edit_file()) + map('term', fm.execute_command('x-terminal-emulator', flags='d')) + map('du', fm.execute_command('du --max-depth=1 -h | less')) return k diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index 79552bf2..eb7c26fa 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -134,13 +134,12 @@ class UI(DisplayableContainer): return kbuf = self.env.keybuffer + cmd = kbuf.command - if kbuf.done: - cmd = kbuf.command - elif kbuf.failure: + if kbuf.failure: kbuf.clear() return - else: + elif not cmd: return self.env.cmd = cmd @@ -153,7 +152,8 @@ class UI(DisplayableContainer): cmd.function(CommandArgs.from_widget(self)) except Exception as error: self.fm.notify(error) - kbuf.clear() + if kbuf.done: + kbuf.clear() def get_next_key(self): """Waits for key input and returns the pressed key""" diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 0bc99de1..45ac4e33 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -50,10 +50,10 @@ class Test(PressTestCase): return arg.n return fnc - km.add('ppp', n(5)) - km.add('pp', n(8)) - km.add('pp', n(2)) - directions.add('j', dir=Direction(down=1)) + km.map('ppp', n(5)) + km.map('pp', n(8)) + km.map('pp', n(2)) + directions.map('j', dir=Direction(down=1)) press = self._mkpress(kb, km) self.assertEqual(5, press('ppp')) @@ -113,22 +113,22 @@ class Test(PressTestCase): return 5 directions = KeyMap() - directions.add('j', dir=Direction(down=1)) - directions.add('k', dir=Direction(down=-1)) - directions.add('', alias='j') + directions.map('j', dir=Direction(down=1)) + directions.map('k', dir=Direction(down=-1)) + directions.map('', alias='j') base = KeyMap() - base.add('a', add_dirs) - base.add('b', add_dirs) - base.add('xx', add_dirs) - base.add('f', return5) - base.add('yy', alias='y') - base.add('!', alias='!') + base.map('a', add_dirs) + base.map('b', add_dirs) + base.map('xx', add_dirs) + base.map('f', return5) + base.map('yy', alias='y') + base.map('!', alias='!') other = KeyMap() - other.add('bb', alias='xx') - other.add('c', add_dirs) - other.add('g', alias='f') + other.map('bb', alias='xx') + other.map('c', add_dirs) + other.map('g', alias='f') km = base.merge(other) kb = KeyBuffer(km, directions) @@ -248,9 +248,9 @@ class Test(PressTestCase): def test_add(self): c = KeyMap() - c.add('aa', 'b', lambda *_: 'lolz') + c.map('aa', 'b', lambda *_: 'lolz') self.assert_(c['aa'].function(), 'lolz') - @c.add('a', 'c') + @c.map('a', 'c') def test(): return 5 self.assert_(c['b'].function(), 'lolz') @@ -268,7 +268,7 @@ class Test(PressTestCase): return value return arg.n return fnc - km.add('p', n(5)) + km.map('p', n(5)) press = self._mkpress(kb, km) self.assertEqual(5, press('p')) self.assertEqual(3, press('3p')) @@ -278,16 +278,16 @@ class Test(PressTestCase): km = KeyMap() directions = KeyMap() kb = KeyBuffer(km, directions) - directions.add('j', dir=Direction(down=1)) - directions.add('k', dir=Direction(down=-1)) + directions.map('j', dir=Direction(down=1)) + directions.map('k', dir=Direction(down=-1)) def nd(arg): """ n * direction """ n = arg.n is None and 1 or arg.n dir = arg.direction is None and Direction(down=1) \ or arg.direction return n * dir.down - km.add('d', nd) - km.add('dd', func=nd) + km.map('d', nd) + km.map('dd', func=nd) press = self._mkpress(kb, km) @@ -302,8 +302,8 @@ class Test(PressTestCase): self.assertEqual( 33, press('33dd')) self.assertEqual( 1, press('dd')) - km.add('x', nd) - km.add('xxxx', func=nd) + km.map('x', nd) + km.map('xxxx', func=nd) self.assertEqual(1, press('xxxxj')) self.assertEqual(1, press('xxxxjsomeinvalitchars')) @@ -319,18 +319,18 @@ class Test(PressTestCase): km = KeyMap() directions = KeyMap() kb = KeyBuffer(km, directions) - directions.add('j', dir=Direction(down=1)) - directions.add('k', dir=Direction(down=-1)) + directions.map('j', dir=Direction(down=1)) + directions.map('k', dir=Direction(down=-1)) - directions.add('g', dir=Direction(down=-1)) + directions.map('g', dir=Direction(down=-1)) def cat(arg): n = arg.n is None and 1 or arg.n return ''.join(chr(c) for c in arg.matches) * n - km.add('return', cat) - km.add('cat4', cat) - km.add('foo', cat) + km.map('return', cat) + km.map('cat4', cat) + km.map('foo', cat) press = self._mkpress(kb, km) @@ -342,7 +342,7 @@ class Test(PressTestCase): self.assertEqual('x', press('foojx')) self.assertPressFails(kb, 'fooggx') # ANYKEY forbidden in DIRECTION - km.add('', lambda _: Ellipsis) + km.map('', lambda _: Ellipsis) self.assertEqual('x', press('returnx')) self.assertEqual('abcd', press('cat4abcd')) self.assertEqual(Ellipsis, press('2cat4abcd')) @@ -356,8 +356,8 @@ class Test(PressTestCase): km = KeyMap() directions = KeyMap() kb = KeyBuffer(km, directions) - directions.add('j', dir=Direction(down=1)) - directions.add('k', dir=Direction(down=-1)) + directions.map('j', dir=Direction(down=1)) + directions.map('k', dir=Direction(down=-1)) def add_dirs(arg): n = 0 @@ -365,8 +365,8 @@ class Test(PressTestCase): n += dir.down return n - km.add('xy', add_dirs) - km.add('four', add_dirs) + km.map('xy', add_dirs) + km.map('four', add_dirs) press = self._mkpress(kb, km) @@ -381,9 +381,9 @@ class Test(PressTestCase): directions = KeyMap() kb = KeyBuffer(km, directions) press = self._mkpress(kb, km) - directions.add('j', dir=Direction(down=1)) - directions.add('k', dir=Direction(down=-1)) - km.add('xxx', lambda _: 1) + directions.map('j', dir=Direction(down=1)) + directions.map('k', dir=Direction(down=-1)) + km.map('xxx', lambda _: 1) self.assertEqual(1, press('xxx')) @@ -409,10 +409,10 @@ class Test(PressTestCase): def move(arg): return arg.direction.down - directions.add('j', dir=Direction(down=1)) - directions.add('s', alias='j') - directions.add('k', dir=Direction(down=-1)) - km.add('', func=move) + directions.map('j', dir=Direction(down=1)) + directions.map('s', alias='j') + directions.map('k', dir=Direction(down=-1)) + km.map('', func=move) self.assertEqual(1, press('j')) self.assertEqual(1, press('j')) @@ -428,14 +428,14 @@ class Test(PressTestCase): self.assertEqual(-1, press('k')) self.assertEqual(-1, press('k')) - km.add('k', func=lambda _: 'love') + km.map('k', func=lambda _: 'love') self.assertEqual(1, press('j')) self.assertEqual('love', press('k')) self.assertEqual(40, press('40j')) - km.add('', func=move) + km.map('', func=move) self.assertEqual(40, press('40jkhl')) -- cgit 1.4.1-2-gfad0 From 06152bdc5e20cbece35dd4709509b0bf024b428d Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 18 Feb 2010 23:19:59 +0100 Subject: keyparser: fixes --- ranger/container/keymap.py | 6 +++--- ranger/gui/ui.py | 7 +++---- test/tc_ui.py | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) (limited to 'test') diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py index dae8955a..e49da6ee 100644 --- a/ranger/container/keymap.py +++ b/ranger/container/keymap.py @@ -282,7 +282,7 @@ class KeyBuffer(object): break return self.command -key_map = { +special_keys = { 'dir': DIRKEY, 'any': ANYKEY, 'psv': PASSIVE_ACTION, @@ -303,7 +303,7 @@ key_map = { 'tab': ord('\t'), } for char in ascii_lowercase: - key_map['c-' + char] = ord(char) - 96 + special_keys['c-' + char] = ord(char) - 96 def translate_keys(obj): """ @@ -327,7 +327,7 @@ def translate_keys(obj): in_brackets = False string = ''.join(bracket_content).lower() try: - yield key_map[string] + yield special_keys[string] except KeyError: yield ord('<') for c in bracket_content: diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index eb7c26fa..a972eca9 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -144,16 +144,15 @@ class UI(DisplayableContainer): self.env.cmd = cmd - if hasattr(cmd, 'show_obj') and hasattr(cmd.show_obj, 'hint'): - if hasattr(self, 'hint'): - self.hint(cmd.show_obj.hint) - elif cmd.function: + 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() def get_next_key(self): """Waits for key input and returns the pressed key""" diff --git a/test/tc_ui.py b/test/tc_ui.py index affec907..98ddff93 100644 --- a/test/tc_ui.py +++ b/test/tc_ui.py @@ -28,7 +28,7 @@ class Test(unittest.TestCase): def setUp(self): self.fm = Fake() - self.ui = ui.UI(env=Fake(), fm=self.fm, commandlist=Fake()) + self.ui = ui.UI(env=Fake(), fm=self.fm, keymap=Fake()) def fakesetup(): self.ui.widget = Fake() -- cgit 1.4.1-2-gfad0 From 09d8404c62454a08dffc99d0a79d46ac51d8aab5 Mon Sep 17 00:00:00 2001 From: hut Date: Fri, 19 Feb 2010 00:39:18 +0100 Subject: keyparser: lots of stuff --- ranger/container/keymap.py | 27 +++++++++++------ ranger/defaults/keys.py | 72 ++++++++++++++++++++++++++++++---------------- ranger/ext/tree.py | 4 +-- ranger/gui/ui.py | 2 +- test/tc_newkeys.py | 14 +++++---- 5 files changed, 77 insertions(+), 42 deletions(-) (limited to 'test') diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py index e49da6ee..f9be5e95 100644 --- a/ranger/container/keymap.py +++ b/ranger/container/keymap.py @@ -66,6 +66,7 @@ class CommandArgs(object): self.directions = keybuffer.directions self.keys = str(keybuffer) self.matches = keybuffer.matches + self.match = keybuffer.matches and keybuffer.matches[0] or None self.binding = keybuffer.command @staticmethod @@ -88,6 +89,8 @@ class KeyMap(Tree): return func return decorator_function + __call__ = map + def add_binding(self, *keys, **actions): assert keys bind = Binding(keys, actions) @@ -181,10 +184,13 @@ class KeyBuffer(object): self.dir_tree_pointer = self.dir_tree_pointer._tree match = self.dir_tree_pointer if isinstance(self.dir_tree_pointer, Binding): - if 'alias' in match.actions: - self.dir_tree_pointer = self.direction_keys.traverse( - match.alias) - self._direction_try_to_finish(rec - 1) + if match.alias: + try: + self.dir_tree_pointer = self.direction_keys[match.alias] + self._direction_try_to_finish(rec - 1) + except KeyError: + self.failure = True + return None else: direction = match.actions['dir'] * self.direction_quant self.directions.append(direction) @@ -246,10 +252,13 @@ class KeyBuffer(object): if isinstance(self.tree_pointer, KeyMap): self.tree_pointer = self.tree_pointer._tree if isinstance(self.tree_pointer, Binding): - if 'alias' in self.tree_pointer.actions: - self.tree_pointer = self.keymap.traverse( - translate_keys(self.tree_pointer.actions['alias'])) - self._try_to_finish(rec - 1) + if self.tree_pointer.alias: + try: + self.tree_pointer = self.keymap[self.tree_pointer.alias] + self._try_to_finish(rec - 1) + except KeyError: + self.failure = True + return None else: self.command = self.tree_pointer self.done = True @@ -285,7 +294,7 @@ class KeyBuffer(object): special_keys = { 'dir': DIRKEY, 'any': ANYKEY, - 'psv': PASSIVE_ACTION, + 'bg': PASSIVE_ACTION, 'cr': ord("\n"), 'enter': ord("\n"), 'space': ord(" "), diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index aaa332b9..f2d93c58 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -36,6 +36,7 @@ Check ranger.keyapi for more information from ranger.api.keys import * + def _vimlike_aliases(command_list): bind, alias = make_abbreviations(command_list) @@ -334,31 +335,43 @@ def _basic_movement(command_list): bind(KEY_HOME, wdg.move(absolute=0)) bind(KEY_END, wdg.move(absolute=-1)) -def get_directions(): - k = KeyMap() - map = k.map +def base_directions(): + map = KeyMap() map('', dir=Direction(down=1)) map('', dir=Direction(down=-1)) map('', dir=Direction(right=-1)) map('', dir=Direction(right=1)) + return map + +def vim(): + map = KeyMap() + map.merge(base_directions()) map('j', alias='') map('k', alias='') map('h', alias='') map('l', alias='') - return k -def move(arg): - arg.fm.move_pointer(relative=arg.direction.down) + return map + +def system_keys(): + map = KeyMap() + map('Q', fm.exit()) + map('', fm.handle_mouse()) + map('', fm.redraw_window()) + map('', fm.resize()) + + return map -def get_ui_keys(): - k = KeyMap() - k.merge(system_keys()) - map = k.map +def browser_keys(): + map = KeyMap() + map.merge(system_keys()) - map('', move) - map('', 'Q', fm.exit()) + @map('') + def move(arg): + arg.fm.move_pointer(relative=arg.direction.down) + map(fm.exit(), 'Q') # --------------------------------------------------------- history map('H', fm.history_go(-1)) @@ -378,29 +391,38 @@ def get_ui_keys(): map('pp', fm.paste()) map('po', fm.paste(overwrite=True)) map('pl', fm.paste_symlink()) - map('p', fm.notify('press //p// once again to confirm pasting' \ + map('p', fm.notify('press //p// once again to confirm pasting' \ ', or //l// to create symlinks')) # ---------------------------------------------------- run programs map('s', fm.execute_command(os.environ['SHELL'])) map('E', fm.edit_file()) - map('term', fm.execute_command('x-terminal-emulator', flags='d')) + map('.term', fm.execute_command('x-terminal-emulator', flags='d')) map('du', fm.execute_command('du --max-depth=1 -h | less')) - return k + map(':', ';', fm.open_console(cmode.COMMAND)) -def system_keys(): - k = KeyMap() - k.map(fm.exit(), 'Q') - k.map(fm.handle_mouse(), '') - k.map(fm.redraw_window(), '') - k.map(fm.resize(), '') - return k + return map + +def console_keys(): + map = KeyMap() + map.merge(system_keys()) + + @map('') + def type_key(arg): + arg.wdg.type_key(arg.match) + + map('', wdg.history_move(-1)) + map('', wdg.history_move(1)) + map('', wdg.tab()) +#from pprint import pprint +#pprint(browser_keys()._tree[106].__dict__) +#raise SystemExit() -ui_keys = get_ui_keys() +ui_keys = browser_keys() taskview_keys = ui_keys pager_keys = ui_keys embedded_pager_keys = ui_keys -console_keys = ui_keys -directions = get_directions() +console_keys = console_keys() +directions = vim() diff --git a/ranger/ext/tree.py b/ranger/ext/tree.py index d7b08cd7..54505f06 100644 --- a/ranger/ext/tree.py +++ b/ranger/ext/tree.py @@ -36,7 +36,7 @@ class Tree(object): newtree._tree = self._tree return newtree - def merge(self, other, copy=True): + def merge(self, other, copy=False): """Merge another Tree into a copy of self""" def deep_merge(branch, otherbranch): assert isinstance(otherbranch, dict) @@ -125,7 +125,7 @@ class Tree(object): except TypeError: raise KeyError("trying to enter leaf") except KeyError: - raise KeyError(str(char) + " not in tree " + str(tree)) + raise KeyError(repr(char) + " not in tree " + str(tree)) if isinstance(tree, dict): return type(self)(tree, parent=last_tree, key=char) else: diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index a972eca9..2b406113 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -36,7 +36,7 @@ class UI(DisplayableContainer): self.fm = fm if keymap is None: - self.keymap = self.settings.keys.ui_keys + self.keymap = self.settings.keys.browser_keys() else: self.keymap = keymap self.win = curses.initscr() diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 45ac4e33..a4d69805 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -51,7 +51,7 @@ class Test(PressTestCase): return fnc km.map('ppp', n(5)) - km.map('pp', n(8)) + km.map('pp', n(8)) km.map('pp', n(2)) directions.map('j', dir=Direction(down=1)) @@ -116,6 +116,7 @@ class Test(PressTestCase): directions.map('j', dir=Direction(down=1)) directions.map('k', dir=Direction(down=-1)) directions.map('', alias='j') + directions.map('@', alias='') base = KeyMap() base.map('a', add_dirs) @@ -130,7 +131,7 @@ class Test(PressTestCase): other.map('c', add_dirs) other.map('g', alias='f') - km = base.merge(other) + km = base.merge(other, copy=True) kb = KeyBuffer(km, directions) press = self._mkpress(kb, km) @@ -142,6 +143,9 @@ class Test(PressTestCase): self.assertEqual(5, press('f')) self.assertEqual(5, press('g')) + self.assertEqual(press('c'), press('c@')) + self.assertEqual(press('c'), press('c@')) + self.assertEqual(press('c'), press('c@')) for n in range(1, 50): self.assertPressIncomplete(kb, 'y' * n) @@ -205,19 +209,19 @@ class Test(PressTestCase): # test 1 t = Tree('a') u = Tree('b') - merged = t.merge(u) + merged = t.merge(u, copy=True) self.assertEqual('b', merged._tree) # test 2 t = Tree('a') u = makeTreeA() - merged = t.merge(u) + merged = t.merge(u, copy=True) self.assertEqual(u._tree, merged._tree) # test 3 t = makeTreeA() u = makeTreeB() - v = t.merge(u) + v = t.merge(u, copy=True) self.assertEqual(0, v['aaaX']) self.assertEqual(2, v['aaaY']) -- cgit 1.4.1-2-gfad0 From 4a435286452a8a0177994a6f27f3806cdb74fa82 Mon Sep 17 00:00:00 2001 From: hut Date: Mon, 8 Mar 2010 21:13:22 +0100 Subject: keyparser: added copying info to testcase --- test/tc_newkeys.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index a4d69805..92bfb513 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -1,4 +1,19 @@ # coding=utf-8 +# 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 . + if __name__ == '__main__': from __init__ import init; init() from unittest import TestCase, main -- cgit 1.4.1-2-gfad0 From efbde17048b14d43895e4cf91e798fb97702b68f Mon Sep 17 00:00:00 2001 From: hut Date: Tue, 9 Mar 2010 12:40:51 +0100 Subject: keyparser: test for collisions with directories of a lenght > 1 --- ranger/container/keymap.py | 28 +++++++++++++++------------- test/tc_newkeys.py | 35 +++++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 21 deletions(-) (limited to 'test') diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py index 7f72a282..e8cf6119 100644 --- a/ranger/container/keymap.py +++ b/ranger/container/keymap.py @@ -115,7 +115,7 @@ 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): self.keymap = keymap self.direction_keys = direction_keys @@ -210,20 +210,23 @@ class KeyBuffer(object): self.failure = True return None except KeyError: - if DIRKEY in self.tree_pointer: - self.eval_command = False - self.eval_quantifier = True + try: 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 - elif ANYKEY in self.tree_pointer: - self.matches.append(key) - self.tree_pointer = self.tree_pointer[ANYKEY] - assert isinstance(self.tree_pointer, (Binding, dict)) - self._try_to_finish() - else: - self.failure = True - return None else: if isinstance(self.tree_pointer, dict): try: @@ -286,7 +289,6 @@ special_keys = { 'cr': ord("\n"), 'enter': ord("\n"), 'space': ord(" "), - 'space': ord(" "), 'down': curses.KEY_DOWN, 'up': curses.KEY_UP, 'left': curses.KEY_LEFT, diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 92bfb513..0c810af5 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -83,6 +83,31 @@ class Test(PressTestCase): self.assert_(match.function) self.assertEqual(8, match.function(args)) + def test_map_collision(self): + def add_dirs(arg): + return sum(dir.down for dir in arg.directions) + def return5(_): + return 5 + + + directions = KeyMap() + directions.map('gg', dir=Direction(down=1)) + + + km = KeyMap() + km.map('gh', return5) + km.map('agh', return5) + km.map('a', add_dirs) + + kb = KeyBuffer(km, directions) + press = self._mkpress(kb, km) + + self.assertEqual(5, press('gh')) + self.assertEqual(5, press('agh')) +# self.assertPressFails(kb, 'agh') + self.assertEqual(1, press('agg')) + + def test_translate_keys(self): def test(string, *args): if not args: @@ -120,10 +145,7 @@ class Test(PressTestCase): def test_alias(self): def add_dirs(arg): - n = 0 - for dir in arg.directions: - n += dir.down - return n + return sum(dir.down for dir in arg.directions) def return5(_): return 5 @@ -379,10 +401,7 @@ class Test(PressTestCase): directions.map('k', dir=Direction(down=-1)) def add_dirs(arg): - n = 0 - for dir in arg.directions: - n += dir.down - return n + return sum(dir.down for dir in arg.directions) km.map('xy', add_dirs) km.map('four', add_dirs) -- cgit 1.4.1-2-gfad0 From 894665269984ebf9f07bd9cae681ba2057715e25 Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 7 Apr 2010 18:18:02 +0200 Subject: updated container.keymap + testcase to work with new ext.direction --- ranger/container/keymap.py | 12 ++---------- test/tc_newkeys.py | 16 +++++++++------- 2 files changed, 11 insertions(+), 17 deletions(-) (limited to 'test') diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py index e8cf6119..41875cb2 100644 --- a/ranger/container/keymap.py +++ b/ranger/container/keymap.py @@ -144,12 +144,6 @@ class KeyBuffer(object): self._do_eval_direction(key) def _do_eval_direction(self, key): - # swap quant and direction_quant in bindings like '' - if self.quant is not None and self.command is None \ - and self.direction_quant is None: - self.direction_quant = self.quant - self.quant = None - try: assert isinstance(self.dir_tree_pointer, dict) self.dir_tree_pointer = self.dir_tree_pointer[key] @@ -176,11 +170,9 @@ class KeyBuffer(object): self.failure = True return None else: + direction = match.actions['dir'].copy() if self.direction_quant is not None: - direction = match.actions['dir'] * self.direction_quant - direction.has_explicit_direction = True - else: - direction = match.actions['dir'].copy() + direction.multiply(self.direction_quant) self.directions.append(direction) self.direction_quant = None self.eval_command = True diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index 0c810af5..bcf08e5f 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -85,7 +85,7 @@ class Test(PressTestCase): def test_map_collision(self): def add_dirs(arg): - return sum(dir.down for dir in arg.directions) + return sum(dir.down() for dir in arg.directions) def return5(_): return 5 @@ -145,7 +145,7 @@ class Test(PressTestCase): def test_alias(self): def add_dirs(arg): - return sum(dir.down for dir in arg.directions) + return sum(dir.down() for dir in arg.directions) def return5(_): return 5 @@ -326,7 +326,7 @@ class Test(PressTestCase): n = arg.n is None and 1 or arg.n dir = arg.direction is None and Direction(down=1) \ or arg.direction - return n * dir.down + return n * dir.down() km.map('d', nd) km.map('dd', func=nd) @@ -401,7 +401,7 @@ class Test(PressTestCase): directions.map('k', dir=Direction(down=-1)) def add_dirs(arg): - return sum(dir.down for dir in arg.directions) + return sum(dir.down() for dir in arg.directions) km.map('xy', add_dirs) km.map('four', add_dirs) @@ -445,7 +445,7 @@ class Test(PressTestCase): press = self._mkpress(kb, km) def move(arg): - return arg.direction.down + return arg.direction.down() directions.map('j', dir=Direction(down=1)) directions.map('s', alias='j') @@ -471,11 +471,13 @@ class Test(PressTestCase): self.assertEqual(1, press('j')) self.assertEqual('love', press('k')) - self.assertEqual(40, press('40j')) + self.assertEqual(1, press('40j')) + self.assertEqual(40, kb.quant) km.map('', func=move) - self.assertEqual(40, press('40jkhl')) + self.assertEqual(1, press('40jkhl')) + self.assertEqual(40, kb.quant) def test_tree_deep_copy(self): t = Tree() -- cgit 1.4.1-2-gfad0 From eacd742bd40013d8868fc75a29a85a14f1ee1e81 Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 7 Apr 2010 18:50:50 +0200 Subject: container.keymap: support for Alt key --- ranger/container/keymap.py | 7 ++++++- test/tc_newkeys.py | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py index 41875cb2..dee54430 100644 --- a/ranger/container/keymap.py +++ b/ranger/container/keymap.py @@ -295,6 +295,7 @@ special_keys = { } for char in ascii_lowercase: special_keys['c-' + char] = ord(char) - 96 + special_keys['a-' + char] = (27, ord(char)) def translate_keys(obj): """ @@ -318,12 +319,16 @@ def translate_keys(obj): in_brackets = False string = ''.join(bracket_content).lower() try: - yield special_keys[string] + keys = special_keys[string] + for key in keys: + yield key except KeyError: yield ord('<') for c in bracket_content: yield ord(c) yield ord('>') + except TypeError: + yield keys # it was no tuple, just an int else: bracket_content.append(char) else: diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index bcf08e5f..bbfc74e9 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -136,6 +136,8 @@ class Test(PressTestCase): test('', 2) for i in range(1, 26): test('', i) + test('', 27, ord('x')) + test('', 27, ord('o')) test('k') test('k') -- cgit 1.4.1-2-gfad0 From bf4a79dba297d9d5b3801c5785fdf0458de7873c Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 7 Apr 2010 18:53:11 +0200 Subject: tc_newkey: added comment --- test/tc_newkeys.py | 1 + 1 file changed, 1 insertion(+) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index bbfc74e9..c953e88b 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -123,6 +123,7 @@ class Test(PressTestCase): lst.append(arg) return tuple(lst) + # 1 argument means: assume nothing is translated. test('k') test('kj') test('k', 'k', DIRKEY) -- cgit 1.4.1-2-gfad0 From 0b306138950baef339164481d93aaabb882027bc Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 7 Apr 2010 21:04:37 +0200 Subject: added KeyManager --- ranger/__main__.py | 1 + ranger/container/__init__.py | 2 +- ranger/container/keymap.py | 33 ++- ranger/core/environment.py | 7 +- ranger/defaults/keys.py | 557 ++++------------------------------------- ranger/defaults/oldkeys.py | 555 ++++++++++++++++++++++++++++++++++++++++ ranger/gui/ui.py | 12 +- ranger/gui/widgets/console.py | 3 - ranger/gui/widgets/pager.py | 5 - ranger/gui/widgets/taskview.py | 1 - ranger/shared/settings.py | 12 +- test/tc_newkeys.py | 39 ++- test/tc_ui.py | 2 +- 13 files changed, 689 insertions(+), 540 deletions(-) create mode 100644 ranger/defaults/oldkeys.py (limited to 'test') diff --git a/ranger/__main__.py b/ranger/__main__.py index 674ad8f6..863eadd5 100644 --- a/ranger/__main__.py +++ b/ranger/__main__.py @@ -112,6 +112,7 @@ def main(): path = '.' Environment(path) + SettingsAware._setup_keys() try: my_ui = UI() diff --git a/ranger/container/__init__.py b/ranger/container/__init__.py index 4c8f08ba..c1bb8194 100644 --- a/ranger/container/__init__.py +++ b/ranger/container/__init__.py @@ -17,5 +17,5 @@ used to manage stored data """ from ranger.container.history import History -from .keymap import KeyMap, KeyBuffer +from .keymap import KeyMap, KeyBuffer, KeyManager from .bookmarks import Bookmarks diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py index 1e634f1d..62cf0e7a 100644 --- a/ranger/container/keymap.py +++ b/ranger/container/keymap.py @@ -61,11 +61,11 @@ class KeyMap(Tree): """Contains a tree with all the keybindings""" def map(self, *args, **keywords): if keywords: - return self.add_binding(*args, **keywords) + return self._add_binding(*args, **keywords) firstarg = args[-1] if isfunction(firstarg): keywords[FUNC] = firstarg - return self.add_binding(*args[:-1], **keywords) + return self._add_binding(*args[:-1], **keywords) def decorator_function(func): keywords = {FUNC:func} self.map(*args, **keywords) @@ -74,7 +74,7 @@ class KeyMap(Tree): __call__ = map - def add_binding(self, *keys, **actions): + def _add_binding(self, *keys, **actions): assert keys bind = Binding(keys, actions) for key in keys: @@ -83,6 +83,33 @@ class KeyMap(Tree): def __getitem__(self, key): return self.traverse(translate_keys(key)) + +class KeyManager(object): + def __init__(self, keybuffer, contexts): + self._keybuffer = keybuffer + self._contexts = { + 'any': KeyMap(), + 'directions': KeyMap(), + } + for context in contexts: + self._contexts[context] = KeyMap() + + def map(self, context, *args, **keywords): + self.get_context(context).map(*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'): + context = self.get_context(context) + if self._keybuffer.keymap is not context: + directions = self.get_context(directions) + self._keybuffer.assign(context, directions) + self._keybuffer.clear() + class Binding(object): """The keybinding object""" def __init__(self, keys, actions): diff --git a/ranger/core/environment.py b/ranger/core/environment.py index 0b38c475..d83003b1 100644 --- a/ranger/core/environment.py +++ b/ranger/core/environment.py @@ -20,10 +20,13 @@ import socket from os.path import abspath, normpath, join, expanduser, isdir from ranger.fsobject.directory import Directory, NoDirectoryGiven -from ranger.container import KeyBuffer, History +from ranger.container import KeyBuffer, KeyManager, History from ranger.ext.signal_dispatcher import SignalDispatcher from ranger.shared import SettingsAware +ALLOWED_CONTEXTS = ('general', 'pager', 'embedded_pager', 'taskview', + 'console') + class Environment(SettingsAware, SignalDispatcher): """A collection of data which is relevant for more than one class. @@ -40,6 +43,7 @@ class Environment(SettingsAware, SignalDispatcher): pathway = None path = None keybuffer = None + keymanager = None def __init__(self, path): SignalDispatcher.__init__(self) @@ -48,6 +52,7 @@ class Environment(SettingsAware, SignalDispatcher): self.pathway = () self.directories = {} self.keybuffer = KeyBuffer(None, None) + self.keymanager = KeyManager(self.keybuffer, ALLOWED_CONTEXTS) self.copy = set() self.history = History(self.settings.max_history_size) diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index 467d26e6..72558a23 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -39,517 +39,48 @@ Check ranger.keyapi for more information from ranger.api.keys import * -def _vimlike_aliases(map): - alias = map.alias - # the key 'k' will always do the same as KEY_UP, etc. - alias(KEY_UP, 'k') - alias(KEY_DOWN, 'j') - alias(KEY_LEFT, 'h') - alias(KEY_RIGHT, 'l') - - alias(KEY_NPAGE, ctrl('f')) - alias(KEY_PPAGE, ctrl('b')) - alias(KEY_HOME, 'gg') - alias(KEY_END, 'G') - - -def _emacs_aliases(map): - alias = map.alias - alias(KEY_LEFT, ctrl('b')) - alias(KEY_RIGHT, ctrl('f')) - alias(KEY_HOME, ctrl('a')) - alias(KEY_END, ctrl('e')) - alias(KEY_DC, ctrl('d')) - alias(DEL, ctrl('h')) - - -def initialize_commands(map): - """Initialize the commands for the main user interface""" - - # -------------------------------------------------------- movement - _vimlike_aliases(map) - _basic_movement(map) - - map.alias(KEY_LEFT, KEY_BACKSPACE, DEL) - map.alias(KEY_RIGHT, KEY_ENTER, ctrl('j')) - - map('%', fm.move(to=50, percentage=True)) - map(KEY_NPAGE, ctrl('f'), fm.move(down=1, pages=True)) - map(KEY_PPAGE, ctrl('b'), fm.move(up=1, pages=True)) - map(ctrl('d'), 'J', fm.move(down=0.5, pages=True)) - map(ctrl('u'), 'K', fm.move(up=0.5, pages=True)) - - map(']', fm.traverse()) - map('[', fm.history_go(-1)) - - # --------------------------------------------------------- history - map('H', fm.history_go(-1)) - map('L', fm.history_go(1)) - - # ----------------------------------------------- tagging / marking - map('t', fm.tag_toggle()) - map('T', fm.tag_remove()) - - map(' ', fm.mark(toggle=True)) - 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('pp', fm.paste()) - map('po', fm.paste(overwrite=True)) - map('pl', fm.paste_symlink()) - map('p', hint='press //p// once again to confirm pasting' \ - ', or //l// to create symlinks') - - # ---------------------------------------------------- run programs - map('s', fm.execute_command(os.environ['SHELL'])) - map('E', fm.edit_file()) - map(',term', fm.execute_command('x-terminal-emulator', flags='d')) - map('du', fm.execute_command('du --max-depth=1 -h | less')) - - # -------------------------------------------------- toggle options - map('b', fm.notify('Warning: settings are now changed with z!', bad=True)) - map('z', hint="show_//h//idden //p//review_files //d//irectories_first " \ - "//c//ollapse_preview flush//i//nput ca//s//e_insensitive") - map('zh', fm.toggle_boolean_option('show_hidden')) - map('zp', fm.toggle_boolean_option('preview_files')) - map('zP', fm.toggle_boolean_option('preview_directories')) - map('zi', fm.toggle_boolean_option('flushinput')) - map('zd', fm.toggle_boolean_option('sort_directories_first')) - map('zc', fm.toggle_boolean_option('collapse_preview')) - map('zs', fm.toggle_boolean_option('sort_case_insensitive')) - - # ------------------------------------------------------------ sort - map('o', 'O', hint="//s//ize //b//ase//n//ame //m//time //t//ype //r//everse") - sort_dict = { - 's': 'size', - 'b': 'basename', - 'n': 'basename', - 'm': 'mtime', - 't': 'type', - } - - for key, val in sort_dict.items(): - for key, is_capital in ((key, False), (key.upper(), True)): - # reverse if any of the two letters is capital - map('o' + key, fm.sort(func=val, reverse=is_capital)) - map('O' + key, fm.sort(func=val, reverse=True)) - - map('or', 'Or', 'oR', 'OR', lambda arg: \ - arg.fm.sort(reverse=not arg.fm.settings.sort_reverse)) - - # ----------------------------------------------- console shortcuts - @map("A") - def append_to_filename(arg): - command = 'rename ' + arg.fm.env.cf.basename - arg.fm.open_console(cmode.COMMAND, command) - - map('cw', fm.open_console(cmode.COMMAND, 'rename ')) - map('cd', fm.open_console(cmode.COMMAND, 'cd ')) - map('f', fm.open_console(cmode.COMMAND_QUICK, 'find ')) - map('tf', fm.open_console(cmode.COMMAND, 'filter ')) - map('d', hint='d//u// (disk usage) d//d// (cut)') - map('@', fm.open_console(cmode.OPEN, '@')) - map('#', fm.open_console(cmode.OPEN, 'p!')) - - # --------------------------------------------- jump to directories - map('gh', fm.cd('~')) - map('ge', fm.cd('/etc')) - map('gu', fm.cd('/usr')) - map('gd', fm.cd('/dev')) - map('gl', fm.cd('/lib')) - map('go', fm.cd('/opt')) - map('gv', fm.cd('/var')) - map('gr', 'g/', fm.cd('/')) - map('gm', fm.cd('/media')) - map('gn', fm.cd('/mnt')) - map('gt', fm.cd('/tmp')) - map('gs', fm.cd('/srv')) - map('gR', fm.cd(RANGERDIR)) - - # ------------------------------------------------------------ tabs - map('gc', ctrl('W'), fm.tab_close()) - map('gt', TAB, fm.tab_move(1)) - map('gT', KEY_BTAB, fm.tab_move(-1)) - map('gn', ctrl('N'), fm.tab_new()) - for n in range(10): - map('g' + str(n), fm.tab_open(n)) - - # ------------------------------------------------------- searching - map('/', fm.open_console(cmode.SEARCH)) - - map('n', fm.search()) - map('N', fm.search(forward=False)) - - map('ct', fm.search(order='tag')) - map('cc', fm.search(order='ctime')) - map('cm', fm.search(order='mimetype')) - map('cs', fm.search(order='size')) - map('c', hint='//c//time //m//imetype //s//ize //t//agged') - - # ------------------------------------------------------- bookmarks - for key in ALLOWED_BOOKMARK_KEYS: - map("`" + key, "'" + key, fm.enter_bookmark(key)) - map("m" + key, fm.set_bookmark(key)) - map("um" + key, fm.unset_bookmark(key)) - map("`", "'", "m", "um", draw_bookmarks=True) - - # ---------------------------------------------------- change views - map('i', fm.display_file()) - map(ctrl('p'), fm.display_log()) - map('?', KEY_F1, fm.display_help()) - map('w', lambda arg: arg.fm.ui.open_taskview()) - - # ---------------------------------------------------------- custom - # This is useful to track watched episode of a series. - @bind(']') - def tag_next_and_run(arg): - fm = arg.fm - fm.tag_remove() - fm.tag_remove(movedown=False) - fm.tag_toggle() - fm.move_pointer(relative=-2) - fm.move_right() - fm.move_pointer(relative=1) - - # "enter" = shortcut for "1l" - bind(KEY_ENTER, ctrl('j'), fm.move_right(mode=1)) - - # ------------------------------------------------ system functions - _system_functions(map) - map('ZZ', 'ZQ', fm.exit()) - map(ctrl('R'), fm.reset()) - map('R', fm.reload_cwd()) - @map(ctrl('C')) - def ctrl_c(arg): - try: - item = arg.fm.loader.queue[0] - except: - arg.fm.notify("Type Q or :quit to exit Ranger") - else: - arg.fm.notify("Aborting: " + item.get_description()) - arg.fm.loader.remove(index=0) - - map(':', ';', fm.open_console(cmode.COMMAND)) - map('>', fm.open_console(cmode.COMMAND_QUICK)) - map('!', fm.open_console(cmode.OPEN)) - map('r', fm.open_console(cmode.OPEN_QUICK)) - - map.rebuild_paths() - - -def initialize_console_commands(map): - """Initialize the commands for the console widget only""" - - _basic_movement(map) - _emacs_aliases(map) - - # -------------------------------------------------------- movement - map(KEY_UP, wdg.history_move(-1)) - map(KEY_DOWN, wdg.history_move(1)) - map(KEY_HOME, wdg.move(right=0, absolute=True)) - map(KEY_END, wdg.move(right=-1, absolute=True)) - - # ----------------------------------------- deleting / pasting text - map(KEY_DC, wdg.delete(0)) - map(KEY_BACKSPACE, DEL, wdg.delete(-1)) - map(ctrl('w'), wdg.delete_word()) - map(ctrl('k'), wdg.delete_rest(1)) - map(ctrl('u'), wdg.delete_rest(-1)) - map(ctrl('y'), wdg.paste()) - - # ------------------------------------------------ system functions - map(KEY_F1, lambda arg: arg.fm.display_command_help(arg.wdg)) - map(ctrl('c'), ESC, wdg.close()) - map(ctrl('j'), KEY_ENTER, wdg.execute()) - map(TAB, wdg.tab()) - map(KEY_BTAB, wdg.tab(-1)) - - map.rebuild_paths() - - -def initialize_taskview_commands(map): - """Initialize the commands for the TaskView widget""" - _basic_movement(map) - _vimlike_aliases(map) - _system_functions(map) - - # -------------------------------------------------- (re)move tasks - map('K', wdg.task_move(0)) - map('J', wdg.task_move(-1)) - map('dd', wdg.task_remove()) - - # ------------------------------------------------ system functions - map('?', fm.display_help()) - map('w', 'q', ESC, ctrl('d'), ctrl('c'), - lambda arg: arg.fm.ui.close_taskview()) - - map.rebuild_paths() - - -def initialize_pager_commands(map): - _base_pager_commands(map) - map('q', 'i', ESC, KEY_F1, lambda arg: arg.fm.ui.close_pager()) - map.rebuild_paths() - - -def initialize_embedded_pager_commands(map): - _base_pager_commands(map) - map('q', 'i', ESC, lambda arg: arg.fm.ui.close_embedded_pager()) - map.rebuild_paths() - - -def _base_pager_commands(map): - _basic_movement(map) - _vimlike_aliases(map) - _system_functions(map) - - # -------------------------------------------------------- movement - map(KEY_LEFT, wdg.move(left=4)) - map(KEY_RIGHT, wdg.move(right=4)) - map(KEY_NPAGE, ctrl('f'), wdg.move(down=1, pages=True)) - map(KEY_PPAGE, ctrl('b'), wdg.move(up=1, pages=True)) - map(ctrl('d'), wdg.move(down=0.5, pages=True)) - map(ctrl('u'), wdg.move(up=0.5, pages=True)) - map(' ', wdg.move(down=0.8, pages=True)) - - # ---------------------------------------------------------- others - map('E', fm.edit_file()) - map('?', fm.display_help()) - - # --------------------------------------------- less-like shortcuts - map.alias(KEY_NPAGE, 'f') - map.alias(KEY_PPAGE, 'b') - map.alias(ctrl('d'), 'd') - map.alias(ctrl('u'), 'u') - - -def _system_functions(map): - map('Q', fm.exit()) - map(ctrl('L'), fm.redraw_window()) - - -def _basic_movement(map): - map(KEY_DOWN, wdg.move(down=1)) - map(KEY_UP, wdg.move(up=1)) - map(KEY_RIGHT, wdg.move(right=1)) - map(KEY_LEFT, wdg.move(left=1)) - map(KEY_HOME, wdg.move(to=0)) - map(KEY_END, wdg.move(to=-1)) - - - -# ------ newkey: - - -def base_directions(): - # Direction Keys - map = KeyMap() - map('', dir=Direction(down=1)) - map('', dir=Direction(down=-1)) - map('', dir=Direction(right=-1)) - map('', dir=Direction(right=1)) - map('', dir=Direction(down=0, absolute=True)) - map('', dir=Direction(down=-1, absolute=True)) - map('', dir=Direction(down=1, pages=True)) - map('', dir=Direction(down=-1, pages=True)) - map('%', dir=Direction(down=1, percentage=True, absolute=True)) - map('', dir=Direction(down=1, pages=True)) - map('', dir=Direction(down=1)) - - return map - -def vim(): - # Direction Keys - map = KeyMap() - map.merge(base_directions()) - map('j', alias='') - map('k', alias='') - map('h', alias='') - map('l', alias='') - map('gg', alias='') - map('G', alias='') - map('J', dir=Direction(down=20)) - map('K', dir=Direction(down=-20)) - - return map - -def system_keys(): - map = KeyMap() - map('Q', fm.exit()) - map('', fm.handle_mouse()) - map('', fm.redraw_window()) - map('', fm.resize()) - - return map - -def browser_keys(): - map = KeyMap() - map.merge(system_keys()) - - @map('') - def move(arg): - arg.fm.move(narg=arg.n, **arg.direction) - map('gg', fm.move(to=0)) - map(fm.exit(), 'Q') - - map('', fm.move(dir=Direction(right=1))) - - # --------------------------------------------------------- history - map('H', fm.history_go(-1)) - map('L', fm.history_go(1)) - - # ----------------------------------------------- tagging / marking - map('t', fm.tag_toggle()) - map('T', fm.tag_remove()) - - map(' ', fm.mark(toggle=True)) - 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('pp', fm.paste()) - map('po', fm.paste(overwrite=True)) - map('pl', fm.paste_symlink()) - map('p', fm.hint('press //p// once again to confirm pasting' \ - ', or //l// to create symlinks')) - - # ---------------------------------------------------- run programs - map('s', fm.execute_command(os.environ['SHELL'])) - map('E', fm.edit_file()) - map('.term', fm.execute_command('x-terminal-emulator', flags='d')) - map('du', fm.execute_command('du --max-depth=1 -h | less')) - - # -------------------------------------------------- toggle options - map('b', fm.hint("bind_//h//idden //p//review_files" \ - "//d//irectories_first //c//ollapse_preview flush//i//nput")) - map('bh', fm.toggle_boolean_option('show_hidden')) - map('bp', fm.toggle_boolean_option('preview_files')) - map('bi', fm.toggle_boolean_option('flushinput')) - map('bd', fm.toggle_boolean_option('directories_first')) - map('bc', fm.toggle_boolean_option('collapse_preview')) - - # ------------------------------------------------------------ sort - map('o', 'O', fm.hint("//s//ize //b//ase//n//ame //m//time" \ - " //t//ype //r//everse")) - sort_dict = { - 's': 'size', - 'b': 'basename', - 'n': 'basename', - 'm': 'mtime', - 't': 'type', - } - - for key, val in sort_dict.items(): - for key, is_capital in ((key, False), (key.upper(), True)): - # reverse if any of the two letters is capital - map('o' + key, fm.sort(func=val, reverse=is_capital)) - map('O' + key, fm.sort(func=val, reverse=True)) - - map('or', 'Or', 'oR', 'OR', lambda arg: \ - arg.fm.sort(reverse=not arg.fm.settings.reverse)) - - # ----------------------------------------------- console shortcuts - @map("A") - def append_to_filename(arg): - command = 'rename ' + arg.fm.env.cf.basename - arg.fm.open_console(cmode.COMMAND, command) - - map('cw', fm.open_console(cmode.COMMAND, 'rename ')) - map('cd', fm.open_console(cmode.COMMAND, 'cd ')) - map('f', fm.open_console(cmode.COMMAND_QUICK, 'find ')) - map('bf', fm.open_console(cmode.COMMAND, 'filter ')) - map('d', fm.hint('d//u// (disk usage) d//d// (cut)')) - - - # --------------------------------------------- jump to directories - map('gh', fm.cd('~')) - map('ge', fm.cd('/etc')) - map('gu', fm.cd('/usr')) - map('gd', fm.cd('/dev')) - map('gl', fm.cd('/lib')) - map('go', fm.cd('/opt')) - map('gv', fm.cd('/var')) - map('gr', 'g/', fm.cd('/')) - map('gm', fm.cd('/media')) - map('gn', fm.cd('/mnt')) - map('gs', fm.cd('/srv')) - map('gR', fm.cd(RANGERDIR)) - - # ------------------------------------------------------------ tabs - map('gc', ctrl('W'), fm.tab_close()) - map('gt', TAB, fm.tab_move(1)) - map('gT', KEY_BTAB, fm.tab_move(-1)) - map('gn', ctrl('N'), fm.tab_new()) - for n in range(10): - map('g' + str(n), fm.tab_open(n)) - map('', fm.tab_open(n)) - - # ------------------------------------------------------- searching - map('/', fm.open_console(cmode.SEARCH)) - - map('n', fm.search()) - map('N', fm.search(forward=False)) - - map('ct', fm.search(order='tag')) - map('cc', fm.search(order='ctime')) - map('cm', fm.search(order='mimetype')) - map('cs', fm.search(order='size')) - map('c', fm.hint('//c//time //m//imetype //s//ize')) - - # ------------------------------------------------------- bookmarks - for key in ALLOWED_BOOKMARK_KEYS: - map("`" + key, "'" + key, fm.enter_bookmark(key)) - map("m" + key, fm.set_bookmark(key)) - map("um" + key, fm.unset_bookmark(key)) - map("`", "'", "m", fm.draw_bookmarks()) - - - map(':', ';', fm.open_console(cmode.COMMAND)) - - # ---------------------------------------------------- change views - map('i', fm.display_file()) - map(ctrl('p'), fm.display_log()) - map('?', KEY_F1, fm.display_help()) - map('w', lambda arg: arg.fm.ui.open_taskview()) - - # ------------------------------------------------ system functions - map('ZZ', fm.exit()) - map(ctrl('R'), fm.reset()) - map('R', fm.reload_cwd()) - map(ctrl('C'), fm.exit()) - - map(':', ';', fm.open_console(cmode.COMMAND)) - map('>', fm.open_console(cmode.COMMAND_QUICK)) - map('!', fm.open_console(cmode.OPEN)) - map('r', fm.open_console(cmode.OPEN_QUICK)) - - return map - -def console_keys(): - map = KeyMap() - map.merge(system_keys()) - - @map('') - def type_key(arg): - arg.wdg.type_key(arg.match) - - map('', wdg.history_move(-1)) - map('', wdg.history_move(1)) - map('', wdg.tab()) - -#from pprint import pprint -#pprint(browser_keys()._tree[106].__dict__) -#raise SystemExit() - -ui_keys = browser_keys() -taskview_keys = ui_keys -pager_keys = ui_keys -embedded_pager_keys = ui_keys -console_keys = console_keys() -directions = vim() +# --------------------------------------------------------- +# Define keys for everywhere: +map = keymanager['general'] +@map('') +def move(arg): + arg.wdg.move(narg=arg.n, **arg.direction) + +map('Q', fm.exit()) +map('', fm.redraw_window()) + +# --------------------------------------------------------- +# Define keys in "general" context: +map = keymanager['general'] + + +map('j', fm.move(down=1)) +map('Q', fm.exit()) + +# --------------------------------------------------------- history +map('H', fm.history_go(-1)) +map('L', fm.history_go(1)) + +# ----------------------------------------------- tagging / marking +map('t', fm.tag_toggle()) +map('T', fm.tag_remove()) + +map(' ', fm.mark(toggle=True)) +map('v', fm.mark(all=True, toggle=True)) +map('V', fm.mark(all=True, val=False)) + +# --------------------------------------------------------- +# Define direction keys +map = keymanager.get_context('directions') +map('', dir=Direction(down=1)) +map('', dir=Direction(down=-1)) +map('', dir=Direction(right=-1)) +map('', dir=Direction(right=1)) +map('', dir=Direction(down=0, absolute=True)) +map('', dir=Direction(down=-1, absolute=True)) +map('', dir=Direction(down=1, pages=True)) +map('', dir=Direction(down=-1, pages=True)) +map('%', dir=Direction(down=1, percentage=True, absolute=True)) +map('', dir=Direction(down=1, pages=True)) +map('', dir=Direction(down=1)) diff --git a/ranger/defaults/oldkeys.py b/ranger/defaults/oldkeys.py new file mode 100644 index 00000000..467d26e6 --- /dev/null +++ b/ranger/defaults/oldkeys.py @@ -0,0 +1,555 @@ +# 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 . + +""" +This is the default key configuration file of ranger. +Syntax for binding keys: map(*keys, fnc) + +keys are one or more key-combinations which are either: +* a string +* an integer which represents an ascii code +* a tuple of integers + +fnc is a function which is called with the CommandArgument object. + +The CommandArgument object has these attributes: +arg.fm: the file manager instance +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 + +Check ranger.keyapi for more information +""" + +# NOTE: The "map" object used below is a callable CommandList +# object and NOT the builtin python map function! + +from ranger.api.keys import * + +def _vimlike_aliases(map): + alias = map.alias + + # the key 'k' will always do the same as KEY_UP, etc. + alias(KEY_UP, 'k') + alias(KEY_DOWN, 'j') + alias(KEY_LEFT, 'h') + alias(KEY_RIGHT, 'l') + + alias(KEY_NPAGE, ctrl('f')) + alias(KEY_PPAGE, ctrl('b')) + alias(KEY_HOME, 'gg') + alias(KEY_END, 'G') + + +def _emacs_aliases(map): + alias = map.alias + alias(KEY_LEFT, ctrl('b')) + alias(KEY_RIGHT, ctrl('f')) + alias(KEY_HOME, ctrl('a')) + alias(KEY_END, ctrl('e')) + alias(KEY_DC, ctrl('d')) + alias(DEL, ctrl('h')) + + +def initialize_commands(map): + """Initialize the commands for the main user interface""" + + # -------------------------------------------------------- movement + _vimlike_aliases(map) + _basic_movement(map) + + map.alias(KEY_LEFT, KEY_BACKSPACE, DEL) + map.alias(KEY_RIGHT, KEY_ENTER, ctrl('j')) + + map('%', fm.move(to=50, percentage=True)) + map(KEY_NPAGE, ctrl('f'), fm.move(down=1, pages=True)) + map(KEY_PPAGE, ctrl('b'), fm.move(up=1, pages=True)) + map(ctrl('d'), 'J', fm.move(down=0.5, pages=True)) + map(ctrl('u'), 'K', fm.move(up=0.5, pages=True)) + + map(']', fm.traverse()) + map('[', fm.history_go(-1)) + + # --------------------------------------------------------- history + map('H', fm.history_go(-1)) + map('L', fm.history_go(1)) + + # ----------------------------------------------- tagging / marking + map('t', fm.tag_toggle()) + map('T', fm.tag_remove()) + + map(' ', fm.mark(toggle=True)) + 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('pp', fm.paste()) + map('po', fm.paste(overwrite=True)) + map('pl', fm.paste_symlink()) + map('p', hint='press //p// once again to confirm pasting' \ + ', or //l// to create symlinks') + + # ---------------------------------------------------- run programs + map('s', fm.execute_command(os.environ['SHELL'])) + map('E', fm.edit_file()) + map(',term', fm.execute_command('x-terminal-emulator', flags='d')) + map('du', fm.execute_command('du --max-depth=1 -h | less')) + + # -------------------------------------------------- toggle options + map('b', fm.notify('Warning: settings are now changed with z!', bad=True)) + map('z', hint="show_//h//idden //p//review_files //d//irectories_first " \ + "//c//ollapse_preview flush//i//nput ca//s//e_insensitive") + map('zh', fm.toggle_boolean_option('show_hidden')) + map('zp', fm.toggle_boolean_option('preview_files')) + map('zP', fm.toggle_boolean_option('preview_directories')) + map('zi', fm.toggle_boolean_option('flushinput')) + map('zd', fm.toggle_boolean_option('sort_directories_first')) + map('zc', fm.toggle_boolean_option('collapse_preview')) + map('zs', fm.toggle_boolean_option('sort_case_insensitive')) + + # ------------------------------------------------------------ sort + map('o', 'O', hint="//s//ize //b//ase//n//ame //m//time //t//ype //r//everse") + sort_dict = { + 's': 'size', + 'b': 'basename', + 'n': 'basename', + 'm': 'mtime', + 't': 'type', + } + + for key, val in sort_dict.items(): + for key, is_capital in ((key, False), (key.upper(), True)): + # reverse if any of the two letters is capital + map('o' + key, fm.sort(func=val, reverse=is_capital)) + map('O' + key, fm.sort(func=val, reverse=True)) + + map('or', 'Or', 'oR', 'OR', lambda arg: \ + arg.fm.sort(reverse=not arg.fm.settings.sort_reverse)) + + # ----------------------------------------------- console shortcuts + @map("A") + def append_to_filename(arg): + command = 'rename ' + arg.fm.env.cf.basename + arg.fm.open_console(cmode.COMMAND, command) + + map('cw', fm.open_console(cmode.COMMAND, 'rename ')) + map('cd', fm.open_console(cmode.COMMAND, 'cd ')) + map('f', fm.open_console(cmode.COMMAND_QUICK, 'find ')) + map('tf', fm.open_console(cmode.COMMAND, 'filter ')) + map('d', hint='d//u// (disk usage) d//d// (cut)') + map('@', fm.open_console(cmode.OPEN, '@')) + map('#', fm.open_console(cmode.OPEN, 'p!')) + + # --------------------------------------------- jump to directories + map('gh', fm.cd('~')) + map('ge', fm.cd('/etc')) + map('gu', fm.cd('/usr')) + map('gd', fm.cd('/dev')) + map('gl', fm.cd('/lib')) + map('go', fm.cd('/opt')) + map('gv', fm.cd('/var')) + map('gr', 'g/', fm.cd('/')) + map('gm', fm.cd('/media')) + map('gn', fm.cd('/mnt')) + map('gt', fm.cd('/tmp')) + map('gs', fm.cd('/srv')) + map('gR', fm.cd(RANGERDIR)) + + # ------------------------------------------------------------ tabs + map('gc', ctrl('W'), fm.tab_close()) + map('gt', TAB, fm.tab_move(1)) + map('gT', KEY_BTAB, fm.tab_move(-1)) + map('gn', ctrl('N'), fm.tab_new()) + for n in range(10): + map('g' + str(n), fm.tab_open(n)) + + # ------------------------------------------------------- searching + map('/', fm.open_console(cmode.SEARCH)) + + map('n', fm.search()) + map('N', fm.search(forward=False)) + + map('ct', fm.search(order='tag')) + map('cc', fm.search(order='ctime')) + map('cm', fm.search(order='mimetype')) + map('cs', fm.search(order='size')) + map('c', hint='//c//time //m//imetype //s//ize //t//agged') + + # ------------------------------------------------------- bookmarks + for key in ALLOWED_BOOKMARK_KEYS: + map("`" + key, "'" + key, fm.enter_bookmark(key)) + map("m" + key, fm.set_bookmark(key)) + map("um" + key, fm.unset_bookmark(key)) + map("`", "'", "m", "um", draw_bookmarks=True) + + # ---------------------------------------------------- change views + map('i', fm.display_file()) + map(ctrl('p'), fm.display_log()) + map('?', KEY_F1, fm.display_help()) + map('w', lambda arg: arg.fm.ui.open_taskview()) + + # ---------------------------------------------------------- custom + # This is useful to track watched episode of a series. + @bind(']') + def tag_next_and_run(arg): + fm = arg.fm + fm.tag_remove() + fm.tag_remove(movedown=False) + fm.tag_toggle() + fm.move_pointer(relative=-2) + fm.move_right() + fm.move_pointer(relative=1) + + # "enter" = shortcut for "1l" + bind(KEY_ENTER, ctrl('j'), fm.move_right(mode=1)) + + # ------------------------------------------------ system functions + _system_functions(map) + map('ZZ', 'ZQ', fm.exit()) + map(ctrl('R'), fm.reset()) + map('R', fm.reload_cwd()) + @map(ctrl('C')) + def ctrl_c(arg): + try: + item = arg.fm.loader.queue[0] + except: + arg.fm.notify("Type Q or :quit to exit Ranger") + else: + arg.fm.notify("Aborting: " + item.get_description()) + arg.fm.loader.remove(index=0) + + map(':', ';', fm.open_console(cmode.COMMAND)) + map('>', fm.open_console(cmode.COMMAND_QUICK)) + map('!', fm.open_console(cmode.OPEN)) + map('r', fm.open_console(cmode.OPEN_QUICK)) + + map.rebuild_paths() + + +def initialize_console_commands(map): + """Initialize the commands for the console widget only""" + + _basic_movement(map) + _emacs_aliases(map) + + # -------------------------------------------------------- movement + map(KEY_UP, wdg.history_move(-1)) + map(KEY_DOWN, wdg.history_move(1)) + map(KEY_HOME, wdg.move(right=0, absolute=True)) + map(KEY_END, wdg.move(right=-1, absolute=True)) + + # ----------------------------------------- deleting / pasting text + map(KEY_DC, wdg.delete(0)) + map(KEY_BACKSPACE, DEL, wdg.delete(-1)) + map(ctrl('w'), wdg.delete_word()) + map(ctrl('k'), wdg.delete_rest(1)) + map(ctrl('u'), wdg.delete_rest(-1)) + map(ctrl('y'), wdg.paste()) + + # ------------------------------------------------ system functions + map(KEY_F1, lambda arg: arg.fm.display_command_help(arg.wdg)) + map(ctrl('c'), ESC, wdg.close()) + map(ctrl('j'), KEY_ENTER, wdg.execute()) + map(TAB, wdg.tab()) + map(KEY_BTAB, wdg.tab(-1)) + + map.rebuild_paths() + + +def initialize_taskview_commands(map): + """Initialize the commands for the TaskView widget""" + _basic_movement(map) + _vimlike_aliases(map) + _system_functions(map) + + # -------------------------------------------------- (re)move tasks + map('K', wdg.task_move(0)) + map('J', wdg.task_move(-1)) + map('dd', wdg.task_remove()) + + # ------------------------------------------------ system functions + map('?', fm.display_help()) + map('w', 'q', ESC, ctrl('d'), ctrl('c'), + lambda arg: arg.fm.ui.close_taskview()) + + map.rebuild_paths() + + +def initialize_pager_commands(map): + _base_pager_commands(map) + map('q', 'i', ESC, KEY_F1, lambda arg: arg.fm.ui.close_pager()) + map.rebuild_paths() + + +def initialize_embedded_pager_commands(map): + _base_pager_commands(map) + map('q', 'i', ESC, lambda arg: arg.fm.ui.close_embedded_pager()) + map.rebuild_paths() + + +def _base_pager_commands(map): + _basic_movement(map) + _vimlike_aliases(map) + _system_functions(map) + + # -------------------------------------------------------- movement + map(KEY_LEFT, wdg.move(left=4)) + map(KEY_RIGHT, wdg.move(right=4)) + map(KEY_NPAGE, ctrl('f'), wdg.move(down=1, pages=True)) + map(KEY_PPAGE, ctrl('b'), wdg.move(up=1, pages=True)) + map(ctrl('d'), wdg.move(down=0.5, pages=True)) + map(ctrl('u'), wdg.move(up=0.5, pages=True)) + map(' ', wdg.move(down=0.8, pages=True)) + + # ---------------------------------------------------------- others + map('E', fm.edit_file()) + map('?', fm.display_help()) + + # --------------------------------------------- less-like shortcuts + map.alias(KEY_NPAGE, 'f') + map.alias(KEY_PPAGE, 'b') + map.alias(ctrl('d'), 'd') + map.alias(ctrl('u'), 'u') + + +def _system_functions(map): + map('Q', fm.exit()) + map(ctrl('L'), fm.redraw_window()) + + +def _basic_movement(map): + map(KEY_DOWN, wdg.move(down=1)) + map(KEY_UP, wdg.move(up=1)) + map(KEY_RIGHT, wdg.move(right=1)) + map(KEY_LEFT, wdg.move(left=1)) + map(KEY_HOME, wdg.move(to=0)) + map(KEY_END, wdg.move(to=-1)) + + + +# ------ newkey: + + +def base_directions(): + # Direction Keys + map = KeyMap() + map('', dir=Direction(down=1)) + map('', dir=Direction(down=-1)) + map('', dir=Direction(right=-1)) + map('', dir=Direction(right=1)) + map('', dir=Direction(down=0, absolute=True)) + map('', dir=Direction(down=-1, absolute=True)) + map('', dir=Direction(down=1, pages=True)) + map('', dir=Direction(down=-1, pages=True)) + map('%', dir=Direction(down=1, percentage=True, absolute=True)) + map('', dir=Direction(down=1, pages=True)) + map('', dir=Direction(down=1)) + + return map + +def vim(): + # Direction Keys + map = KeyMap() + map.merge(base_directions()) + map('j', alias='') + map('k', alias='') + map('h', alias='') + map('l', alias='') + map('gg', alias='') + map('G', alias='') + map('J', dir=Direction(down=20)) + map('K', dir=Direction(down=-20)) + + return map + +def system_keys(): + map = KeyMap() + map('Q', fm.exit()) + map('', fm.handle_mouse()) + map('', fm.redraw_window()) + map('', fm.resize()) + + return map + +def browser_keys(): + map = KeyMap() + map.merge(system_keys()) + + @map('') + def move(arg): + arg.fm.move(narg=arg.n, **arg.direction) + map('gg', fm.move(to=0)) + map(fm.exit(), 'Q') + + map('', fm.move(dir=Direction(right=1))) + + # --------------------------------------------------------- history + map('H', fm.history_go(-1)) + map('L', fm.history_go(1)) + + # ----------------------------------------------- tagging / marking + map('t', fm.tag_toggle()) + map('T', fm.tag_remove()) + + map(' ', fm.mark(toggle=True)) + 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('pp', fm.paste()) + map('po', fm.paste(overwrite=True)) + map('pl', fm.paste_symlink()) + map('p', fm.hint('press //p// once again to confirm pasting' \ + ', or //l// to create symlinks')) + + # ---------------------------------------------------- run programs + map('s', fm.execute_command(os.environ['SHELL'])) + map('E', fm.edit_file()) + map('.term', fm.execute_command('x-terminal-emulator', flags='d')) + map('du', fm.execute_command('du --max-depth=1 -h | less')) + + # -------------------------------------------------- toggle options + map('b', fm.hint("bind_//h//idden //p//review_files" \ + "//d//irectories_first //c//ollapse_preview flush//i//nput")) + map('bh', fm.toggle_boolean_option('show_hidden')) + map('bp', fm.toggle_boolean_option('preview_files')) + map('bi', fm.toggle_boolean_option('flushinput')) + map('bd', fm.toggle_boolean_option('directories_first')) + map('bc', fm.toggle_boolean_option('collapse_preview')) + + # ------------------------------------------------------------ sort + map('o', 'O', fm.hint("//s//ize //b//ase//n//ame //m//time" \ + " //t//ype //r//everse")) + sort_dict = { + 's': 'size', + 'b': 'basename', + 'n': 'basename', + 'm': 'mtime', + 't': 'type', + } + + for key, val in sort_dict.items(): + for key, is_capital in ((key, False), (key.upper(), True)): + # reverse if any of the two letters is capital + map('o' + key, fm.sort(func=val, reverse=is_capital)) + map('O' + key, fm.sort(func=val, reverse=True)) + + map('or', 'Or', 'oR', 'OR', lambda arg: \ + arg.fm.sort(reverse=not arg.fm.settings.reverse)) + + # ----------------------------------------------- console shortcuts + @map("A") + def append_to_filename(arg): + command = 'rename ' + arg.fm.env.cf.basename + arg.fm.open_console(cmode.COMMAND, command) + + map('cw', fm.open_console(cmode.COMMAND, 'rename ')) + map('cd', fm.open_console(cmode.COMMAND, 'cd ')) + map('f', fm.open_console(cmode.COMMAND_QUICK, 'find ')) + map('bf', fm.open_console(cmode.COMMAND, 'filter ')) + map('d', fm.hint('d//u// (disk usage) d//d// (cut)')) + + + # --------------------------------------------- jump to directories + map('gh', fm.cd('~')) + map('ge', fm.cd('/etc')) + map('gu', fm.cd('/usr')) + map('gd', fm.cd('/dev')) + map('gl', fm.cd('/lib')) + map('go', fm.cd('/opt')) + map('gv', fm.cd('/var')) + map('gr', 'g/', fm.cd('/')) + map('gm', fm.cd('/media')) + map('gn', fm.cd('/mnt')) + map('gs', fm.cd('/srv')) + map('gR', fm.cd(RANGERDIR)) + + # ------------------------------------------------------------ tabs + map('gc', ctrl('W'), fm.tab_close()) + map('gt', TAB, fm.tab_move(1)) + map('gT', KEY_BTAB, fm.tab_move(-1)) + map('gn', ctrl('N'), fm.tab_new()) + for n in range(10): + map('g' + str(n), fm.tab_open(n)) + map('', fm.tab_open(n)) + + # ------------------------------------------------------- searching + map('/', fm.open_console(cmode.SEARCH)) + + map('n', fm.search()) + map('N', fm.search(forward=False)) + + map('ct', fm.search(order='tag')) + map('cc', fm.search(order='ctime')) + map('cm', fm.search(order='mimetype')) + map('cs', fm.search(order='size')) + map('c', fm.hint('//c//time //m//imetype //s//ize')) + + # ------------------------------------------------------- bookmarks + for key in ALLOWED_BOOKMARK_KEYS: + map("`" + key, "'" + key, fm.enter_bookmark(key)) + map("m" + key, fm.set_bookmark(key)) + map("um" + key, fm.unset_bookmark(key)) + map("`", "'", "m", fm.draw_bookmarks()) + + + map(':', ';', fm.open_console(cmode.COMMAND)) + + # ---------------------------------------------------- change views + map('i', fm.display_file()) + map(ctrl('p'), fm.display_log()) + map('?', KEY_F1, fm.display_help()) + map('w', lambda arg: arg.fm.ui.open_taskview()) + + # ------------------------------------------------ system functions + map('ZZ', fm.exit()) + map(ctrl('R'), fm.reset()) + map('R', fm.reload_cwd()) + map(ctrl('C'), fm.exit()) + + map(':', ';', fm.open_console(cmode.COMMAND)) + map('>', fm.open_console(cmode.COMMAND_QUICK)) + map('!', fm.open_console(cmode.OPEN)) + map('r', fm.open_console(cmode.OPEN_QUICK)) + + return map + +def console_keys(): + map = KeyMap() + map.merge(system_keys()) + + @map('') + def type_key(arg): + arg.wdg.type_key(arg.match) + + map('', wdg.history_move(-1)) + map('', wdg.history_move(1)) + map('', wdg.tab()) + +#from pprint import pprint +#pprint(browser_keys()._tree[106].__dict__) +#raise SystemExit() + +ui_keys = browser_keys() +taskview_keys = ui_keys +pager_keys = ui_keys +embedded_pager_keys = ui_keys +console_keys = console_keys() +directions = vim() diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index 8d355665..f81f1c5d 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -31,7 +31,7 @@ class UI(DisplayableContainer): is_set_up = False mousemask = curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION load_mode = False - def __init__(self, keymap=None, env=None, fm=None): + def __init__(self, env=None, fm=None): self._draw_title = os.environ["TERM"] in TERMINALS_WITH_TITLE os.environ['ESCDELAY'] = '25' # don't know a cleaner way @@ -40,12 +40,8 @@ class UI(DisplayableContainer): if fm is not None: self.fm = fm - if keymap is None: - self.keymap = self.settings.keys.browser_keys() - else: - self.keymap = keymap self.win = curses.initscr() - self.env.keybuffer.assign(self.keymap, self.settings.keys.directions) + self.env.keymanager.use_context('general') self.env.keybuffer.clear() DisplayableContainer.__init__(self, None) @@ -131,11 +127,11 @@ class UI(DisplayableContainer): self.env.keybuffer.clear() return - self.env.key_append(key) - if DisplayableContainer.press(self, key): return + self.env.keymanager.use_context('general') + self.env.key_append(key) kbuf = self.env.keybuffer cmd = kbuf.command diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index aaa85d8e..67bd7893 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -59,7 +59,6 @@ class Console(Widget): def __init__(self, win): from ranger.container import History Widget.__init__(self, win) - self.keymap = self.settings.keys.console_keys self.clear() self.histories = [] # load histories from files @@ -154,8 +153,6 @@ class Console(Widget): self.line = '' def press(self, key): - from curses.ascii import ctrl, ESC - keytuple = self.env.keybuffer.tuple_with_numbers() try: cmd = self.commandlist[keytuple] diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py index e915f790..b99bf5db 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -40,11 +40,6 @@ class Pager(Widget): self.markup = None self.lines = [] - if embedded: - self.keymap = self.settings.keys.embedded_pager_keys - else: - self.keymap = self.settings.keys.pager_keys - def move_horizontal(self, *a, **k): """For compatibility""" self.fm.notify("Your keys.py is out of date. Can't scroll!", bad=True) diff --git a/ranger/gui/widgets/taskview.py b/ranger/gui/widgets/taskview.py index f7937e11..fe31646d 100644 --- a/ranger/gui/widgets/taskview.py +++ b/ranger/gui/widgets/taskview.py @@ -30,7 +30,6 @@ class TaskView(Widget, Accumulator): Widget.__init__(self, win) Accumulator.__init__(self) self.scroll_begin = 0 - self.keymap = self.settings.keys.taskview_keys def draw(self): base_clr = deque() diff --git a/ranger/shared/settings.py b/ranger/shared/settings.py index a4a58e6e..e4a58f33 100644 --- a/ranger/shared/settings.py +++ b/ranger/shared/settings.py @@ -154,10 +154,16 @@ class SettingsAware(object): except ImportError: from ranger.defaults import apps settings._raw_set('apps', apps) + + SettingsAware.settings = settings + + @staticmethod + def _setup_keys(): # ugly! but works. + import ranger.api.keys + import ranger.shared + env = ranger.shared.EnvironmentAware.env + ranger.api.keys.keymanager = env.keymanager try: import keys except ImportError: from ranger.defaults import keys - settings._raw_set('keys', keys) - - SettingsAware.settings = settings diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index c953e88b..b1cb42fb 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -24,7 +24,7 @@ import sys class PressTestCase(TestCase): """Some useful methods for the actual test""" - def _mkpress(self, keybuffer, keymap): + def _mkpress(self, keybuffer, _=0): def press(keys): keybuffer.clear() match = keybuffer.simulate_press(keys) @@ -492,5 +492,42 @@ class Test(PressTestCase): s.replace('Y') self.assertNotEqual(t._tree, u._tree) + def test_keymap_with_context(self): + def func(arg): + return 5 + def getdown(arg): + return arg.direction.down() + + buffer = KeyBuffer(None, None) + press = self._mkpress(buffer) + kmc = KeyManager(buffer, ['foo', 'bar']) + + map = kmc.get_context('foo') + map('a', func) + map('b', func) + map = kmc.get_context('bar') + map('c', func) + map('', getdown) + + kmc.map('directions', 'j', dir=Direction(down=1)) + + kmc.use_context('foo') + self.assertEqual(5, press('a')) + self.assertEqual(5, press('b')) + self.assertPressFails(buffer, 'c') + + kmc.use_context('bar') + self.assertPressFails(buffer, 'a') + self.assertPressFails(buffer, 'b') + self.assertEqual(5, press('c')) + self.assertEqual(1, press('j')) + kmc.use_context('foo') + kmc.use_context('foo') + kmc.use_context('foo') + kmc.use_context('bar') + kmc.use_context('foo') + kmc.use_context('bar') + kmc.use_context('bar') + self.assertEqual(1, press('j')) if __name__ == '__main__': main() diff --git a/test/tc_ui.py b/test/tc_ui.py index 98ddff93..3c659459 100644 --- a/test/tc_ui.py +++ b/test/tc_ui.py @@ -28,7 +28,7 @@ class Test(unittest.TestCase): def setUp(self): self.fm = Fake() - self.ui = ui.UI(env=Fake(), fm=self.fm, keymap=Fake()) + self.ui = ui.UI(env=Fake(), fm=self.fm) def fakesetup(): self.ui.widget = Fake() -- cgit 1.4.1-2-gfad0 From 4e9384fd9ebcf1c17909dbf2b1e94f0b3ec9100b Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 8 Apr 2010 01:05:03 +0200 Subject: clean up --- ranger/defaults/keys.py | 3 +-- test/tc_newkeys.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'test') diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index c3a2d475..70521e1e 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -227,6 +227,7 @@ map('', 'u', wdg.move(up=0.5, pages=True)) map('', 'f', '', wdg.move(down=1, pages=True)) map('', 'b', '', wdg.move(up=1, pages=True)) map('', wdg.move(down=0.8, pages=True)) +map('', wdg.move(down=1)) # ---------------------------------------------------------- others map('E', fm.edit_file()) @@ -302,5 +303,3 @@ map('', dir=Direction(down=-1, absolute=True)) map('', dir=Direction(down=1, pages=True)) map('', dir=Direction(down=-1, pages=True)) map('%', dir=Direction(down=1, percentage=True, absolute=True)) -map('', dir=Direction(down=1, pages=True)) -map('', dir=Direction(down=1)) diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index b1cb42fb..a79ba83f 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -492,7 +492,7 @@ class Test(PressTestCase): s.replace('Y') self.assertNotEqual(t._tree, u._tree) - def test_keymap_with_context(self): + def test_keymanager(self): def func(arg): return 5 def getdown(arg): @@ -530,4 +530,5 @@ class Test(PressTestCase): kmc.use_context('bar') self.assertEqual(1, press('j')) + if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0 From ef011c914dcf6061af7b30c85db53336594cab81 Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 8 Apr 2010 01:26:27 +0200 Subject: tc_newkey: added 2 failing test cases. Fix it! --- test/tc_newkeys.py | 66 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 13 deletions(-) (limited to 'test') diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index a79ba83f..fc17aeda 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -500,35 +500,75 @@ class Test(PressTestCase): buffer = KeyBuffer(None, None) press = self._mkpress(buffer) - kmc = KeyManager(buffer, ['foo', 'bar']) + keymanager = KeyManager(buffer, ['foo', 'bar']) - map = kmc.get_context('foo') + map = keymanager.get_context('foo') map('a', func) map('b', func) - map = kmc.get_context('bar') + map = keymanager.get_context('bar') map('c', func) map('', getdown) - kmc.map('directions', 'j', dir=Direction(down=1)) + keymanager.map('directions', 'j', dir=Direction(down=1)) - kmc.use_context('foo') + keymanager.use_context('foo') self.assertEqual(5, press('a')) self.assertEqual(5, press('b')) self.assertPressFails(buffer, 'c') - kmc.use_context('bar') + keymanager.use_context('bar') self.assertPressFails(buffer, 'a') self.assertPressFails(buffer, 'b') self.assertEqual(5, press('c')) self.assertEqual(1, press('j')) - kmc.use_context('foo') - kmc.use_context('foo') - kmc.use_context('foo') - kmc.use_context('bar') - kmc.use_context('foo') - kmc.use_context('bar') - kmc.use_context('bar') + keymanager.use_context('foo') + keymanager.use_context('foo') + keymanager.use_context('foo') + keymanager.use_context('bar') + keymanager.use_context('foo') + keymanager.use_context('bar') + keymanager.use_context('bar') self.assertEqual(1, press('j')) + def test_alias_to_direction(self): + def func(arg): + return arg.direction.down() + + km = KeyMap() + directions = KeyMap() + kb = KeyBuffer(km, directions) + press = self._mkpress(kb) + + km.map('', func) + directions.map('j', dir=Direction(down=42)) + self.assertEqual(42, press('j')) + + km.map('o', alias='j') + self.assertEqual(42, press('o')) + + def test_both_directory_and_any_key(self): + def func(arg): + return arg.direction.down() + def func2(arg): + return "yay" + + km = KeyMap() + directions = KeyMap() + kb = KeyBuffer(km, directions) + press = self._mkpress(kb) + + km.map('abc', func) + directions.map('j', dir=Direction(down=42)) + self.assertEqual(42, press('abcj')) + + km.unmap('abc') + + km.map('abc', func2) + self.assertEqual("yay", press('abcd')) + + km.map('abc', func) + + km.map('abc', func2) + self.assertEqual("yay", press('abcd')) if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0 From 4537ca72633b0c60e6fd4700adb6b96c0a823c7b Mon Sep 17 00:00:00 2001 From: hut Date: Thu, 8 Apr 2010 19:56:29 +0200 Subject: tc_direction: fixed maximum=X means that the maximum value is set to X-1. Works for lists: d.move(maximum=len(lst)) is this counter intuitive? --- test/tc_direction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/tc_direction.py b/test/tc_direction.py index dd7e94ab..18f9eb4c 100644 --- a/test/tc_direction.py +++ b/test/tc_direction.py @@ -71,7 +71,7 @@ class TestDirections(unittest.TestCase): self.assertEqual(3, d.move(direction=3)) self.assertEqual(5, d.move(direction=3, current=2)) self.assertEqual(15, d.move(direction=3, pagesize=5)) - self.assertEqual(10, d.move(direction=3, pagesize=5, maximum=10)) + self.assertEqual(9, d.move(direction=3, pagesize=5, maximum=10)) self.assertEqual(18, d.move(direction=9, override=2)) d2 = Direction(absolute=True) self.assertEqual(5, d2.move(direction=9, override=5)) -- cgit 1.4.1-2-gfad0 From 93ecd88d9ad4231ab4926c2df43e00047900cd99 Mon Sep 17 00:00:00 2001 From: hut Date: Fri, 9 Apr 2010 01:29:09 +0200 Subject: partially fix tc_newkeys --- ranger/container/keymap.py | 62 +++++++++++++++++++++++----------------------- ranger/defaults/keys.py | 16 +++++++++--- ranger/ext/tree.py | 1 + test/tc_newkeys.py | 54 +++++++++++++++++++++------------------- 4 files changed, 72 insertions(+), 61 deletions(-) (limited to 'test') diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py index 930800ff..29d6e629 100644 --- a/ranger/container/keymap.py +++ b/ranger/container/keymap.py @@ -14,6 +14,7 @@ # along with this program. If not, see . import curses.ascii +from collections import deque from string import ascii_lowercase from inspect import isfunction, getargspec from ranger.ext.tree import Tree @@ -155,27 +156,28 @@ class KeyBuffer(object): self.direction_keys = direction_keys def add(self, key): - if self.failure: - return None 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 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 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 (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) + # evaluate direction keys {j,k,gg,pagedown,...} + if not self.eval_command: + self._do_eval_direction(key) def _do_eval_direction(self, key): try: @@ -186,8 +188,8 @@ class KeyBuffer(object): else: self._direction_try_to_finish() - def _direction_try_to_finish(self, rec=MAX_ALIAS_RECURSION): - if rec <= 0: + def _direction_try_to_finish(self): + if self.max_alias_recursion <= 0: self.failure = True return None match = self.dir_tree_pointer @@ -197,12 +199,9 @@ class KeyBuffer(object): match = self.dir_tree_pointer if isinstance(self.dir_tree_pointer, Binding): if match.alias: - try: - self.dir_tree_pointer = self.direction_keys[match.alias] - self._direction_try_to_finish(rec - 1) - except KeyError: - self.failure = True - return None + self.key_queue.extend(translate_keys(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: @@ -232,11 +231,11 @@ class KeyBuffer(object): try: self.tree_pointer = self.tree_pointer[key] except TypeError: - print(self.tree_pointer) self.failure = True return None except KeyError: try: + is_ascii_digit(key) or self.direction_keys._tree[key] self.tree_pointer = self.tree_pointer[DIRKEY] except KeyError: try: @@ -261,8 +260,8 @@ class KeyBuffer(object): self.command = None self._try_to_finish() - def _try_to_finish(self, rec=MAX_ALIAS_RECURSION): - if rec <= 0: + def _try_to_finish(self): + if self.max_alias_recursion <= 0: self.failure = True return None assert isinstance(self.tree_pointer, (Binding, dict, KeyMap)) @@ -270,17 +269,15 @@ class KeyBuffer(object): self.tree_pointer = self.tree_pointer._tree if isinstance(self.tree_pointer, Binding): if self.tree_pointer.alias: - try: - self.tree_pointer = self.keymap[self.tree_pointer.alias] - self._try_to_finish(rec - 1) - except KeyError: - self.failure = True - return None + self.key_queue.extend(translate_keys(self.tree_pointer.alias)) + self.tree_pointer = self.keymap._tree + self.max_alias_recursion -= 1 else: self.command = self.tree_pointer self.done = True def clear(self): + self.max_alias_recursion = MAX_ALIAS_RECURSION self.failure = False self.done = False self.quant = None @@ -292,6 +289,8 @@ class KeyBuffer(object): 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 @@ -318,6 +317,7 @@ special_keys = { 'cr': ord("\n"), 'enter': ord("\n"), 'space': ord(" "), + 'esc': curses.ascii.ESC, 'down': curses.KEY_DOWN, 'up': curses.KEY_UP, 'left': curses.KEY_LEFT, diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index 70521e1e..57a0d415 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -265,8 +265,6 @@ map('w', 'q', ESC, ctrl('d'), ctrl('c'), map = keymanager.get_context('console') map.merge(global_keys) map.merge(readline_aliases) -map.unmap('Q') # don't quit with Q in console, so we can type it -map.unmap('') # define my own direction keys map('', wdg.history_move(-1)) map('', wdg.history_move(1)) @@ -274,7 +272,7 @@ map('', wdg.move(right=0, absolute=True)) map('', wdg.move(right=-1, absolute=True)) map('', wdg.tab()) map('', wdg.tab(-1)) -map('', wdg.close()) +map('', '', wdg.close()) map('', '', wdg.execute()) map('', lambda arg: arg.fm.display_command_help(arg.wdg)) @@ -285,14 +283,24 @@ map('', wdg.delete_rest(1)) map('', wdg.delete_rest(-1)) map('', wdg.paste()) -map('') +# Any key which is still undefined will simply be typed in. +@map('') 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('', dir=Direction(down=1)) map('', dir=Direction(down=-1)) diff --git a/ranger/ext/tree.py b/ranger/ext/tree.py index 6d841c2a..a954136b 100644 --- a/ranger/ext/tree.py +++ b/ranger/ext/tree.py @@ -81,6 +81,7 @@ class Tree(object): 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 diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py index fc17aeda..9a7f10c7 100644 --- a/test/tc_newkeys.py +++ b/test/tc_newkeys.py @@ -83,31 +83,6 @@ class Test(PressTestCase): self.assert_(match.function) self.assertEqual(8, match.function(args)) - def test_map_collision(self): - def add_dirs(arg): - return sum(dir.down() for dir in arg.directions) - def return5(_): - return 5 - - - directions = KeyMap() - directions.map('gg', dir=Direction(down=1)) - - - km = KeyMap() - km.map('gh', return5) - km.map('agh', return5) - km.map('a', add_dirs) - - kb = KeyBuffer(km, directions) - press = self._mkpress(kb, km) - - self.assertEqual(5, press('gh')) - self.assertEqual(5, press('agh')) -# self.assertPressFails(kb, 'agh') - self.assertEqual(1, press('agg')) - - def test_translate_keys(self): def test(string, *args): if not args: @@ -187,7 +162,7 @@ class Test(PressTestCase): self.assertEqual(press('c'), press('c@')) self.assertEqual(press('c'), press('c@')) - for n in range(1, 50): + for n in range(1, 10): self.assertPressIncomplete(kb, 'y' * n) for n in range(1, 5): @@ -540,11 +515,13 @@ class Test(PressTestCase): press = self._mkpress(kb) km.map('', func) + km.map('d', func) directions.map('j', dir=Direction(down=42)) self.assertEqual(42, press('j')) km.map('o', alias='j') self.assertEqual(42, press('o')) + self.assertEqual(42, press('do')) def test_both_directory_and_any_key(self): def func(arg): @@ -571,4 +548,29 @@ class Test(PressTestCase): km.map('abc', func2) self.assertEqual("yay", press('abcd')) + def test_map_collision(self): + def add_dirs(arg): + return sum(dir.down() for dir in arg.directions) + def return5(_): + return 5 + + + directions = KeyMap() + directions.map('gg', dir=Direction(down=1)) + + + km = KeyMap() + km.map('gh', return5) + km.map('agh', return5) + km.map('a', add_dirs) + + kb = KeyBuffer(km, directions) + press = self._mkpress(kb, km) + + self.assertEqual(5, press('gh')) + self.assertEqual(5, press('agh')) +# self.assertPressFails(kb, 'agh') + self.assertEqual(1, press('agg')) + + if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0 From feaf9e0a81cf2506b18ad9dbb9732e0be45c10fc Mon Sep 17 00:00:00 2001 From: hut Date: Fri, 9 Apr 2010 04:14:41 +0200 Subject: tc_newkeys: fixed all but one test --- ranger/api/keys.py | 2 +- ranger/container/keymap.py | 32 ++++++++++---- ranger/defaults/keys.py | 101 +++++++++++++++++++++++++-------------------- test/tc_newkeys.py | 30 +++++++++++--- 4 files changed, 107 insertions(+), 58 deletions(-) (limited to 'test') 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 "". + +Example scenario +---------------- +If this keys are defined: +map("dd", "d", 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('', fm.redraw_window()) map('', alias='') # Backspace is bugged sometimes +#map('', wdg.move()) @map('') # move around with direction keys def move(arg): arg.wdg.move(narg=arg.n, **arg.direction) +# -------------------------------------------------- direction keys +map.dir('', down=1) +map.dir('', down=-1) +map.dir('', right=-1) +map.dir('', right=1) +map.dir('', down=0, absolute=True) +map.dir('', down=-1, absolute=True) +map.dir('', down=1, pages=True) +map.dir('', down=-1, pages=True) +map.dir('%', down=1, percentage=True, absolute=True) + # =================================================================== # == Define aliases # =================================================================== -map = vim_aliases = KeyMap() -map('j', alias='') -map('k', alias='') -map('h', alias='') -map('l', alias='') -map('gg', alias='') -map('G', alias='') -map('', alias='') -map('', alias='') - -map = readline_aliases = KeyMap() -map('', alias='') -map('', alias='') -map('', alias='') -map('', alias='') -map('', alias='') -map('', alias='') +map = vim_aliases = KeyMapWithDirections() +map.dir('j', alias='') +map.dir('k', alias='') +map.dir('h', alias='') +map.dir('l', alias='') +map.dir('gg', alias='') +map.dir('G', alias='') +map.dir('', alias='') +map.dir('', alias='') + +map = readline_aliases = KeyMapWithDirections() +map.dir('', alias='') +map.dir('', alias='') +map.dir('', alias='') +map.dir('', alias='') +map.dir('', alias='') +map.dir('', alias='') # =================================================================== @@ -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', fm.copy()) +map('dd', 'd', 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('', 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('', dir=Direction(down=1)) -map('', dir=Direction(down=-1)) -map('', dir=Direction(right=-1)) -map('', dir=Direction(right=1)) -map('', dir=Direction(down=0, absolute=True)) -map('', dir=Direction(down=-1, absolute=True)) -map('', dir=Direction(down=1, pages=True)) -map('', 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('', 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('', func) km.map('d', 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', func) + km.dir('j', down=42) + self.assertEqual(42, press('abcj')) if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0 From 447cbcd65588eafdb85b407750602c0fdd591ef4 Mon Sep 17 00:00:00 2001 From: hut Date: Tue, 13 Apr 2010 17:57:46 +0200 Subject: dirarg: added unit test and almost working implementaiton --- ranger/core/actions.py | 6 +++++- test/tc_direction.py | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/ranger/core/actions.py b/ranger/core/actions.py index 7b8d192d..df623c77 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -545,7 +545,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): def copy(self, narg=None, dirarg=None): """Copy the selected items""" direction = Direction(dirarg or {}) - selected = direction.select( + pos, selected = direction.select( override=narg, lst=self.env.cwd.files, current=self.env.cwd.pointer, @@ -553,7 +553,11 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): selected = selected or self.env.get_selection() self.env.copy = set(f for f in selected if f in self.env.cwd.files) + self.env.copy.add(self.env.cwd.pointed_obj) self.env.cut = False + self.env.cwd.pointer = pos + self.env.cwd.correct_pointer() + self.env.copy.add(self.env.cwd.pointed_obj) self.ui.browser.main_column.request_redraw() def cut(self, narg=None, dirarg=None): diff --git a/test/tc_direction.py b/test/tc_direction.py index 18f9eb4c..124a7001 100644 --- a/test/tc_direction.py +++ b/test/tc_direction.py @@ -76,6 +76,13 @@ class TestDirections(unittest.TestCase): d2 = Direction(absolute=True) self.assertEqual(5, d2.move(direction=9, override=5)) + def test_select(self): + d = Direction(down=3) + lst = list(range(100)) + self.assertEqual((6, [3,4,5]), d.select(current=3, pagesize=10, override=None, lst=lst)) + d = Direction(down=3, pages=True) + self.assertEqual((9, [3,4,5,6,7,8]), d.select(current=3, pagesize=2, override=None, lst=lst)) + if __name__ == '__main__': unittest.main() -- cgit 1.4.1-2-gfad0