summary refs log tree commit diff stats
path: root/ranger/core/actions.py
diff options
context:
space:
mode:
Diffstat (limited to 'ranger/core/actions.py')
-rw-r--r--ranger/core/actions.py348
1 files changed, 239 insertions, 109 deletions
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 163cc3d6..e9d89061 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
+# Copyright (C) 2009, 2010, 2011  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
@@ -13,10 +13,12 @@
 # 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 codecs
 import os
 import re
 import shutil
 import string
+import tempfile
 from os.path import join, isdir, realpath
 from os import link, symlink, getcwd
 from inspect import cleandoc
@@ -24,21 +26,21 @@ 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 key_to_string, construct_keybinding
 from ranger.ext.shell_escape import shell_quote
-from ranger import fsobject
 from ranger.core.shared import FileManagerAware, EnvironmentAware, \
 		SettingsAware
 from ranger.fsobject import File
 from ranger.core.loader import CommandLoader
 
+MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>"
+
 class _MacroTemplate(string.Template):
 	"""A template for substituting macros in commands"""
-	delimiter = '%'
-	idpattern = '\d?[a-z]'
+	delimiter = ranger.MACRO_DELIMITER
 
 class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	search_method = 'ctime'
-	search_forward = False
 
 	# --------------------------
 	# -- Basic Commands
@@ -52,7 +54,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		"""Reset the filemanager, clearing the directory buffer"""
 		old_path = self.env.cwd.path
 		self.previews = {}
-		self.env.garbage_collect(-1)
+		self.env.garbage_collect(-1, self.tabs)
 		self.enter_dir(old_path)
 
 	def reload_cwd(self):
@@ -70,8 +72,20 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 			bad = True
 		text = str(text)
 		self.log.appendleft(text)
-		if hasattr(self.ui, 'notify'):
-			self.ui.notify(text, duration=duration, bad=bad)
+		if self.ui and self.ui.is_on:
+			self.ui.status.notify("  ".join(text.split("\n")),
+					duration=duration, bad=bad)
+		else:
+			print(text)
+
+	def abort(self):
+		try:
+			item = self.loader.queue[0]
+		except:
+			self.notify("Type Q or :quit<Enter> to exit ranger")
+		else:
+			self.notify("Aborting: " + item.get_description())
+			self.loader.remove(index=0)
 
 	def redraw_window(self):
 		"""Redraw the window"""
@@ -79,41 +93,78 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 
 	def open_console(self, string='', prompt=None, position=None):
 		"""Open the console if the current UI supports that"""
-		if hasattr(self.ui, 'open_console'):
-			self.ui.open_console(string, prompt=prompt, position=position)
+		self.ui.open_console(string, prompt=prompt, position=position)
 
-	def execute_console(self, string=''):
+	def execute_console(self, string='', wildcards=[], quantifier=None):
 		"""Execute a command for the console"""
-		self.open_console(string=string)
-		self.ui.console.line = string
-		self.ui.console.execute()
-
-	def substitute_macros(self, string):
-		return _MacroTemplate(string).safe_substitute(self._get_macros())
+		command_name = string.split()[0]
+		cmd_class = self.commands.get_command(command_name, abbrev=False)
+		if cmd_class is None:
+			self.notify("Command not found: `%s'" % command_name, bad=True)
+			return
+		cmd = cmd_class(string)
+		if cmd.resolve_macros and _MacroTemplate.delimiter in string:
+			macros = dict(('any%d'%i, key_to_string(char)) \
+					for i, char in enumerate(wildcards))
+			if 'any0' in macros:
+				macros['any'] = macros['any0']
+			try:
+				string = self.substitute_macros(string, additional=macros,
+						escape=cmd.escape_macros_for_shell)
+			except ValueError as e:
+				if ranger.arg.debug:
+					raise
+				else:
+					return self.notify(e)
+		try:
+			cmd_class(string, quantifier=quantifier).execute()
+		except Exception as e:
+			if ranger.arg.debug:
+				raise
+			else:
+				self.notify(e)
+
+	def substitute_macros(self, string, additional=dict(), escape=False):
+		macros = self._get_macros()
+		macros.update(additional)
+		if escape:
+			for key, value in macros.items():
+				if isinstance(value, list):
+					macros[key] = " ".join(shell_quote(s) for s in value)
+				elif value != MACRO_FAIL:
+					macros[key] = shell_quote(value)
+		else:
+			for key, value in macros.items():
+				if isinstance(value, list):
+					macros[key] = " ".join(value)
+		result = _MacroTemplate(string).safe_substitute(macros)
+		if MACRO_FAIL in result:
+			raise ValueError("Could not apply macros to `%s'" % string)
+		return result
 
 	def _get_macros(self):
 		macros = {}
 
+		macros['rangerdir'] = ranger.RANGERDIR
+
 		if self.fm.env.cf:
-			macros['f'] = shell_quote(self.fm.env.cf.basename)
+			macros['f'] = self.fm.env.cf.basename
 		else:
-			macros['f'] = ''
+			macros['f'] = MACRO_FAIL
 
-		macros['s'] = ' '.join(shell_quote(fl.basename) \
-				for fl in self.fm.env.get_selection())
+		macros['s'] = [fl.basename for fl in self.fm.env.get_selection()]
 
-		macros['c'] = ' '.join(shell_quote(fl.path)
-				for fl in self.fm.env.copy)
+		macros['c'] = [fl.path for fl in self.fm.env.copy]
 
-		macros['t'] = ' '.join(shell_quote(fl.basename)
-				for fl in self.fm.env.cwd.files
-				if fl.realpath in self.fm.tags)
+		macros['t'] = [fl.basename for fl in self.fm.env.cwd.files
+				if fl.realpath in (self.fm.tags or [])]
 
 		if self.fm.env.cwd:
-			macros['d'] = shell_quote(self.fm.env.cwd.path)
+			macros['d'] = self.fm.env.cwd.path
 		else:
 			macros['d'] = '.'
 
+
 		# define d/f/s macros for each tab
 		for i in range(1,10):
 			try:
@@ -122,10 +173,12 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 				continue
 			tab_dir = self.fm.env.get_directory(tab_dir_path)
 			i = str(i)
-			macros[i + 'd'] = shell_quote(tab_dir_path)
-			macros[i + 'f'] = shell_quote(tab_dir.pointed_obj.path)
-			macros[i + 's'] = ' '.join(shell_quote(fl.path)
-				for fl in tab_dir.get_selection())
+			macros[i + 'd'] = tab_dir_path
+			macros[i + 's'] = [fl.path for fl in tab_dir.get_selection()]
+			if tab_dir.pointed_obj:
+				macros[i + 'f'] = tab_dir.pointed_obj.path
+			else:
+				macros[i + 'f'] = MACRO_FAIL
 
 		# define D/F/S for the next tab
 		found_current_tab = False
@@ -143,13 +196,29 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 			next_tab_path = self.fm.tabs[first_tab]
 		next_tab = self.fm.env.get_directory(next_tab_path)
 
-		macros['D'] = shell_quote(next_tab)
-		macros['F'] = shell_quote(next_tab.pointed_obj.path)
-		macros['S'] = ' '.join(shell_quote(fl.path)
-			for fl in next_tab.get_selection())
+		macros['D'] = next_tab
+		if next_tab.pointed_obj:
+			macros['F'] = next_tab.pointed_obj.path
+		else:
+			macros['F'] = MACRO_FAIL
+		macros['S'] = [fl.path for fl in next_tab.get_selection()]
 
 		return macros
 
+	def source(self, filename):
+		filename = os.path.expanduser(filename)
+		for line in open(filename, 'r'):
+			line = line.rstrip("\r\n")
+			if line.startswith("#") or not line.strip():
+				continue
+			try:
+				self.execute_console(line)
+			except Exception as e:
+				if ranger.arg.debug:
+					raise
+				else:
+					self.notify('Error in line `%s\':\n  %s' %
+							(line, str(e)), bad=True)
 
 	def execute_file(self, files, **kw):
 		"""Execute a file.
@@ -158,6 +227,11 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		mode is a positive integer.
 		Both flags and mode specify how the program is run."""
 
+		# ranger can act as a file chooser when running with --choosefile=...
+		if ranger.arg.choosefile:
+			open(ranger.arg.choosefile, 'w').write(self.fm.env.cf.path)
+			raise SystemExit()
+
 		if isinstance(files, set):
 			files = list(files)
 		elif type(files) not in (list, tuple):
@@ -226,23 +300,26 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 						pagesize=self.ui.browser.hei)
 				cwd.move(to=newpos)
 
-	def move_parent(self, n):
+	def move_parent(self, n, narg=None):
+		if narg is not None:
+			n *= narg
 		parent = self.env.at_level(-1)
-		if parent.pointer + n < 0:
-			n = 0 - parent.pointer
-		try:
-			self.env.enter_dir(parent.files[parent.pointer+n])
-		except IndexError:
-			pass
+		if parent is not None:
+			if parent.pointer + n < 0:
+				n = 0 - parent.pointer
+			try:
+				self.env.enter_dir(parent.files[parent.pointer+n])
+			except IndexError:
+				pass
 
 	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"""
-		if hasattr(self.ui, 'scroll'):
-			self.ui.scroll(relative)
+		if self.ui.browser and self.ui.browser.main_column:
+			self.ui.browser.main_column.scroll(relative)
 			self.env.cf = self.env.cwd.pointed_obj
 
 	def enter_dir(self, path, remember=False, history=True):
@@ -281,6 +358,24 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	# -- Shortcuts / Wrappers
 	# --------------------------
 
+	def pager_move(self, narg=None, **kw):
+		self.ui.browser.pager.move(narg=narg, **kw)
+
+	def taskview_move(self, narg=None, **kw):
+		self.ui.taskview.move(narg=narg, **kw)
+
+	def pager_close(self):
+		if self.ui.pager.visible:
+			self.ui.close_pager()
+		if self.ui.browser.pager.visible:
+			self.ui.close_embedded_pager()
+
+	def taskview_open(self):
+		self.ui.open_taskview()
+
+	def taskview_close(self):
+		self.ui.close_taskview()
+
 	def execute_command(self, cmd, **kw):
 		return self.run(cmd, **kw)
 
@@ -294,10 +389,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 			return
 		self.execute_file(file, app = 'editor')
 
-	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
@@ -319,7 +411,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		except:
 			pass
 
-	def mark(self, all=False, toggle=False, val=None, movedown=None, narg=1):
+	def mark_files(self, all=False, toggle=False, val=None, movedown=None, narg=1):
 		"""
 		A wrapper for the directory.mark_xyz functions.
 
@@ -360,10 +452,8 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		if movedown:
 			self.move(down=narg)
 
-		if hasattr(self.ui, 'redraw_main_column'):
-			self.ui.redraw_main_column()
-		if hasattr(self.ui, 'status'):
-			self.ui.status.need_redraw = True
+		self.ui.redraw_main_column()
+		self.ui.status.need_redraw = True
 
 	def mark_in_direction(self, val=True, dirarg=None):
 		cwd = self.env.cwd
@@ -386,14 +476,10 @@ 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)
-		else:
-			direction = not bool(forward)
 
 		if order is None:
 			order = self.search_method
@@ -437,7 +523,6 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	def set_search_method(self, order, forward=True):
 		if order in ('search', 'tag', 'size', 'mimetype', 'ctime'):
 			self.search_method = order
-			self.search_forward = forward
 
 	# --------------------------
 	# -- Tags
@@ -464,8 +549,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		if movedown:
 			self.move(down=1)
 
-		if hasattr(self.ui, 'redraw_main_column'):
-			self.ui.redraw_main_column()
+		self.ui.redraw_main_column()
 
 	def tag_remove(self, paths=None, movedown=None):
 		self.tag_toggle(paths=paths, value=False, movedown=movedown)
@@ -482,10 +566,10 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		"""Enter the bookmark with the name <key>"""
 		try:
 			self.bookmarks.update_if_outdated()
-			destination = self.bookmarks[key]
+			destination = self.bookmarks[str(key)]
 			cwd = self.env.cwd
 			if destination.path != cwd.path:
-				self.bookmarks.enter(key)
+				self.bookmarks.enter(str(key))
 				self.bookmarks.remember(cwd)
 		except KeyError:
 			pass
@@ -493,12 +577,12 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	def set_bookmark(self, key):
 		"""Set the bookmark with the name <key> to the current directory"""
 		self.bookmarks.update_if_outdated()
-		self.bookmarks[key] = self.env.cwd
+		self.bookmarks[str(key)] = self.env.cwd
 
 	def unset_bookmark(self, key):
 		"""Delete the bookmark with the name <key>"""
 		self.bookmarks.update_if_outdated()
-		self.bookmarks.delete(key)
+		self.bookmarks.delete(str(key))
 
 	def draw_bookmarks(self):
 		self.ui.browser.draw_bookmarks = True
@@ -512,9 +596,6 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	# These commands open the built-in pager and set specific sources.
 
 	def display_command_help(self, console_widget):
-		if not hasattr(self.ui, 'open_pager'):
-			return
-
 		try:
 			command = console_widget._get_cmd_class()
 		except:
@@ -534,40 +615,17 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		lines = cleandoc(command.__doc__).split('\n')
 		pager.set_source(lines)
 
-	def display_help(self, topic='index', narg=None):
-		if not hasattr(self.ui, 'open_pager'):
-			return
-
-		from ranger.help import get_help, get_help_by_index
-
-		scroll_to_line = 0
-		if narg is not None:
-			chapter, subchapter = int(str(narg)[0]), str(narg)[1:]
-			help_text = get_help_by_index(chapter)
-			lines = help_text.split('\n')
-			if chapter:
-				chapternumber = str(chapter) + '.' + subchapter + '. '
-				skip_to_content = True
-				for line_number, line in enumerate(lines):
-					if skip_to_content:
-						if line[:10] == '==========':
-							skip_to_content = False
-					else:
-						if line.startswith(chapternumber):
-							scroll_to_line = line_number
-		else:
-			help_text = get_help(topic)
-			lines = help_text.split('\n')
-
-		pager = self.ui.open_pager()
-		pager.set_source(lines)
-		pager.markup = 'help'
-		pager.move(down=scroll_to_line)
+	def display_help(self):
+		manualpath = self.relpath('../doc/ranger.1')
+		if os.path.exists(manualpath):
+			process = self.run(['man', manualpath])
+			if process.poll() != 16:
+				return
+		process = self.run(['man', 'ranger'])
+		if process.poll() == 16:
+			self.notify("Could not find manpage.", bad=True)
 
 	def display_log(self):
-		if not hasattr(self.ui, 'open_pager'):
-			return
-
 		pager = self.ui.open_pager()
 		if self.log:
 			pager.set_source(["Message Log:"] + list(self.log))
@@ -575,8 +633,6 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 			pager.set_source(["Message Log:", "No messages!"])
 
 	def display_file(self):
-		if not hasattr(self.ui, 'open_embedded_pager'):
-			return
 		if not self.env.cf or not self.env.cf.is_file:
 			return
 
@@ -631,7 +687,15 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 						data[(-1, -1)] = None
 						data['foundpreview'] = False
 					elif exit == 2:
-						data[(-1, -1)] = open(path, 'r').read(1024 * 32)
+						f = codecs.open(path, 'r', errors='ignore')
+						try:
+							data[(-1, -1)] = f.read(1024 * 32)
+						except UnicodeDecodeError:
+							f.close()
+							f = codecs.open(path, 'r', encoding='latin-1',
+									errors='ignore')
+							data[(-1, -1)] = f.read(1024 * 32)
+						f.close()
 					else:
 						data[(-1, -1)] = None
 					if self.env.cf.realpath == path:
@@ -654,7 +718,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 				return found
 		else:
 			try:
-				return open(path, 'r')
+				return codecs.open(path, 'r', errors='ignore')
 			except:
 				return None
 
@@ -694,10 +758,10 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		if newtab != self.current_tab:
 			self.tab_open(newtab)
 
-	def tab_new(self):
+	def tab_new(self, path=None):
 		for i in range(1, 10):
 			if not i in self.tabs:
-				self.tab_open(i)
+				self.tab_open(i, path)
 				break
 
 	def _get_tab_list(self):
@@ -708,6 +772,71 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		self.tabs[self.current_tab] = self.env.cwd.path
 
 	# --------------------------
+	# -- Overview of internals
+	# --------------------------
+
+	def dump_keybindings(self, *contexts):
+		if not contexts:
+			contexts = 'browser', 'console', 'pager', 'taskview'
+
+		temporary_file = tempfile.NamedTemporaryFile()
+		def write(string):
+			temporary_file.write(string.encode('utf-8'))
+
+		def recurse(before, pointer):
+			for key, value in pointer.items():
+				keys = before + [key]
+				if isinstance(value, dict):
+					recurse(keys, value)
+				else:
+					write("%12s %s\n" % (construct_keybinding(keys), value))
+
+		for context in contexts:
+			write("Keybindings in `%s'\n" % context)
+			if context in self.env.keymaps:
+				recurse([], self.env.keymaps[context])
+			else:
+				write("  None\n")
+			write("\n")
+
+		temporary_file.flush()
+		self.run(app='pager', files=[File(temporary_file.name)])
+
+	def dump_commands(self):
+		temporary_file = tempfile.NamedTemporaryFile()
+		def write(string):
+			temporary_file.write(string.encode('utf-8'))
+
+		undocumented = []
+		for cmd_name in sorted(self.commands.commands):
+			cmd = self.commands.commands[cmd_name]
+			if hasattr(cmd, '__doc__') and cmd.__doc__:
+				write(cleandoc(cmd.__doc__))
+				write("\n\n" + "-" * 60 + "\n")
+			else:
+				undocumented.append(cmd)
+
+		if undocumented:
+			write("Undocumented commands:\n\n")
+			for cmd in undocumented:
+				write("    :%s\n" % cmd.get_name())
+
+		temporary_file.flush()
+		self.run(app='pager', files=[File(temporary_file.name)])
+
+	def dump_settings(self):
+		from ranger.container.settingobject import ALLOWED_SETTINGS
+		temporary_file = tempfile.NamedTemporaryFile()
+		def write(string):
+			temporary_file.write(string.encode('utf-8'))
+
+		for setting in sorted(ALLOWED_SETTINGS):
+			write("%30s = %s\n" % (setting, getattr(self.settings, setting)))
+
+		temporary_file.flush()
+		self.run(app='pager', files=[File(temporary_file.name)])
+
+	# --------------------------
 	# -- File System Operations
 	# --------------------------
 
@@ -817,6 +946,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		self.loader.add(obj)
 
 	def delete(self):
+		# XXX: warn when deleting mount points/unseen marked files?
 		self.notify("Deleting!")
 		selected = self.env.get_selection()
 		self.env.copy -= set(selected)
@@ -845,6 +975,6 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 			src = src.path
 
 		try:
-			os.rename(src, dest)
+			os.renames(src, dest)
 		except OSError as err:
 			self.notify(err)