summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorhut <hut@lavabit.com>2011-09-30 18:12:39 +0200
committerhut <hut@lavabit.com>2011-09-30 18:12:39 +0200
commit8eecaa8a29740a7cadb102df31c0e0c0af0bbe8e (patch)
tree39c7ee231b6d36108685d3938594a559f8dbbedb
parentdedc56b6d68dec95b688e47bbadb6bdea9c39568 (diff)
downloadranger-8eecaa8a29740a7cadb102df31c0e0c0af0bbe8e.tar.gz
ext.keybindings: implemented %any macro
-rw-r--r--ranger/api/commands.py1
-rw-r--r--ranger/api/keys.py131
-rw-r--r--ranger/container/__init__.py2
-rw-r--r--ranger/container/bookmarks.py2
-rw-r--r--ranger/container/settingobject.py2
-rw-r--r--ranger/core/actions.py16
-rw-r--r--ranger/core/fm.py6
-rw-r--r--ranger/core/helper.py28
-rw-r--r--ranger/defaults/commands.py1
-rw-r--r--ranger/defaults/options.py10
-rw-r--r--ranger/defaults/rc.conf9
-rw-r--r--ranger/ext/keybinding_parser.py14
-rw-r--r--ranger/ext/keybindings.py2
-rw-r--r--ranger/gui/ui.py5
-rw-r--r--ranger/gui/widgets/console.py1
-rw-r--r--ranger/gui/widgets/pager.py1
-rw-r--r--ranger/gui/widgets/taskview.py1
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