summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--doc/ranger.pod5
-rw-r--r--ranger/api/commands.py50
-rw-r--r--ranger/core/actions.py8
-rw-r--r--ranger/core/helper.py4
-rw-r--r--ranger/defaults/commands.py63
-rw-r--r--ranger/defaults/rc.conf293
-rw-r--r--ranger/ext/keybinding_parser.py2
-rw-r--r--ranger/ext/keybindings.py13
-rw-r--r--ranger/gui/ui.py2
-rw-r--r--ranger/gui/widgets/console.py3
10 files changed, 407 insertions, 36 deletions
diff --git a/doc/ranger.pod b/doc/ranger.pod
index 5945ad7c..6625c4e4 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -660,10 +660,11 @@ word starts with a `y'.
 
 Edit the current file or the file in the argument.
 
-=item eval I<python_code>
+=item eval [I<-q>] I<python_code>
 
 Evaluates the python code.  `fm' is a reference to the FM instance.  To display
-text, use the function `p'.
+text, use the function `p'.  The result is displayed on the screen unless you
+use the "-q" option.
 
 Examples:
  :eval fm
diff --git a/ranger/api/commands.py b/ranger/api/commands.py
index 130906b6..1e2cf912 100644
--- a/ranger/api/commands.py
+++ b/ranger/api/commands.py
@@ -52,6 +52,18 @@ class CommandContainer(object):
 			except:
 				pass
 
+	def load_commands_from_object(self, obj, filtr):
+		for attribute_name in dir(obj):
+			if attribute_name[0] == '_' or attribute_name not in filtr:
+				continue
+			attribute = getattr(obj, attribute_name)
+			if hasattr(attribute, '__call__'):
+				cmd = type(attribute_name, (FunctionCommand, ), dict())
+				cmd._based_function = attribute
+				cmd._function_name = attribute.__name__
+				cmd._object_name = obj.__class__.__name__
+				self.commands[attribute_name] = cmd
+
 	def get_command(self, name, abbrev=True):
 		if abbrev:
 			lst = [cls for cmd, cls in self.commands.items() \
@@ -240,3 +252,41 @@ class Command(FileManagerAware):
 			# more than one result. append no slash, so the user can
 			# manually type in the slash to advance into that directory
 			return (line.start(1) + join(rel_dirname, name) for name in names)
+
+
+class FunctionCommand(Command):
+	_based_function = None
+	_object_name = ""
+	_function_name = "unknown"
+	def execute(self):
+		if not self._based_function:
+			return
+		if len(self.args) == 1:
+			return self._based_function()
+
+		args, keywords = list(), dict()
+		for arg in self.args[1:]:
+			equal_sign = arg.find("=")
+			value = arg if (equal_sign is -1) else arg[equal_sign + 1:]
+			try:
+				value = int(value)
+			except:
+				if value in ('True', 'False'):
+					value = (value == 'True')
+				else:
+					try:
+						value = float(value)
+					except:
+						pass
+
+			if equal_sign == -1:
+				args.append(value)
+			else:
+				keywords[arg[:equal_sign]] = value
+
+		try:
+			return self._based_function(*args, **keywords)
+		except TypeError:
+			self.fm.notify("Bad arguments for %s.%s: %s, %s" %
+					(self._object_name, self._function_name,
+						repr(args), repr(keywords)), bad=True)
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 3427af7f..44b5eac8 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -269,7 +269,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 
 	def history_go(self, relative):
 		"""Move back and forth in the history"""
-		self.env.history_go(relative)
+		self.env.history_go(int(relative))
 
 	def scroll(self, relative):
 		"""Scroll down by <relative> lines"""
@@ -329,7 +329,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	def hint(self, text):
 		self.ui.hint(text)
 
-	def toggle_boolean_option(self, string):
+	def toggle_option(self, string):
 		"""Toggle a boolean option named <string>"""
 		if isinstance(self.env.settings[string], bool):
 			self.env.settings[string] ^= True
@@ -418,9 +418,9 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 			except:
 				return False
 		self.env.last_search = text
-		self.search(order='search', offset=offset)
+		self.search_next(order='search', offset=offset)
 
-	def search(self, order=None, offset=1, forward=True):
+	def search_next(self, order=None, offset=1, forward=True):
 		original_order = order
 		if self.search_forward:
 			direction = bool(forward)
diff --git a/ranger/core/helper.py b/ranger/core/helper.py
index 56b8e5ad..a02cb0c0 100644
--- a/ranger/core/helper.py
+++ b/ranger/core/helper.py
@@ -87,6 +87,7 @@ def parse_arguments():
 
 
 def load_settings(fm, clean):
+	from ranger.core.actions import Actions
 	import ranger.core.shared
 	import ranger.api.commands
 	import ranger.api.keys
@@ -95,6 +96,7 @@ def load_settings(fm, clean):
 
 		# Load commands
 		comcont = ranger.api.commands.CommandContainer()
+		comcont.load_commands_from_object(fm, dir(Actions))
 		ranger.api.commands.alias = comcont.alias
 		try:
 			import commands
@@ -149,8 +151,6 @@ def load_settings(fm, clean):
 	else:
 		comcont = ranger.api.commands.CommandContainer()
 		ranger.api.commands.alias = comcont.alias
-		keymanager = ranger.core.shared.EnvironmentAware.env.keymanager
-		ranger.api.keys.keymanager = keymanager
 		from ranger.defaults import commands, keys, apps
 		comcont.load_commands_from_module(commands)
 		fm.commands = comcont
diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py
index a40d447d..34214ab7 100644
--- a/ranger/defaults/commands.py
+++ b/ranger/defaults/commands.py
@@ -62,19 +62,26 @@ from ranger.core.runner import ALLOWED_FLAGS
 alias('e', 'edit')
 alias('q', 'quit')
 alias('q!', 'quitall')
+alias('console!', 'console_bang')
 alias('qall', 'quitall')
 
 class cd(Command):
 	"""
-	:cd <dirname>
+	:cd [-r] <dirname>
 
 	The cd command changes the directory.
 	The command 'cd -' is equivalent to typing ``.
+	Using the option "-r" will get you to the real path.
 	"""
 
 	def execute(self):
-		line = parse(self.line)
-		destination = line.rest(1)
+		if self.arg(1) == '-r':
+			import os.path
+			self.shift()
+			destination = os.path.realpath(self.rest(1))
+		else:
+			destination = self.rest(1)
+
 		if not destination:
 			destination = '~'
 
@@ -137,6 +144,16 @@ class cd(Command):
 			return (line.start(1) + join(rel_dirname, dirname) for dirname in dirnames)
 
 
+class chain(Command):
+	"""
+	:chain <command1>; <command2>; ...
+	Calls multiple commands at once, separated by semicolons.
+	"""
+	def execute(self):
+		for command in self.rest(1).split(";"):
+			self.fm.execute_console(command)
+
+
 class search(Command):
 	def execute(self):
 		self.fm.search_file(parse(self.line).rest(1), regexp=True)
@@ -492,6 +509,33 @@ class mark(Command):
 		self.fm.ui.need_redraw = True
 
 
+class console(Command):
+	"""
+	:console <command>
+
+	Open the console with the given command.
+	"""
+	def execute(self):
+		position = None
+		if self.arg(1)[0:2] == '-p':
+			try:
+				position = int(self.arg(1)[2:])
+				self.shift()
+			except:
+				pass
+		self.fm.open_console(self.rest(1), position=position)
+
+
+class console_bang(Command):
+	"""
+	:console! <command>
+
+	Execute the given command in the console.
+	"""
+	def execute(self):
+		self.fm.execute_console(self.rest(1))
+
+
 class load_copy_buffer(Command):
 	"""
 	:load_copy_buffer
@@ -600,7 +644,7 @@ class edit(Command):
 
 class eval_(Command):
 	"""
-	:eval <python code>
+	:eval [-q] <python code>
 
 	Evaluates the python code.
 	`fm' is a reference to the FM instance.
@@ -614,8 +658,15 @@ class eval_(Command):
 	name = 'eval'
 
 	def execute(self):
-		code = parse(self.line).rest(1)
+		if self.arg(1) == '-q':
+			code = self.rest(2)
+			quiet = True
+		else:
+			code = self.rest(1)
+			quiet = False
+		import ranger
 		fm = self.fm
+		cmd = self.fm.execute_console
 		p = fm.notify
 		try:
 			try:
@@ -623,7 +674,7 @@ class eval_(Command):
 			except SyntaxError:
 				exec(code)
 			else:
-				if result:
+				if result and not quiet:
 					p(result)
 		except Exception as err:
 			p(err)
diff --git a/ranger/defaults/rc.conf b/ranger/defaults/rc.conf
index 59401e3b..cb90a38f 100644
--- a/ranger/defaults/rc.conf
+++ b/ranger/defaults/rc.conf
@@ -1,21 +1,280 @@
-# VIM
-map x break_shit
-map gg eval fm.move(to=0)
-map G  eval fm.move(to=-1)
-map : eval fm.open_console()
-map j eval fm.move(down=1)
-map k eval fm.move(up=1)
-map h eval fm.move(left=1)
-map l eval fm.move(right=1)
-map q quit
+# ===================================================================
+# == Define keys for the browser
+# ===================================================================
+
+# Basic
 map Q quit
+map q quit
+map ZZ quit
+map ZQ quit
 
-cmap <ESC> quit
+map R reload_cwd
+map <C-r> reset
+map <C-l> redraw_window
+map H history_go -1
+map L history_go 1
+map <C-c> abort
 
-map R eval fm.reload_cwd()
-map <c-r> eval fm.reset()
-map <c-l> eval fm.redraw_window()
-map H eval fm.history_go(-1)
-map L eval fm.history_go(1)
-map <c-c> eval fm.abort()
+map i display_file
 map ? help
+map W display_log
+map w eval fm.ui.open_taskview()
+map S shell $SHELL
+
+map :  console
+map ;  console
+map !  console shell 
+map @  console -p6 shell  %s
+map #  console shell -p 
+map s  console shell 
+map r  console open_with 
+map f  console find 
+map cd console cd 
+
+# Tagging / Marking
+map t tag_toggle
+map T tag_remove
+
+# VIM-like
+map gg    move to=0
+map G     move to=-1
+map j     move down=1
+map k     move up=1
+map h     move left=1
+map l     move right=1
+map J     move down=0.5 pages=True
+map K     move up=0.5   pages=True
+map <C-d> move down=0.5 pages=True
+map <C-u> move up=0.5   pages=True
+map <C-f> move down=1   pages=True
+map <C-b> move up=1     pages=True
+
+# For the nostalgics: Midnight Commander bindings
+map <F1> help
+map <F3> display_file
+map <F4> edit
+map <F5> copy
+map <F6> cut
+map <F7> console mkdir 
+map <F8> console delete seriously? 
+map <F10> exit
+
+# In case you work on a keyboard with dvorak layout
+map <UP>       move down=1
+map <LEFT>     move up=1
+map <LEFT>     move left=1
+map <RIGHT>    move right=1
+map <HOME>     move to=0
+map <END>      move to=-1
+map <PAGEDOWN> move down=1   pages=True
+map <PAGEUP>   move up=1     pages=True
+map <CR>       move right=1
+map <DEL>      console delete
+map <INS>      console touch
+
+# Jumping around
+map ]     move_parent 1
+map [     move_parent -1
+map }     traverse
+
+map gh cd ~
+map ge cd /etc
+map gu cd /usr
+map gd cd /dev
+map gl cd -r .
+map gL cd -r %f
+map go cd /opt
+map gv cd /var
+map gm cd /media
+map gM cd /mnt
+map gs cd /srv
+map gR eval fm.cd(ranger.RANGERDIR)
+
+# External Programs
+map du shell -p du --max-depth=1 -h --apparent-size
+map dU shell -p du --max-depth=1 -h --apparent-size | sort -rh
+map yp shell -d echo -n %d/%f | xsel -i
+map yd shell -d echo -n %d    | xsel -i
+map yn shell -d echo -n %%f   | xsel -i
+
+# Filesystem Operations
+map =  chmod
+
+map cw console rename 
+map A  eval fm.open_console('rename ' + fm.env.cf.basename)
+map I  eval fm.open_console('rename ' + fm.env.cf.basename, position=7)
+
+map pp paste
+map po paste overwrite=True
+map pl paste_symlink relative=False
+map pL paste_symlink relative=True
+map phl paste_hardlink
+
+map dd cut
+map ud uncut
+map da cut mode=add
+map dr cut mode=remove
+
+map yy copy
+map uy uncut
+map ya copy mode=add
+map yr copy mode=remove
+
+# Temporary workarounds
+map dgg eval fm.cut(dirarg=dict(to=0))
+map dG  eval fm.cut(dirarg=dict(to=-1))
+map dj  eval fm.cut(dirarg=dict(down=1))
+map dk  eval fm.cut(dirarg=dict(up=1))
+map ygg eval fm.copy(dirarg=dict(to=0))
+map yG  eval fm.copy(dirarg=dict(to=-1))
+map yj  eval fm.copy(dirarg=dict(down=1))
+map yk  eval fm.copy(dirarg=dict(up=1))
+
+# Searching
+map /  console search 
+map n  search_next
+map N  search_next forward=False
+map ct search_next order=tag
+map cs search_next order=size
+map ci search_next order=mimetype
+map cc search_next order=ctime
+map cm search_next order=mtime
+map ca search_next order=atime
+
+# Tabs
+map <C-n>   chain tab_new; cd ~
+map <C-w>   tab_close
+map <TAB>   tab_move 1
+map <S-TAB> tab_move -1
+map gn      chain tab_new; cd ~
+map gc      tab_close
+map gt      tab_move 1
+map gT      tab_move -1
+map <a-1>   tab_open 1
+map <a-2>   tab_open 2
+map <a-3>   tab_open 3
+map <a-4>   tab_open 4
+map <a-5>   tab_open 5
+map <a-6>   tab_open 6
+map <a-7>   tab_open 7
+map <a-8>   tab_open 8
+map <a-9>   tab_open 9
+
+# Sorting
+map or eval fm.settings.sort_reverse ^= True
+map os chain set sort=size;      set sort_reverse=False
+map ob chain set sort=basename;  set sort_reverse=False
+map on chain set sort=natural;   set sort_reverse=False
+map om chain set sort=mtime;     set sort_reverse=False
+map oc chain set sort=ctime;     set sort_reverse=False
+map oa chain set sort=atime;     set sort_reverse=False
+map ot chain set sort=type;      set sort_reverse=False
+
+map oS chain set sort=size;      set sort_reverse=True
+map oB chain set sort=basename;  set sort_reverse=True
+map oN chain set sort=natural;   set sort_reverse=True
+map oM chain set sort=mtime;     set sort_reverse=True
+map oC chain set sort=ctime;     set sort_reverse=True
+map oA chain set sort=atime;     set sort_reverse=True
+map oT chain set sort=type;      set sort_reverse=True
+
+# Settings
+map zc    toggle_option collapse_preview
+map zd    toggle_option sort_directories_first
+map zh    toggle_option show_hidden
+map <C-h> toggle_option show_hidden
+map zi    toggle_option flushinput
+map zm    toggle_option mouse_enabled
+map zp    toggle_option preview_files
+map zP    toggle_option preview_directories
+map zs    toggle_option sort_case_insensitive
+map zv    toggle_option use_preview_script
+map zf    console filter 
+
+# 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)
+eval import itertools; [cmd("map +%s%s shell -d chmod %s+%s %%s" % (mode+mode)) for mode in itertools.product("ugoa", "rwxXst")]
+
+# map -ow shell -d chmod o+w (one mapping for each combination)
+eval import itertools; [cmd("map -%s%s shell -d chmod %s-%s %%s" % (mode+mode)) for mode in itertools.product("ugoa", "rwxXst")]
+
+# map "x tag_toggle tag=x (one mapping for each key)
+eval -q [cmd('map "%s eval fm.tag_toggle(tag=chr(%d))' % (chr(s),s)) for s in range(33, 127)]
+
+
+# ===================================================================
+# == Define keys for the console
+# ===================================================================
+
+# Basic
+cmap <tab>   eval fm.ui.console.tab()
+cmap <s-tab> eval fm.ui.console.tab(-1)
+cmap <C-c>   eval fm.ui.console.close()
+cmap <C-d>   eval fm.ui.console.close()
+cmap <ESC>   eval fm.ui.console.close()
+cmap <CR>    eval fm.ui.console.execute()
+cmap <C-j>   eval fm.ui.console.execute()
+cmap <C-l>   redraw_window
+
+# This special expression allows typing in numerals:
+cmap <allow_quantifiers> false
+
+# Move around
+cmap <left>  eval fm.ui.console.move(left=1)
+cmap <right> eval fm.ui.console.move(right=1)
+cmap <home>  eval fm.ui.console.move(right=0, absolute=True)
+cmap <end>   eval fm.ui.console.move(right=-1, absolute=True)
+cmap <up>    eval fm.ui.console.history_move(-1)
+cmap <down>  eval fm.ui.console.history_move(1)
+
+# And of course the emacs way
+cmap <C-b>   eval fm.ui.console.move(left=1)
+cmap <C-f>   eval fm.ui.console.move(right=1)
+cmap <C-a>   eval fm.ui.console.move(right=0, absolute=True)
+cmap <C-e>   eval fm.ui.console.move(right=-1, absolute=True)
+cmap <C-p>   eval fm.ui.console.history_move(-1)
+cmap <C-n>   eval fm.ui.console.history_move(1)
+
+# Line Editing
+# Note: There are multiple ways to express backspaces.  <backspace> (code 263)
+# and <backspace2> (code 127).  To be sure, use both.
+cmap <backspace>  eval fm.ui.console.delete(-1)
+cmap <backspace2> eval fm.ui.console.delete(-1)
+cmap <delete>     eval fm.ui.console.delete(0)
+cmap <C-h>        eval fm.ui.console.delete(-1)
+cmap <C-d>        eval fm.ui.console.delete(0)
+cmap <C-w>        eval fm.ui.console.delete_word()
+cmap <C-k>        eval fm.ui.console.delete_rest(1)
+cmap <C-u>        eval fm.ui.console.delete_rest(-1)
+cmap <C-y>        eval fm.ui.console.paste()
+
+
+# ===================================================================
+# == Taskview Keybindings
+# ===================================================================
+
+# Movement
+tmap j eval -q fm.ui.taskview.move(down=1)
+tmap k eval -q fm.ui.taskview.move(up=1)
+tmap gg eval -q fm.ui.taskview.move(down=0, absolute=True)
+tmap G eval -q fm.ui.taskview.move(down=-1, absolute=True)
+tmap <down> eval -q fm.ui.taskview.move(down=1)
+tmap <up> eval -q fm.ui.taskview.move(up=1)
+tmap <home> eval -q fm.ui.taskview.move(down=0, absolute=True)
+tmap <end> eval -q fm.ui.taskview.move(down=-1, absolute=True)
+
+# Changing priority and deleting tasks
+tmap J eval -q fm.ui.taskview.task_move(-1)
+tmap K eval -q fm.ui.taskview.task_move(0)
+tmap dd eval -q fm.ui.taskview.task_remove()
+tmap <pagedown> eval -q fm.ui.taskview.task_move(-1)
+tmap <pageup> eval -q fm.ui.taskview.task_move(0)
+tmap <delete> eval -q fm.ui.taskview.task_remove()
+
+# A bunch of aliases for closing
+tmap w eval -q fm.ui.close_taskview()
+tmap q eval -q fm.ui.close_taskview()
+tmap Q eval -q fm.ui.close_taskview()
+tmap <esc> eval -q fm.ui.close_taskview()
+tmap <c-c> eval -q fm.ui.close_taskview()
diff --git a/ranger/ext/keybinding_parser.py b/ranger/ext/keybinding_parser.py
index bfd1b6d4..855904ba 100644
--- a/ranger/ext/keybinding_parser.py
+++ b/ranger/ext/keybinding_parser.py
@@ -68,11 +68,13 @@ DIRKEY = 9001
 ANYKEY = 9002
 PASSIVE_ACTION = 9003
 ALT_KEY = 9004
+QUANT_KEY = 9005
 
 very_special_keys = {
 	'dir': DIRKEY,
 	'any': ANYKEY,
 	'bg': PASSIVE_ACTION,
+	'allow_quantifiers': QUANT_KEY,
 }
 
 special_keys = {
diff --git a/ranger/ext/keybindings.py b/ranger/ext/keybindings.py
index 36b13ad0..d40094d3 100644
--- a/ranger/ext/keybindings.py
+++ b/ranger/ext/keybindings.py
@@ -14,7 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from ranger.ext.keybinding_parser import (parse_keybinding,
-	ANYKEY, PASSIVE_ACTION)
+	ANYKEY, PASSIVE_ACTION, QUANT_KEY)
 
 digits = set(range(ord('0'), ord('9')+1))
 
@@ -48,8 +48,9 @@ class KeyMaps(dict):
 
 
 class KeyBuffer(object):
-	any_key     = ANYKEY
-	passive_key = PASSIVE_ACTION
+	any_key        = ANYKEY
+	passive_key    = PASSIVE_ACTION
+	quantifier_key = QUANT_KEY
 
 	def __init__(self, keymap=None):
 		self.keymap = keymap
@@ -64,6 +65,10 @@ class KeyBuffer(object):
 		self.finished_parsing = False
 		self.parse_error = False
 
+		if self.keymap and self.quantifier_key in self.keymap:
+			if self.keymap[self.quantifier_key] == 'false':
+				self.finished_parsing_quantifier = True
+
 	def add(self, key):
 		self.keys.append(key)
 		if not self.finished_parsing_quantifier and key in digits:
@@ -93,4 +98,4 @@ class KeyBuffer(object):
 				self.parse_error = True
 
 	def __str__(self):
-		return repr(self.keys)
+		return "".join("{0:c}".format(c) for c in self.keys)
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index a2c5c9a1..0b124be1 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -156,6 +156,8 @@ class UI(DisplayableContainer):
 					keybuffer.clear()
 		elif keybuffer.finished_parsing:
 			keybuffer.clear()
+			return False
+		return True
 
 	def handle_keys(self, *keys):
 		for key in keys:
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index f50a5f3c..c1371040 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -152,7 +152,8 @@ class Console(Widget):
 
 	def press(self, key):
 		self.env.keymaps.use_keymap('console')
-		self.fm.ui.press(key)
+		if not self.fm.ui.press(key):
+			self.type_key(key)
 
 	def type_key(self, key):
 		self.tab_deque = None