diff options
author | hut <hut@lavabit.com> | 2011-09-30 18:12:39 +0200 |
---|---|---|
committer | hut <hut@lavabit.com> | 2011-09-30 18:12:39 +0200 |
commit | 8eecaa8a29740a7cadb102df31c0e0c0af0bbe8e (patch) | |
tree | 39c7ee231b6d36108685d3938594a559f8dbbedb /ranger | |
parent | dedc56b6d68dec95b688e47bbadb6bdea9c39568 (diff) | |
download | ranger-8eecaa8a29740a7cadb102df31c0e0c0af0bbe8e.tar.gz |
ext.keybindings: implemented %any macro
Diffstat (limited to 'ranger')
-rw-r--r-- | ranger/api/commands.py | 1 | ||||
-rw-r--r-- | ranger/api/keys.py | 131 | ||||
-rw-r--r-- | ranger/container/__init__.py | 2 | ||||
-rw-r--r-- | ranger/container/bookmarks.py | 2 | ||||
-rw-r--r-- | ranger/container/settingobject.py | 2 | ||||
-rw-r--r-- | ranger/core/actions.py | 16 | ||||
-rw-r--r-- | ranger/core/fm.py | 6 | ||||
-rw-r--r-- | ranger/core/helper.py | 28 | ||||
-rw-r--r-- | ranger/defaults/commands.py | 1 | ||||
-rw-r--r-- | ranger/defaults/options.py | 10 | ||||
-rw-r--r-- | ranger/defaults/rc.conf | 9 | ||||
-rw-r--r-- | ranger/ext/keybinding_parser.py | 14 | ||||
-rw-r--r-- | ranger/ext/keybindings.py | 2 | ||||
-rw-r--r-- | ranger/gui/ui.py | 5 | ||||
-rw-r--r-- | ranger/gui/widgets/console.py | 1 | ||||
-rw-r--r-- | ranger/gui/widgets/pager.py | 1 | ||||
-rw-r--r-- | ranger/gui/widgets/taskview.py | 1 |
17 files changed, 71 insertions, 161 deletions
diff --git a/ranger/api/commands.py b/ranger/api/commands.py index 1e2cf912..01f47732 100644 --- a/ranger/api/commands.py +++ b/ranger/api/commands.py @@ -90,6 +90,7 @@ class Command(FileManagerAware): """Abstract command class""" name = None allow_abbrev = True + resolve_macros = True _shifted = 0 def __init__(self, line): diff --git a/ranger/api/keys.py b/ranger/api/keys.py deleted file mode 100644 index 75de6237..00000000 --- a/ranger/api/keys.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os -from curses import * -from curses.ascii import * -from inspect import getargspec, ismethod - -from ranger import RANGERDIR -from ranger.api import * -from ranger.container.bookmarks import ALLOWED_KEYS as ALLOWED_BOOKMARK_KEYS -from ranger.container.tags import ALLOWED_KEYS as ALLOWED_TAGS_KEYS -from ranger.container.keymap import KeyMap, Direction, KeyMapWithDirections - -# A dummy that allows the generation of docstrings in ranger.defaults.keys -class DummyKeyManager(object): - def get_context(self, _): - class Dummy(object): - def __getattr__(self, *_, **__): - return Dummy() - __call__ = __getattr__ - return Dummy() -keymanager = DummyKeyManager() - -class Wrapper(object): - def __init__(self, firstattr): - self.__firstattr__ = firstattr - - def __getattr__(self, attr): - if attr.startswith('_'): - raise AttributeError - def wrapper(*real_args, **real_keywords): - 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 or direction is not None: - args, kws = replace_narg(number, direction, fnc, args, kws) - return fnc(*args, **kws) - return function - return wrapper - -# fm.enter_dir('~') is translated into lambda arg: arg.fm.enter_dir('~') -# this makes things like this possible: -# bind('gh', fm.enter_dir('~')) -# -# but NOT: (note the 2 dots) -# bind('H', fm.history.go(-1)) -# -# for something like that, use the long version: -# bind('H', lambda arg: arg.fm.history.go(-1)) -# -# If the method has an argument named "narg", pressing a number before -# the key will pass that number as the narg argument. If you want the -# same behaviour in a custom lambda function, you can write: -# bind('gg', fm.move(to=0)) -# as: -# bind('gg', lambda arg: narg(arg.n, arg.fm.move, to=0)) - -fm = Wrapper('fm') -wdg = Wrapper('wdg') - - -DIRARG_KEYWORD = 'dirarg' -NARG_KEYWORD = 'narg' - -def narg(number_, function_, *args_, **keywords_): - """ - This applies the replace_narg function to the arguments and keywords - and directly runs this function. - - Example: - def foo(xyz, narg): return hash((xyz, narg)) - - narg(50, foo, 123) == foo(123, narg=50) - """ - args, keywords = replace_narg(number_, function_, args_, keywords_) - return 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 - will be modified so that the value of "narg" will be <number>. - - def foo(xyz, narg): pass - - replace_narg(666, foo, (), {'narg': 10, 'xyz': 5}) - => (), {'narg': 666, 'xyz': 5} - - replace_narg(666, foo, (1, 2), {}) - => (1, 666), {} - """ - argspec = getargspec(function).args - args = list(args) - if number is not None and NARG_KEYWORD in argspec: - try: - # is narg in args? - index = argspec.index(NARG_KEYWORD) - if ismethod(function): - index -= 1 # because of 'self' - args[index] = number - except (ValueError, IndexError): - # 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 01eb6043..21a336bc 100644 --- a/ranger/container/__init__.py +++ b/ranger/container/__init__.py @@ -3,6 +3,4 @@ This package includes container-objects which are used to manage stored data """ from ranger.container.history import History -from .keymap import KeyMap, KeyManager -from .keybuffer import KeyBuffer from .bookmarks import Bookmarks diff --git a/ranger/container/bookmarks.py b/ranger/container/bookmarks.py index f115c753..0dcfcbc3 100644 --- a/ranger/container/bookmarks.py +++ b/ranger/container/bookmarks.py @@ -89,7 +89,7 @@ class Bookmarks(object): if key in self.dct: return self.dct[key] else: - raise KeyError("Nonexistant Bookmark!") + raise KeyError("Nonexistant Bookmark: `%s'!" % key) def __setitem__(self, key, value): """Bookmark <value> to the key <key>. diff --git a/ranger/container/settingobject.py b/ranger/container/settingobject.py index a245db21..4d95536a 100644 --- a/ranger/container/settingobject.py +++ b/ranger/container/settingobject.py @@ -31,7 +31,7 @@ ALLOWED_SETTINGS = { 'draw_borders': bool, 'flushinput': bool, 'hidden_filter': lambda x: isinstance(x, str) or hasattr(x, 'match'), - 'load_default_rc': bool, + 'load_default_rc': (bool, type(None)), 'max_console_history_size': (int, type(None)), 'max_history_size': (int, type(None)), 'mouse_enabled': bool, diff --git a/ranger/core/actions.py b/ranger/core/actions.py index 44b5eac8..2621abc5 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -24,6 +24,7 @@ from inspect import cleandoc import ranger from ranger.ext.direction import Direction from ranger.ext.relative_symlink import relative_symlink +from ranger.ext.keybinding_parser import construct_keybinding from ranger.ext.shell_escape import shell_quote from ranger import fsobject from ranger.core.shared import FileManagerAware, EnvironmentAware, \ @@ -34,7 +35,6 @@ from ranger.core.loader import CommandLoader class _MacroTemplate(string.Template): """A template for substituting macros in commands""" delimiter = '%' - idpattern = '\d?[a-z]' class Actions(FileManagerAware, EnvironmentAware, SettingsAware): search_method = 'ctime' @@ -92,7 +92,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): """Open the console if the current UI supports that""" self.ui.open_console(string, prompt=prompt, position=position) - def execute_console(self, string=''): + def execute_console(self, string='', wildcards=[]): """Execute a command for the console""" command_name = string.split()[0] try: @@ -100,13 +100,21 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): except: self.notify("Command not found: `%s'" % command_name, bad=True) else: + cmd = cmd_class(string) + if cmd.resolve_macros and _MacroTemplate.delimiter in string: + macros = dict(('any%d'%i, construct_keybinding([char])) \ + for i, char in enumerate(wildcards)) + if 'any0' in macros: + macros['any'] = macros['any0'] + string = self.substitute_macros(string, additional=macros) try: cmd_class(string).execute() except Exception as error: self.notify(error) - def substitute_macros(self, string): - return _MacroTemplate(string).safe_substitute(self._get_macros()) + def substitute_macros(self, string, additional): + return _MacroTemplate(string).safe_substitute(self._get_macros(), + **additional) def _get_macros(self): macros = {} diff --git a/ranger/core/fm.py b/ranger/core/fm.py index 341ab4c7..c292e0c9 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -142,8 +142,8 @@ class FM(Actions, SignalDispatcher): copy('defaults/apps.py', 'apps.py') if which == 'commands' or which == 'all': copy('defaults/commands.py', 'commands.py') - if which == 'keys' or which == 'all': - copy('defaults/keys.py', 'keys.py') + if which == 'rc' or which == 'all': + copy('defaults/rc.conf', 'rc.conf') if which == 'options' or which == 'all': copy('defaults/options.py', 'options.py') if which == 'scope' or which == 'all': @@ -151,7 +151,7 @@ class FM(Actions, SignalDispatcher): os.chmod(self.confpath('scope.sh'), os.stat(self.confpath('scope.sh')).st_mode | stat.S_IXUSR) if which not in \ - ('all', 'apps', 'scope', 'commands', 'keys', 'options'): + ('all', 'apps', 'scope', 'commands', 'rc', 'options'): sys.stderr.write("Unknown config file `%s'\n" % which) def confpath(self, *paths): diff --git a/ranger/core/helper.py b/ranger/core/helper.py index 57102582..1c6460d1 100644 --- a/ranger/core/helper.py +++ b/ranger/core/helper.py @@ -90,7 +90,6 @@ def load_settings(fm, clean): from ranger.core.actions import Actions import ranger.core.shared import ranger.api.commands - import ranger.api.keys if not clean: allow_access_to_confdir(ranger.arg.confdir, True) @@ -115,13 +114,26 @@ def load_settings(fm, clean): fm.apps = apps.CustomApplications() # Load rc.conf - conf = fm.confpath('rc.conf') - if os.access(conf, os.R_OK): - fm.source_cmdlist(conf) - if fm.settings.load_default_rc: - conf = fm.relpath('defaults', 'rc.conf') - if os.access(conf, os.R_OK): - fm.source_cmdlist(conf) + custom_conf = fm.confpath('rc.conf') + default_conf = fm.relpath('defaults', 'rc.conf') + load_default_rc = fm.settings.load_default_rc + + # If load_default_rc is None, think hard: If the users rc.conf is + # about as large as the default rc.conf, he probably copied it as a whole + # and doesn't want to load the default rc.conf anymore. + if load_default_rc is None: + try: + custom_conf_size = os.stat(custom_conf).st_size + except: + load_default_rc = True + else: + default_conf_size = os.stat(default_conf).st_size + load_default_rc = custom_conf_size < default_conf_size - 2048 + + if load_default_rc: + fm.source_cmdlist(default_conf) + if os.access(custom_conf, os.R_OK): + fm.source_cmdlist(custom_conf) # Load plugins try: diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py index 34214ab7..20f40d13 100644 --- a/ranger/defaults/commands.py +++ b/ranger/defaults/commands.py @@ -656,6 +656,7 @@ class eval_(Command): :eval p("Hello World!") """ name = 'eval' + resolve_macros = False def execute(self): if self.arg(1) == '-q': diff --git a/ranger/defaults/options.py b/ranger/defaults/options.py index 8b2395da..fefdf39d 100644 --- a/ranger/defaults/options.py +++ b/ranger/defaults/options.py @@ -34,8 +34,11 @@ of the values stay the same. from ranger.api.options import * # Load the deault rc.conf file? If you've copied it to your configuration -# direcory, then you should deactivate this option. -load_default_rc = True +# direcory, then you should deactivate this option. "None" means guess. +load_default_rc = None + +# How many columns are there, and what are their relative widths? +column_ratios = (1, 3, 4) # Which files should be hidden? Toggle this by typing `zh' or # changing the setting `show_hidden' @@ -78,9 +81,6 @@ draw_bookmark_borders = True # Display the directory name in tabs? dirname_in_tabs = False -# How many columns are there, and what are their relative widths? -column_ratios = (1, 1, 4, 3) - # Enable the mouse support? mouse_enabled = True diff --git a/ranger/defaults/rc.conf b/ranger/defaults/rc.conf index cb90a38f..7743f37a 100644 --- a/ranger/defaults/rc.conf +++ b/ranger/defaults/rc.conf @@ -191,6 +191,15 @@ map zs toggle_option sort_case_insensitive map zv toggle_option use_preview_script map zf console filter +# Bookmarks +map `<bg> draw_bookmarks +map '<bg> draw_bookmarks +map um<bg> draw_bookmarks +map `<any> enter_bookmark %any +map '<any> enter_bookmark %any +map m<any> set_bookmark %any +map um<any> unset_bookmark %any + # Beware. I haven't figured out how to make these keybindings pretty yet: # map +ow shell -d chmod o+w (one mapping for each combination) diff --git a/ranger/ext/keybinding_parser.py b/ranger/ext/keybinding_parser.py index 855904ba..b888032e 100644 --- a/ranger/ext/keybinding_parser.py +++ b/ranger/ext/keybinding_parser.py @@ -21,7 +21,7 @@ def parse_keybinding(obj): Translate a keybinding to a sequence of integers Example: - lol<CR> => (ord('l'), ord('o'), ord('l'), ord('\n')) + lol<CR> => (ord('l'), ord('o'), ord('l'), ord('\\n')) => (108, 111, 108, 10) x<A-Left> => (120, (27, curses.KEY_LEFT)) """ @@ -63,6 +63,18 @@ def parse_keybinding(obj): for c in bracket_content: yield ord(c) +def construct_keybinding(iterable): + """ + Does the reverse of parse_keybinding + """ + result = [] + for c in iterable: + try: + result.append(chr(c)) + except: + result.append("?") + return ''.join(result) + # Arbitrary numbers which are not used with curses.KEY_XYZ DIRKEY = 9001 ANYKEY = 9002 diff --git a/ranger/ext/keybindings.py b/ranger/ext/keybindings.py index d40094d3..0c09de11 100644 --- a/ranger/ext/keybindings.py +++ b/ranger/ext/keybindings.py @@ -58,6 +58,7 @@ class KeyBuffer(object): def clear(self): self.keys = [] + self.wildcards = [] self.pointer = self.keymap self.result = None self.quantifier = None @@ -82,6 +83,7 @@ class KeyBuffer(object): if key in self.pointer: self.pointer = self.pointer[key] elif self.any_key in self.pointer: + self.wildcards.append(key) self.pointer = self.pointer[self.any_key] else: moved = False diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index 0b124be1..3c3bbca8 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -144,13 +144,14 @@ class UI(DisplayableContainer): def press(self, key): keybuffer = self.env.keybuffer self.status.clear_message() - self.fm.hide_bookmarks() keybuffer.add(key) + self.fm.hide_bookmarks() if keybuffer.result is not None: try: - self.fm.execute_console(keybuffer.result) + self.fm.execute_console(keybuffer.result, + wildcards=keybuffer.wildcards) finally: if keybuffer.finished_parsing: keybuffer.clear() diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index c1371040..924f3557 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -23,7 +23,6 @@ import re from collections import deque from . import Widget -from ranger.container.keymap import CommandArgs from ranger.ext.direction import Direction from ranger.ext.utfwidth import uwid, uchars, utf_char_width_ from ranger.container import History diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py index 3c11d38c..4b6a51de 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -21,7 +21,6 @@ import re from . import Widget from ranger.gui import ansi from ranger.ext.direction import Direction -from ranger.container.keymap import CommandArgs class Pager(Widget): source = None diff --git a/ranger/gui/widgets/taskview.py b/ranger/gui/widgets/taskview.py index 2dcace92..805fa270 100644 --- a/ranger/gui/widgets/taskview.py +++ b/ranger/gui/widgets/taskview.py @@ -22,7 +22,6 @@ from collections import deque from . import Widget from ranger.ext.accumulator import Accumulator -from ranger.container.keymap import CommandArgs class TaskView(Widget, Accumulator): old_lst = None |