diff options
author | hut <hut@lavabit.com> | 2010-04-14 20:01:47 +0200 |
---|---|---|
committer | hut <hut@lavabit.com> | 2010-04-14 20:01:47 +0200 |
commit | ef18cd85cd48117dd8bd36325887bb2a7cc49653 (patch) | |
tree | ca320ae025cc0426527f62b27c08ebbc406b0590 /ranger | |
parent | 681d9f6adf185f1c3adf0801da3d3baac99e1ea7 (diff) | |
parent | 5a8c735198079d928a7611b1f9c5aa7bf6d62a1e (diff) | |
download | ranger-ef18cd85cd48117dd8bd36325887bb2a7cc49653.tar.gz |
Merge branch 'newkey' into devel
Diffstat (limited to 'ranger')
-rw-r--r-- | ranger/__main__.py | 1 | ||||
-rw-r--r-- | ranger/api/keys.py | 23 | ||||
-rw-r--r-- | ranger/container/__init__.py | 3 | ||||
-rw-r--r-- | ranger/container/commandlist.py | 225 | ||||
-rw-r--r-- | ranger/container/keybuffer.py | 71 | ||||
-rw-r--r-- | ranger/container/keymap.py | 406 | ||||
-rw-r--r-- | ranger/core/actions.py | 34 | ||||
-rw-r--r-- | ranger/core/environment.py | 11 | ||||
-rw-r--r-- | ranger/core/fm.py | 20 | ||||
-rw-r--r-- | ranger/defaults/keys.py | 618 | ||||
-rw-r--r-- | ranger/defaults/oldkeys.py | 555 | ||||
-rw-r--r-- | ranger/defaults/options.py | 4 | ||||
-rw-r--r-- | ranger/ext/direction.py | 6 | ||||
-rw-r--r-- | ranger/ext/tree.py | 136 | ||||
-rw-r--r-- | ranger/gui/ui.py | 83 | ||||
-rw-r--r-- | ranger/gui/widgets/browserview.py | 8 | ||||
-rw-r--r-- | ranger/gui/widgets/console.py | 52 | ||||
-rw-r--r-- | ranger/gui/widgets/pager.py | 45 | ||||
-rw-r--r-- | ranger/gui/widgets/taskview.py | 40 | ||||
-rw-r--r-- | ranger/shared/settings.py | 16 |
20 files changed, 1620 insertions, 737 deletions
diff --git a/ranger/__main__.py b/ranger/__main__.py index 827452ff..2aae6343 100644 --- a/ranger/__main__.py +++ b/ranger/__main__.py @@ -113,6 +113,7 @@ def main(): path = '.' EnvironmentAware._assign(Environment(path)) + SettingsAware._setup_keys() try: my_ui = UI() diff --git a/ranger/api/keys.py b/ranger/api/keys.py index 00479b0d..92a0269c 100644 --- a/ranger/api/keys.py +++ b/ranger/api/keys.py @@ -21,6 +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, KeyMapWithDirections class Wrapper(object): def __init__(self, firstattr): @@ -33,10 +34,11 @@ class Wrapper(object): def function(command_argument): args, kws = real_args, real_keywords number = command_argument.n + direction = command_argument.direction obj = getattr(command_argument, self.__firstattr__) fnc = getattr(obj, attr) - if number is not None: - args, kws = replace_narg(number, fnc, args, kws) + if number is not None or direction is not None: + args, kws = replace_narg(number, direction, fnc, args, kws) return fnc(*args, **kws) return function return wrapper @@ -62,6 +64,7 @@ fm = Wrapper('fm') wdg = Wrapper('wdg') +DIRARG_KEYWORD = 'dirarg' NARG_KEYWORD = 'narg' def narg(number_, function_, *args_, **keywords_): @@ -77,7 +80,7 @@ def narg(number_, function_, *args_, **keywords_): args, keywords = replace_narg(number_, function_, args_, keywords_) return function_(*args, **keywords) -def replace_narg(number, function, args, keywords): +def replace_narg(number, direction, function, args, keywords): """ This function returns (args, keywords) with one little change: if <function> has a named argument called "narg", args and keywords @@ -92,10 +95,10 @@ def replace_narg(number, function, args, keywords): => (1, 666), {} """ argspec = getargspec(function).args - if NARG_KEYWORD in argspec: + args = list(args) + if number is not None and NARG_KEYWORD in argspec: try: # is narg in args? - args = list(args) index = argspec.index(NARG_KEYWORD) if ismethod(function): index -= 1 # because of 'self' @@ -104,4 +107,14 @@ def replace_narg(number, function, args, keywords): # is narg in keywords? keywords = dict(keywords) keywords[NARG_KEYWORD] = number + if direction is not None and DIRARG_KEYWORD in argspec: + try: + index = argspec.index(DIRARG_KEYWORD) + if ismethod(function): + index -= 1 # because of 'self' + args[index] = direction + except (ValueError, IndexError): + # is narg in keywords? + keywords = dict(keywords) + keywords[DIRARG_KEYWORD] = direction return args, keywords diff --git a/ranger/container/__init__.py b/ranger/container/__init__.py index 51122291..c1bb8194 100644 --- a/ranger/container/__init__.py +++ b/ranger/container/__init__.py @@ -17,6 +17,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, KeyBuffer, KeyManager from .bookmarks import Bookmarks diff --git a/ranger/container/commandlist.py b/ranger/container/commandlist.py deleted file mode 100644 index eb289c46..00000000 --- a/ranger/container/commandlist.py +++ /dev/null @@ -1,225 +0,0 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -from ranger.ext.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 __call__(self, *args, **keywords): - if keywords: - self.show(*args, **keywords) - else: - lastarg = args[-1] - if hasattr(lastarg, '__call__'): - # do the binding - self.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: self.bind(fnc, *args) - - 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 <new> keys to the command of the <existing> 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 <romanz@lavabit.com> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -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..f09d9cfb --- /dev/null +++ b/ranger/container/keymap.py @@ -0,0 +1,406 @@ +# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import curses.ascii +from collections import deque +from string import ascii_lowercase +from inspect import isfunction, getargspec +from ranger.ext.tree import Tree +from ranger.ext.direction import Direction + +MAX_ALIAS_RECURSION = 20 +PASSIVE_ACTION = 9003 +DIRKEY = 9001 +ANYKEY = 9002 +FUNC = 'func' +DIRECTION = 'direction' +DIRARG = 'dir' +ALIASARG = 'alias' + +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.match = keybuffer.matches and keybuffer.matches[0] or None + self.binding = keybuffer.command + + @staticmethod + def from_widget(widget): + return CommandArgs(widget.fm, \ + widget, widget.env.keybuffer) + +class KeyMap(Tree): + """Contains a tree with all the keybindings""" + def map(self, *args, **keywords): + if keywords: + return self._add_binding(*args, **keywords) + firstarg = args[-1] + if isfunction(firstarg): + keywords[FUNC] = firstarg + return self._add_binding(*args[:-1], **keywords) + def decorator_function(func): + keywords = {FUNC:func} + self.map(*args, **keywords) + return func + return decorator_function + + __call__ = map + + def _add_binding(self, *keys, **actions): + assert keys + bind = Binding(keys, actions) + for key in keys: + self.set(translate_keys(key), bind) + + def unmap(self, *keys): + for key in keys: + self.unset(translate_keys(key)) + + def __getitem__(self, key): + 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 + self._list_of_contexts = contexts + self.clear() + + def clear(self): + self.contexts = dict() + for context in self._list_of_contexts: + self.contexts[context] = KeyMapWithDirections() + + def map(self, context, *args, **keywords): + self.get_context(context).map(*args, **keywords) + + def dir(self, context, *args, **keywords): + self.get_context(context).dir(*args, **keywords) + + def unmap(self, context, *args, **keywords): + self.get_context(context).unmap(*args, **keywords) + + def merge_all(self, keymapwithdirection): + for context, keymap in self.contexts.items(): + keymap.merge(keymapwithdirection) + + def get_context(self, context): + assert isinstance(context, str) + assert context in self.contexts, "no such context: " + context + return self.contexts[context] + __getitem__ = get_context + + def use_context(self, context): + context = self.get_context(context) + if self._keybuffer.keymap is not context: + self._keybuffer.assign(context, context.directions) + self._keybuffer.clear() + + +class Binding(object): + """The keybinding object""" + def __init__(self, keys, actions): + assert hasattr(keys, '__iter__') + assert isinstance(actions, dict) + self.actions = actions + try: + self.function = self.actions[FUNC] + except KeyError: + self.function = None + 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 = tuple(translate_keys(alias)) + +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 + + def add(self, key): + assert isinstance(key, int) + assert key >= 0 + self.all_keys.append(key) + self.key_queue.append(key) + while self.key_queue: + key = self.key_queue.popleft() + + # evaluate quantifiers + if self.eval_quantifier and self._do_eval_quantifier(key): + return + + # evaluate the command + if self.eval_command and self._do_eval_command(key): + return + + # evaluate (the first number of) the direction-quantifier + if self.eval_quantifier and self._do_eval_quantifier(key): + return + + # evaluate direction keys {j,k,gg,pagedown,...} + if not self.eval_command: + self._do_eval_direction(key) + + def _do_eval_direction(self, key): + try: + assert isinstance(self.dir_tree_pointer, dict) + self.dir_tree_pointer = self.dir_tree_pointer[key] + except KeyError: + self.failure = True + else: + self._direction_try_to_finish() + + def _direction_try_to_finish(self): + if self.max_alias_recursion <= 0: + self.failure = True + return None + match = self.dir_tree_pointer + assert isinstance(match, (Binding, dict, KeyMap)) + if isinstance(match, KeyMap): + self.dir_tree_pointer = self.dir_tree_pointer._tree + match = self.dir_tree_pointer + if isinstance(self.dir_tree_pointer, Binding): + if match.alias: + self.key_queue.extend(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: + direction.multiply(self.direction_quant) + self.directions.append(direction) + self.direction_quant = None + self.eval_command = True + self._try_to_finish() + + def _do_eval_quantifier(self, key): + if self.eval_command: + tree = self.tree_pointer + else: + tree = self.dir_tree_pointer + if 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): + assert isinstance(self.tree_pointer, dict), self.tree_pointer + try: + self.tree_pointer = self.tree_pointer[key] + except TypeError: + self.failure = True + return None + except KeyError: + try: + is_ascii_digit(key) or self.direction_keys._tree[key] + self.tree_pointer = self.tree_pointer[DIRKEY] + except KeyError: + try: + self.tree_pointer = self.tree_pointer[ANYKEY] + except KeyError: + self.failure = True + return None + else: + self.matches.append(key) + assert isinstance(self.tree_pointer, (Binding, dict)) + self._try_to_finish() + else: + assert isinstance(self.tree_pointer, (Binding, dict)) + self.eval_command = False + self.eval_quantifier = True + self.dir_tree_pointer = self.direction_keys._tree + else: + if isinstance(self.tree_pointer, dict): + try: + self.command = self.tree_pointer[PASSIVE_ACTION] + except (KeyError, TypeError): + self.command = None + self._try_to_finish() + + def _try_to_finish(self): + if self.max_alias_recursion <= 0: + self.failure = True + return None + assert isinstance(self.tree_pointer, (Binding, dict, KeyMap)) + if isinstance(self.tree_pointer, KeyMap): + self.tree_pointer = self.tree_pointer._tree + if isinstance(self.tree_pointer, Binding): + if self.tree_pointer.alias: + 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 + self.matches = [] + self.command = None + self.direction_quant = None + self.directions = [] + self.all_keys = [] + self.tree_pointer = self.keymap._tree + self.dir_tree_pointer = self.direction_keys._tree + + self.key_queue = deque() + + self.eval_quantifier = True + self.eval_command = True + + def __str__(self): + """returns a concatenation of all characters""" + return "".join(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 + return self.command + +special_keys = { + 'dir': DIRKEY, + 'any': ANYKEY, + 'bg': PASSIVE_ACTION, + 'backspace': curses.KEY_BACKSPACE, + 'backspace2': curses.ascii.DEL, + 'delete': curses.KEY_DC, + 'cr': ord("\n"), + 'enter': ord("\n"), + 'space': ord(" "), + 'esc': curses.ascii.ESC, + 'down': curses.KEY_DOWN, + 'up': curses.KEY_UP, + 'left': curses.KEY_LEFT, + 'right': curses.KEY_RIGHT, + 'pagedown': curses.KEY_NPAGE, + 'pageup': curses.KEY_PPAGE, + 'home': curses.KEY_HOME, + 'end': curses.KEY_END, + 'tab': ord('\t'), + 's-tab': curses.KEY_BTAB, +} +for char in ascii_lowercase: + special_keys['c-' + char] = ord(char) - 96 + +for char in (ascii_lowercase + '0123456789'): + special_keys['a-' + char] = (27, ord(char)) + +def translate_keys(obj): + """ + Translate a keybinding to a sequence of integers + + Example: + lol<CR> => (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: + 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: + 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/core/actions.py b/ranger/core/actions.py index d7e33c9b..2fd7d46d 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -99,6 +99,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): self.log.appendleft(text) if hasattr(self.ui, 'notify'): self.ui.notify(text, duration=duration, bad=bad) + hint = notify def redraw_window(self): """Redraw the window""" @@ -144,7 +145,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): self.move(to=1, percentage=True) # moves to 80% """ direction = Direction(kw) - if 'left' in direction: + if 'left' in direction or direction.left() > 0: steps = direction.left() if narg is not None: steps *= narg @@ -415,6 +416,12 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): """Delete the bookmark with the name <key>""" self.bookmarks.delete(key) + def draw_bookmarks(self): + self.ui.browser.draw_bookmarks = True + + def hide_bookmarks(self): + self.ui.browser.draw_bookmarks = False + # -------------------------- # -- Pager # -------------------------- @@ -539,16 +546,29 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): self.env.cut = False self.ui.browser.main_column.request_redraw() - def copy(self): + def copy(self, narg=None, dirarg=None): """Copy the selected items""" - - selected = self.env.get_selection() - self.env.copy = set(f for f in selected if f in self.env.cwd.files) + cwd = self.env.cwd + if not narg and not dirarg: + selected = (f for f in self.env.get_selection() if f in cwd.files) + else: + if not dirarg and narg: + direction = Direction(down=1) + offset = 0 + else: + direction = Direction(dirarg) + offset = 1 + pos, selected = direction.select( + override=narg, lst=cwd.files, current=cwd.pointer, + pagesize=self.env.termsize[0], offset=offset) + self.env.cwd.pointer = pos + self.env.cwd.correct_pointer() + self.env.copy = set(selected) self.env.cut = False self.ui.browser.main_column.request_redraw() - def cut(self): - self.copy() + def cut(self, narg=None, dirarg=None): + self.copy(narg=narg, dirarg=dirarg) self.env.cut = True self.ui.browser.main_column.request_redraw() diff --git a/ranger/core/environment.py b/ranger/core/environment.py index e5deda07..b712683a 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) @@ -47,7 +51,8 @@ class Environment(SettingsAware, SignalDispatcher): self._cf = None self.pathway = () self.directories = {} - self.keybuffer = KeyBuffer() + self.keybuffer = KeyBuffer(None, None) + self.keymanager = KeyManager(self.keybuffer, ALLOWED_CONTEXTS) self.copy = set() self.history = History(self.settings.max_history_size) @@ -81,7 +86,7 @@ class Environment(SettingsAware, SignalDispatcher): if key == curses.KEY_RESIZE: self.keybuffer.clear() - self.keybuffer.append(key) + self.keybuffer.add(key) def key_clear(self): """Clear the keybuffer""" diff --git a/ranger/core/fm.py b/ranger/core/fm.py index 224ef06f..495b9f13 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -19,7 +19,6 @@ The File Manager, putting the pieces together from time import time from collections import deque -from curses import KEY_MOUSE, KEY_RESIZE import os import sys @@ -100,6 +99,11 @@ class FM(Actions, SignalDispatcher): self.input_blocked = sec != 0 self.input_blocked_until = time() + sec + def input_is_blocked(self): + if self.input_blocked and time() > self.input_blocked_until: + self.input_blocked = False + return self.input_blocked + def loop(self): """ The main loop consists of: @@ -136,19 +140,7 @@ class FM(Actions, SignalDispatcher): ui.set_load_mode(loader.has_work()) - key = ui.get_next_key() - - if key > 0: - if key == KEY_MOUSE: - ui.handle_mouse() - elif key == KEY_RESIZE: - ui.update_size() - else: - if self.input_blocked and \ - time() > self.input_blocked_until: - self.input_blocked = False - if not self.input_blocked: - ui.handle_key(key) + ui.handle_input() gc_tick += 1 if gc_tick > TICKS_BEFORE_COLLECTING_GARBAGE: diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index 136fe11d..866aab87 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -17,320 +17,326 @@ 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 +Examples for keys: "x", "gg", "<C-J><A-4>", "<tab>", "<down><up><right>" -fnc is a function which is called with the CommandArgument object. +fnc is a function which is called with the CommandArgs object. -The CommandArgument object has these attributes: +The CommandArgs object has these attributes: arg.fm: the file manager instance arg.wdg: the current widget arg.n: the number typed before the key combination (if allowed) +arg.direction: the direction object (if applicable) arg.keys: the string representation of the used key combination arg.keybuffer: the keybuffer instance -Check ranger.keyapi for more information +Direction keys are special. They must be mapped with: map.dir(*keys, **args) +where args is a dict of values such as up, down, to, absolute, relative... +Example: map.dir('gg', to=0) +Direction keys can be accessed in a mapping that contians "<dir>". + +Additionally, there are shortcuts for accessing methods of the current +file manager and widget instance: +map('xyz', fm.method(foo=bar)) +will be translated to: +map('xyz', lamdba arg: arg.fm.method(foo=bar)) +If possible, arg.n and arg.direction are automatically inserted. + + +Example scenario +---------------- +If this keys are defined: +map("dd", "d<dir>", fm.cut(foo=bar)) +map.dir("gg", to=0) + +Type in the keys on the left and the function on the right will be executed: +dd => fm.cut(foo=bar) +5dd => fm.cut(foo=bar, narg=5) +dgg => fm.cut(foo=bar, dirarg=Direction(to=0)) +5dgg => fm.cut(foo=bar, narg=5, dirarg=Direction(to=0)) +5d3gg => fm.cut(foo=bar, narg=5, dirarg=Direction(to=3)) """ -# 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)) - - def move_parent(n): - def fnc(arg): - arg.fm.move_left() - arg.fm.move_pointer(n) - if arg.fm.env.cf.is_directory: - arg.fm.move_right() - return fnc - - map(']', move_parent(1)) - map('[', move_parent(-1)) - 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('ud', fm.uncut()) - 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('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(1, 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()) - - # ------------------------------------------------ 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<Enter> 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('!', 's', 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)) +from ranger import log + +# =================================================================== +# == Define keys for everywhere: +# =================================================================== +map = global_keys = KeyMapWithDirections() +map('Q', fm.exit()) +map('<C-L>', fm.redraw_window()) +map('<backspace2>', alias='<backspace>') # Backspace is bugged sometimes + +#map('<dir>', wdg.move()) +@map('<dir>') # move around with direction keys +def move(arg): + arg.wdg.move(narg=arg.n, **arg.direction) + +# -------------------------------------------------- direction keys +map.dir('<down>', down=1) +map.dir('<up>', up=1) +map.dir('<left>', left=1) +map.dir('<right>', right=1) +map.dir('<home>', down=0, absolute=True) +map.dir('<end>', down=-1, absolute=True) +map.dir('<pagedown>', down=1, pages=True) +map.dir('<pageup>', down=-1, pages=True) +map.dir('%', down=1, percentage=True, absolute=True) + + +# =================================================================== +# == Define aliases +# =================================================================== +map = vim_aliases = KeyMapWithDirections() +map.dir('j', alias='<down>') +map.dir('k', alias='<up>') +map.dir('h', alias='<left>') +map.dir('l', alias='<right>') +map.dir('gg', alias='<home>') +map.dir('G', alias='<end>') +map.dir('<C-F>', alias='<pagedown>') +map.dir('<C-B>', alias='<pageup>') + +map = readline_aliases = KeyMapWithDirections() +map.dir('<C-B>', alias='<left>') +map.dir('<C-F>', alias='<right>') +map.dir('<C-A>', alias='<home>') +map.dir('<C-E>', alias='<end>') +map.dir('<C-D>', alias='<delete>') +map.dir('<C-H>', alias='<backspace>') + + +# =================================================================== +# == Define keys in "general" context: +# =================================================================== +map = keymanager['general'] +map.merge(global_keys) +map.merge(vim_aliases) + +# -------------------------------------------------------- movement +map('gg', fm.move(to=0)) +map('<C-D>', 'J', fm.move(down=0.5, pages=True)) +map('<C-U>', 'K', fm.move(up=0.5, pages=True)) +map(']', fm.move_parent(1)) +map('[', fm.move_parent(-1)) +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', 'y<dir>', fm.copy()) +map('dd', 'd<dir>', fm.cut()) +map('ud', fm.uncut()) +map('pp', fm.paste()) +map('po', fm.paste(overwrite=True)) +map('pl', fm.paste_symlink()) +map('p<bg>', 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('z<bg>', fm.hint("show_//h//idden //p//review_files" \ + "//d//irectories_first //c//ollapse_preview flush//i//nput")) +map('zh', fm.toggle_boolean_option('show_hidden')) +map('zp', fm.toggle_boolean_option('preview_files')) +map('zi', fm.toggle_boolean_option('flushinput')) +map('zd', fm.toggle_boolean_option('directories_first')) +map('zc', fm.toggle_boolean_option('collapse_preview')) + +# ------------------------------------------------------------ sort +map('o<bg>', 'O<bg>', 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.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('bf', fm.open_console(cmode.COMMAND, 'filter ')) +map('d<bg>', fm.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('gs', fm.cd('/srv')) +map('gR', fm.cd(RANGERDIR)) + +# ------------------------------------------------------------ tabs +map('gc', '<C-W>', fm.tab_close()) +map('gt', '<TAB>', fm.tab_move(1)) +map('gT', '<S-TAB>', fm.tab_move(-1)) +map('gn', '<C-N>', fm.tab_new()) +for n in range(1, 10): + map('g' + str(n), fm.tab_open(n)) + map('<A-' + 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<bg>', 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("`<bg>", "'<bg>", "m<bg>", fm.draw_bookmarks()) + +# ---------------------------------------------------- change views +map('i', fm.display_file()) +map('<C-P>', fm.display_log()) +map('?', KEY_F1, fm.display_help()) +map('w', lambda arg: arg.fm.ui.open_taskview()) + +# ------------------------------------------------ system functions +map('ZZ', 'ZQ', fm.exit()) +map('<C-R>', fm.reset()) +map('R', fm.reload_cwd()) +@map('<C-C>') +def ctrl_c(arg): + try: + item = arg.fm.loader.queue[0] + except: + arg.fm.notify("Type Q or :quit<Enter> 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('!', 's', fm.open_console(cmode.OPEN)) +map('r', fm.open_console(cmode.OPEN_QUICK)) + + +# =================================================================== +# == Define keys for the pager +# =================================================================== +map = pager_keys = KeyMapWithDirections() +map.merge(global_keys) +map.merge(vim_aliases) + +# -------------------------------------------------------- movement +map('<left>', wdg.move(left=4)) +map('<right>', wdg.move(right=4)) +map('<C-D>', 'd', wdg.move(down=0.5, pages=True)) +map('<C-U>', 'u', wdg.move(up=0.5, pages=True)) +map('<C-F>', 'f', '<pagedown>', wdg.move(down=1, pages=True)) +map('<C-B>', 'b', '<pageup>', wdg.move(up=1, pages=True)) +map('<space>', wdg.move(down=0.8, pages=True)) +map('<cr>', wdg.move(down=1)) + +# ---------------------------------------------------------- others +map('E', fm.edit_file()) +map('?', fm.display_help()) + +# --------------------------------------------------- bind the keys +# There are two different kinds of pagers, each have a different +# method for exiting: + +map = keymanager.get_context('pager') +map.merge(pager_keys) +map('q', 'i', '<esc>', lambda arg: arg.fm.ui.close_pager()) + +map = keymanager.get_context('embedded_pager') +map.merge(pager_keys) +map('q', 'i', '<esc>', lambda arg: arg.fm.ui.close_embedded_pager()) + + +# =================================================================== +# == Define keys for the taskview +# =================================================================== +map = keymanager.get_context('taskview') +map('K', wdg.task_move(0)) +map('J', wdg.task_move(-1)) +map('dd', wdg.task_remove()) + +map('?', fm.display_help()) +map('w', 'q', ESC, ctrl('d'), ctrl('c'), + lambda arg: arg.fm.ui.close_taskview()) + + +# =================================================================== +# == Define keys for the console +# =================================================================== +map = keymanager.get_context('console') +map.merge(global_keys) +map.merge(readline_aliases) + +map('<up>', wdg.history_move(-1)) +map('<down>', wdg.history_move(1)) +map('<home>', wdg.move(right=0, absolute=True)) +map('<end>', wdg.move(right=-1, absolute=True)) +map('<tab>', wdg.tab()) +map('<s-tab>', wdg.tab(-1)) +map('<c-c>', '<esc>', wdg.close()) +map('<CR>', '<c-j>', wdg.execute()) +map('<F1>', lambda arg: arg.fm.display_command_help(arg.wdg)) + +map('<backspace>', wdg.delete(-1)) +map('<delete>', wdg.delete(1)) +map('<C-W>', wdg.delete_word()) +map('<C-K>', wdg.delete_rest(1)) +map('<C-U>', wdg.delete_rest(-1)) +map('<C-Y>', wdg.paste()) + +# Any key which is still undefined will simply be typed in. +@map('<any>') +def type_key(arg): + arg.wdg.type_key(arg.match) + +# Unmap some global keys so we can type them: +map.unmap('Q') +map.directions.unmap('%') 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 <romanz@lavabit.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +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<Enter> 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('<down>', dir=Direction(down=1)) + map('<up>', dir=Direction(down=-1)) + map('<left>', dir=Direction(right=-1)) + map('<right>', dir=Direction(right=1)) + map('<home>', dir=Direction(down=0, absolute=True)) + map('<end>', dir=Direction(down=-1, absolute=True)) + map('<pagedown>', dir=Direction(down=1, pages=True)) + map('<pageup>', dir=Direction(down=-1, pages=True)) + map('%<any>', dir=Direction(down=1, percentage=True, absolute=True)) + map('<space>', dir=Direction(down=1, pages=True)) + map('<CR>', dir=Direction(down=1)) + + return map + +def vim(): + # Direction Keys + map = KeyMap() + map.merge(base_directions()) + map('j', alias='<down>') + map('k', alias='<up>') + map('h', alias='<left>') + map('l', alias='<right>') + map('gg', alias='<home>') + map('G', alias='<end>') + map('J', dir=Direction(down=20)) + map('K', dir=Direction(down=-20)) + + return map + +def system_keys(): + map = KeyMap() + map('Q', fm.exit()) + map('<mouse>', fm.handle_mouse()) + map('<C-L>', fm.redraw_window()) + map('<resize>', fm.resize()) + + return map + +def browser_keys(): + map = KeyMap() + map.merge(system_keys()) + + @map('<dir>') + def move(arg): + arg.fm.move(narg=arg.n, **arg.direction) + map('gg', fm.move(to=0)) + map(fm.exit(), 'Q') + + map('<cr>', 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<bg>', 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<bg>', 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<bg>', 'O<bg>', 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<bg>', 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('<A-' + 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<bg>', 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("`<bg>", "'<bg>", "m<bg>", 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('<any>') + def type_key(arg): + arg.wdg.type_key(arg.match) + + map('<up>', wdg.history_move(-1)) + map('<down>', wdg.history_move(1)) + map('<tab>', 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/defaults/options.py b/ranger/defaults/options.py index a9dd8bde..0593bae3 100644 --- a/ranger/defaults/options.py +++ b/ranger/defaults/options.py @@ -121,3 +121,7 @@ def colorscheme_overlay(context, fg, bg, attr): # The above function was just an example, let's set it back to None colorscheme_overlay = None + +# Enable this if key combinations with the Alt Key don't work for you. +# (Especially on xterm) +xterm_alt_key = False diff --git a/ranger/ext/direction.py b/ranger/ext/direction.py index 0b3f55f7..b9fbcac9 100644 --- a/ranger/ext/direction.py +++ b/ranger/ext/direction.py @@ -133,3 +133,9 @@ class Direction(dict): else: pos += current return int(max(min(pos, maximum + offset - 1), minimum)) + + def select(self, lst, override, current, pagesize, offset=1): + dest = self.move(direction=self.down(), override=override, + current=current, pagesize=pagesize, minimum=0, maximum=len(lst)) + selection = lst[min(current, dest):max(current, dest) + offset] + return dest + offset - 1, selection diff --git a/ranger/ext/tree.py b/ranger/ext/tree.py new file mode 100644 index 00000000..a954136b --- /dev/null +++ b/ranger/ext/tree.py @@ -0,0 +1,136 @@ +# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +class Tree(object): + def __init__(self, dictionary=None, parent=None, key=None): + if dictionary is None: + self._tree = dict() + else: + self._tree = dictionary + self.key = key + self.parent = parent + + def copy(self): + """Create a deep copy""" + def deep_copy_dict(dct): + dct = dct.copy() + for key, val in dct.items(): + if isinstance(val, dict): + dct[key] = deep_copy_dict(val) + return dct + newtree = Tree() + if isinstance(self._tree, dict): + newtree._tree = deep_copy_dict(self._tree) + else: + newtree._tree = self._tree + return newtree + + def merge(self, other, copy=False): + """Merge another Tree into a copy of self""" + def deep_merge(branch, otherbranch): + assert isinstance(otherbranch, dict) + if not isinstance(branch, dict): + branch = dict() + elif copy: + branch = branch.copy() + for key, val in otherbranch.items(): + if isinstance(val, dict): + if key not in branch: + branch[key] = None + branch[key] = deep_merge(branch[key], val) + else: + branch[key] = val + return branch + + if isinstance(self._tree, dict) and isinstance(other._tree, dict): + content = deep_merge(self._tree, other._tree) + elif copy and hasattr(other._tree, 'copy'): + content = other._tree.copy() + else: + content = other._tree + return type(self)(content) + + def set(self, keys, value, force=True): + """Sets the element at the end of the path to <value>.""" + if not isinstance(keys, (list, tuple)): + keys = tuple(keys) + if len(keys) == 0: + self.replace(value) + else: + fnc = force and self.plow or self.traverse + subtree = fnc(keys) + subtree.replace(value) + + def unset(self, iterable): + chars = list(iterable) + first = True + + while chars: + if first or isinstance(subtree, Tree) and subtree.empty(): + top = chars.pop() + subtree = self.traverse(chars) + assert top in subtree._tree, "no such key: " + chr(top) + del subtree._tree[top] + else: + break + first = False + + def empty(self): + return len(self._tree) == 0 + + def replace(self, value): + if self.parent: + self.parent[self.key] = value + self._tree = value + + def plow(self, iterable): + """Move along a path, creating nonexistant subtrees""" + tree = self._tree + last_tree = None + char = None + for char in iterable: + try: + newtree = tree[char] + if not isinstance(newtree, dict): + raise KeyError() + except KeyError: + newtree = dict() + tree[char] = newtree + last_tree = tree + tree = newtree + if isinstance(tree, dict): + return type(self)(tree, parent=last_tree, key=char) + else: + return tree + + def traverse(self, iterable): + """Move along a path, raising exceptions when failed""" + tree = self._tree + last_tree = tree + char = None + for char in iterable: + last_tree = tree + try: + tree = tree[char] + except TypeError: + raise KeyError("trying to enter leaf") + except KeyError: + raise KeyError(repr(char) + " not in tree " + str(tree)) + if isinstance(tree, dict): + return type(self)(tree, parent=last_tree, key=char) + else: + return tree + + __getitem__ = traverse diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index 6d9c78ad..983cffc8 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -20,8 +20,8 @@ import curses import _curses from .displayable import DisplayableContainer +from ranger.container.keymap import CommandArgs from .mouse_event import MouseEvent -from ranger.container import CommandList TERMINALS_WITH_TITLE = ("xterm", "xterm-256color", "rxvt", "rxvt-256color", "rxvt-unicode", "aterm", "Eterm", @@ -47,7 +47,7 @@ def _setup_mouse(signal): class UI(DisplayableContainer): is_set_up = False load_mode = False - def __init__(self, commandlist=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 @@ -56,13 +56,9 @@ class UI(DisplayableContainer): if fm is not None: self.fm = fm - - if commandlist is None: - self.commandlist = CommandList() - self.settings.keys.initialize_commands(self.commandlist) - else: - self.commandlist = commandlist self.win = curses.initscr() + self.env.keymanager.use_context('general') + self.env.keybuffer.clear() DisplayableContainer.__init__(self, None) @@ -136,41 +132,74 @@ class UI(DisplayableContainer): if hasattr(self, 'hint'): self.hint() - self.env.key_append(key) + if key < 0: + self.env.keybuffer.clear() + return if DisplayableContainer.press(self, key): return - try: - tup = self.env.keybuffer.tuple_without_numbers() + self.env.keymanager.use_context('general') + self.env.key_append(key) + kbuf = self.env.keybuffer + cmd = kbuf.command - if tup: - cmd = self.commandlist[tup] - else: - return - except KeyError: - self.env.key_clear() + self.fm.hide_bookmarks() + + if kbuf.failure: + kbuf.clear() + return + elif not cmd: return 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 hasattr(cmd, 'execute'): + if cmd.function: try: - cmd.execute_wrap(self.fm) + cmd.function(CommandArgs.from_widget(self.fm)) except Exception as error: self.fm.notify(error) - self.env.key_clear() + if kbuf.done: + kbuf.clear() + else: + kbuf.clear() + + def handle_keys(self, *keys): + for key in keys: + self.handle_key(key) - def get_next_key(self): - """Waits for key input and returns the pressed key""" + def handle_input(self): key = self.win.getch() - if key is not -1: + if key is 27 or key >= 128 and key < 256: + # Handle special keys like ALT+X or unicode here: + keys = [key] + previous_load_mode = self.load_mode + self.set_load_mode(True) + for n in range(4): + getkey = self.win.getch() + if getkey is not -1: + keys.append(getkey) + if len(keys) == 1: + keys.append(-1) + if self.settings.xterm_alt_key: + if len(keys) == 2 and keys[1] in range(127, 256): + keys = [27, keys[1] - 128] + self.handle_keys(*keys) + self.set_load_mode(previous_load_mode) + if self.settings.flushinput: + curses.flushinp() + else: + # Handle simple key presses, CTRL+X, etc here: if self.settings.flushinput: curses.flushinp() - return key + if key > 0: + if key == curses.KEY_MOUSE: + self.handle_mouse() + elif key == curses.KEY_RESIZE: + self.update_size() + else: + if not self.fm.input_is_blocked(): + self.handle_key(key) def setup(self): """ diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/browserview.py index 7f2ea5fb..466e23eb 100644 --- a/ranger/gui/widgets/browserview.py +++ b/ranger/gui/widgets/browserview.py @@ -25,6 +25,7 @@ class BrowserView(Widget, DisplayableContainer): ratios = None preview = True preview_available = True + draw_bookmarks = False stretch_ratios = None need_clear = False @@ -89,10 +90,9 @@ class BrowserView(Widget, DisplayableContainer): self.need_clear = True def draw(self): - try: - if self.env.cmd.show_obj.draw_bookmarks: - self._draw_bookmarks() - except AttributeError: + if self.draw_bookmarks: + self._draw_bookmarks() + else: if self.need_clear: self.win.erase() self.need_redraw = True diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index 677be2a3..c93a4f38 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -29,9 +29,10 @@ from ranger.gui.widgets.console_mode import is_valid_mode, mode_to_class from ranger import log, relpath_conf from ranger.core.runner import ALLOWED_FLAGS from ranger.ext.shell_escape import shell_quote +from ranger.container.keymap import CommandArgs from ranger.ext.get_executables import get_executables from ranger.ext.direction import Direction -from ranger.container import CommandList, History +from ranger.container import History from ranger.container.history import HistoryEmptyException import ranger @@ -49,7 +50,6 @@ class _CustomTemplate(string.Template): class Console(Widget): mode = None visible = False - commandlist = None last_cursor_mode = None prompt = ':' copy = '' @@ -63,8 +63,6 @@ class Console(Widget): def __init__(self, win): Widget.__init__(self, win) - self.commandlist = CommandList() - self.settings.keys.initialize_console_commands(self.commandlist) self.clear() self.histories = [] # load histories from files @@ -159,37 +157,37 @@ class Console(Widget): self.line = '' def press(self, key): + self.env.keymanager.use_context('console') + self.env.key_append(key) + kbuf = self.env.keybuffer + cmd = kbuf.command - keytuple = self.env.keybuffer.tuple_with_numbers() - try: - cmd = self.commandlist[keytuple] - except KeyError: - # An unclean hack to allow unicode input. - # This whole part should be replaced. - try: - chrkey = chr(keytuple[0]) - except: - pass - else: - self.type_key(chrkey) - finally: - self.env.key_clear() - return - - if cmd == self.commandlist.dummy_object: + if kbuf.failure: + kbuf.clear() + return + elif not cmd: return - try: - cmd.execute_wrap(self) - except Exception as error: - self.fm.notify(error) - self.env.key_clear() + self.env.cmd = cmd + + if cmd.function: + try: + cmd.function(CommandArgs.from_widget(self)) + except Exception as error: + self.fm.notify(error) + if kbuf.done: + kbuf.clear() + else: + kbuf.clear() def type_key(self, key): self.tab_deque = None if isinstance(key, int): - key = chr(key) + try: + key = chr(key) + except ValueError: + return if self.pos == len(self.line): self.line += key diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py index 91383a18..85022a01 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -18,8 +18,8 @@ The pager displays text and allows you to scroll inside it. """ import re from . import Widget -from ranger.container.commandlist import CommandList from ranger.ext.direction import Direction +from ranger.container.keymap import CommandArgs BAR_REGEXP = re.compile(r'\|\d+\?\|') QUOTES_REGEXP = re.compile(r'"[^"]+?"') @@ -41,15 +41,6 @@ class Pager(Widget): self.markup = None self.lines = [] - self.commandlist = CommandList() - - if embedded: - keyfnc = self.settings.keys.initialize_embedded_pager_commands - else: - keyfnc = self.settings.keys.initialize_pager_commands - - keyfnc(self.commandlist) - def move_horizontal(self, *a, **k): """For compatibility""" self.fm.notify("Your keys.py is out of date. Can't scroll!", bad=True) @@ -142,22 +133,28 @@ class Pager(Widget): offset=-self.hei) def press(self, key): - try: - tup = self.env.keybuffer.tuple_without_numbers() - if tup: - cmd = self.commandlist[tup] - else: - return + self.env.keymanager.use_context(self.embedded and 'embedded_pager' or 'pager') + self.env.key_append(key) + kbuf = self.env.keybuffer + cmd = kbuf.command + + if kbuf.failure: + kbuf.clear() + return + elif not cmd: + return + + self.env.cmd = cmd - except KeyError: - self.env.key_clear() + if cmd.function: + try: + cmd.function(CommandArgs.from_widget(self)) + except Exception as error: + self.fm.notify(error) + if kbuf.done: + kbuf.clear() else: - if hasattr(cmd, 'execute'): - try: - cmd.execute_wrap(self) - except Exception as error: - self.fm.notify(error) - self.env.key_clear() + kbuf.clear() def set_source(self, source, strip=False): if self.source and self.source_is_stream: diff --git a/ranger/gui/widgets/taskview.py b/ranger/gui/widgets/taskview.py index 6d025048..475b903c 100644 --- a/ranger/gui/widgets/taskview.py +++ b/ranger/gui/widgets/taskview.py @@ -22,7 +22,7 @@ from collections import deque from . import Widget from ranger.ext.accumulator import Accumulator -from ranger.container import CommandList +from ranger.container.keymap import CommandArgs class TaskView(Widget, Accumulator): old_lst = None @@ -31,8 +31,6 @@ class TaskView(Widget, Accumulator): Widget.__init__(self, win) Accumulator.__init__(self) self.scroll_begin = 0 - self.commandlist = CommandList() - self.settings.keys.initialize_taskview_commands(self.commandlist) def draw(self): base_clr = deque() @@ -97,22 +95,28 @@ class TaskView(Widget, Accumulator): self.fm.loader.move(_from=i, to=to) def press(self, key): - try: - tup = self.env.keybuffer.tuple_without_numbers() - if tup: - cmd = self.commandlist[tup] - else: - return - - except KeyError: - self.env.key_clear() + self.env.keymanager.use_context('taskview') + self.env.key_append(key) + kbuf = self.env.keybuffer + cmd = kbuf.command + + if kbuf.failure: + kbuf.clear() + return + elif not cmd: + return + + self.env.cmd = cmd + + if cmd.function: + try: + cmd.function(CommandArgs.from_widget(self)) + except Exception as error: + self.fm.notify(error) + if kbuf.done: + kbuf.clear() else: - if hasattr(cmd, 'execute'): - try: - cmd.execute_wrap(self) - except Exception as error: - self.fm.notify(error) - self.env.key_clear() + kbuf.clear() def get_list(self): return self.fm.loader.queue diff --git a/ranger/shared/settings.py b/ranger/shared/settings.py index 692d06d2..01d4caf6 100644 --- a/ranger/shared/settings.py +++ b/ranger/shared/settings.py @@ -45,6 +45,7 @@ ALLOWED_SETTINGS = { 'colorscheme': str, 'colorscheme_overlay': (type(None), type(lambda:0)), 'hidden_filter': lambda x: isinstance(x, str) or hasattr(x, 'match'), + 'xterm_alt_key': bool, } @@ -173,10 +174,17 @@ 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 + from ranger.defaults import keys try: import keys except ImportError: - from ranger.defaults import keys - settings._raw_set('keys', keys) - - SettingsAware.settings = settings + pass |