about 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.py2254
1 files changed, 1127 insertions, 1127 deletions
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 491f22af..b02160aa 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -20,7 +20,7 @@ from ranger.ext.shell_escape import shell_quote
 from ranger.ext.next_available_filename import next_available_filename
 from ranger.ext.rifle import squash_flags, ASK_COMMAND
 from ranger.core.shared import FileManagerAware, EnvironmentAware, \
-		SettingsAware
+        SettingsAware
 from ranger.core.tab import Tab
 from ranger.fsobject import File
 from ranger.core.loader import CommandLoader, CopyLoader
@@ -29,1131 +29,1131 @@ from ranger.container.settingobject import ALLOWED_SETTINGS
 MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>"
 
 class _MacroTemplate(string.Template):
-	"""A template for substituting macros in commands"""
-	delimiter = ranger.MACRO_DELIMITER
+    """A template for substituting macros in commands"""
+    delimiter = ranger.MACRO_DELIMITER
 
 class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
-	# --------------------------
-	# -- Basic Commands
-	# --------------------------
-
-	def exit(self):
-		"""Exit the program"""
-		raise SystemExit()
-
-	def reset(self):
-		"""Reset the filemanager, clearing the directory buffer"""
-		old_path = self.thisdir.path
-		self.restorable_tabs = {}
-		self.previews = {}
-		self.garbage_collect(-1)
-		self.enter_dir(old_path)
-		self.change_mode('normal')
-
-	def change_mode(self, mode):
-		if mode == self.mode:
-			return
-		if mode == 'visual':
-			self._visual_start       = self.thisdir.pointed_obj
-			self._visual_start_pos   = self.thisdir.pointer
-			self._previous_selection = set(self.thisdir.marked_items)
-			self.mark_files(val=not self._visual_reverse, movedown=False)
-		elif mode == 'normal':
-			if self.mode == 'visual':
-				self._visual_start       = None
-				self._visual_start_pos   = None
-				self._previous_selection = None
-		else:
-			return
-		self.mode = mode
-		self.ui.status.request_redraw()
-
-	def set_option_from_string(self, option_name, value, localpath=None):
-		if option_name not in ALLOWED_SETTINGS:
-			raise ValueError("The option named `%s' does not exist" %
-					option_name)
-		if not isinstance(value, str):
-			raise ValueError("The value for an option needs to be a string.")
-
-		self.settings.set(option_name, self._parse_option_value(option_name, value), localpath)
-
-
-	def _parse_option_value(self, name, value):
-		types = self.fm.settings.types_of(name)
-		if bool in types:
-			if value.lower() in ('false', 'off', '0'):
-				return False
-			elif value.lower() in ('true', 'on', '1'):
-				return True
-		if type(None) in types and value.lower() == 'none':
-			return None
-		if int in types:
-			try:
-				return int(value)
-			except ValueError:
-				pass
-		if str in types:
-			return value
-		if list in types:
-			return value.split(',')
-		raise ValueError("Invalid value `%s' for option `%s'!" % (name, value))
-
-	def toggle_visual_mode(self, reverse=False):
-		if self.mode == 'normal':
-			self._visual_reverse = reverse
-			self.change_mode('visual')
-		else:
-			self.change_mode('normal')
-
-	def reload_cwd(self):
-		try:
-			cwd = self.thisdir
-		except:
-			pass
-		cwd.unload()
-		cwd.load_content()
-
-	def notify(self, text, duration=4, bad=False):
-		if isinstance(text, Exception):
-			if ranger.arg.debug:
-				raise
-			bad = True
-		elif bad == True and ranger.arg.debug:
-			raise Exception(str(text))
-		text = str(text)
-		self.log.appendleft(text)
-		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 get_cumulative_size(self):
-		for f in self.thistab.get_selection() or ():
-			f.look_up_cumulative_size()
-		self.ui.status.request_redraw()
-		self.ui.redraw_main_column()
-
-	def redraw_window(self):
-		"""Redraw the window"""
-		self.ui.redraw_window()
-
-	def open_console(self, string='', prompt=None, position=None):
-		"""Open the console"""
-		self.change_mode('normal')
-		self.ui.open_console(string, prompt=prompt, position=position)
-
-	def execute_console(self, string='', wildcards=[], quantifier=None):
-		"""Execute a command for the console"""
-		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.thisfile:
-			macros['f'] = self.fm.thisfile.basename
-		else:
-			macros['f'] = MACRO_FAIL
-
-		if self.fm.thistab.get_selection:
-			macros['s'] = [fl.basename for fl in self.fm.thistab.get_selection()]
-		else:
-			macros['s'] = MACRO_FAIL
-
-		if self.fm.copy_buffer:
-			macros['c'] = [fl.path for fl in self.fm.copy_buffer]
-		else:
-			macros['c'] = MACRO_FAIL
-
-		if self.fm.thisdir.files:
-			macros['t'] = [fl.basename for fl in self.fm.thisdir.files
-					if fl.realpath in (self.fm.tags or [])]
-		else:
-			macros['t'] = MACRO_FAIL
-
-		if self.fm.thisdir:
-			macros['d'] = self.fm.thisdir.path
-		else:
-			macros['d'] = '.'
-
-		# define d/f/s macros for each tab
-		for i in range(1,10):
-			try:
-				tab = self.fm.tabs[i]
-			except:
-				continue
-			tabdir = tab.thisdir
-			if not tabdir:
-				continue
-			i = str(i)
-			macros[i + 'd'] = tabdir.path
-			if tabdir.get_selection():
-				macros[i + 's'] = [fl.path for fl in tabdir.get_selection()]
-			else:
-				macros[i + 's'] = MACRO_FAIL
-			if tabdir.pointed_obj:
-				macros[i + 'f'] = tabdir.pointed_obj.path
-			else:
-				macros[i + 'f'] = MACRO_FAIL
-
-		# define D/F/S for the next tab
-		found_current_tab = False
-		next_tab = None
-		first_tab = None
-		for tabname in self.fm.tabs:
-			if not first_tab:
-				first_tab = tabname
-			if found_current_tab:
-				next_tab = self.fm.tabs[tabname]
-				break
-			if self.fm.current_tab == tabname:
-				found_current_tab = True
-		if found_current_tab and next_tab is None:
-			next_tab = self.fm.tabs[first_tab]
-		next_tab_dir = next_tab.thisdir
-
-		if next_tab_dir:
-			macros['D'] = str(next_tab_dir.path)
-			if next_tab.thisfile:
-				macros['F'] = next_tab.thisfile.path
-			else:
-				macros['F'] = MACRO_FAIL
-			if next_tab_dir.get_selection():
-				macros['S'] = [fl.path for fl in next_tab.get_selection()]
-			else:
-				macros['S'] = MACRO_FAIL
-		else:
-			macros['D'] = MACRO_FAIL
-			macros['F'] = MACRO_FAIL
-			macros['S'] = MACRO_FAIL
-
-		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.
-		app is the name of a method in Applications, without the "app_"
-		flags is a string consisting of runner.ALLOWED_FLAGS
-		mode is a positive integer.
-		Both flags and mode specify how the program is run."""
-
-		mode = kw['mode'] if 'mode' in kw else 0
-
-		# ranger can act as a file chooser when running with --choosefile=...
-		if mode == 0 and 'label' not in kw:
-			if ranger.arg.choosefile:
-				open(ranger.arg.choosefile, 'w').write(self.fm.thisfile.path)
-
-			if ranger.arg.choosefiles:
-				open(ranger.arg.choosefiles, 'w').write("".join(
-					f.path + "\n" for f in self.fm.thistab.get_selection()))
-
-			if ranger.arg.choosefile or ranger.arg.choosefiles:
-				raise SystemExit()
-
-		if isinstance(files, set):
-			files = list(files)
-		elif type(files) not in (list, tuple):
-			files = [files]
-
-		flags = kw.get('flags', '')
-		if 'c' in squash_flags(flags):
-			files = [self.fm.thisfile]
-
-		self.signal_emit('execute.before', keywords=kw)
-		filenames = [f.path for f in files]
-		label = kw.get('label', kw.get('app', None))
-		try:
-			return self.rifle.execute(filenames, mode, label, flags, None)
-		finally:
-			self.signal_emit('execute.after')
-
-	# --------------------------
-	# -- Moving Around
-	# --------------------------
-
-	def move(self, narg=None, **kw):
-		"""
-		A universal movement method.
-
-		Accepts these parameters:
-		(int) down, (int) up, (int) left, (int) right, (int) to,
-		(bool) absolute, (bool) relative, (bool) pages,
-		(bool) percentage
-
-		to=X is translated to down=X, absolute=True
-
-		Example:
-		self.move(down=4, pages=True)  # moves down by 4 pages.
-		self.move(to=2, pages=True)  # moves to page 2.
-		self.move(to=1, percentage=True)  # moves to 80%
-		"""
-		cwd = self.thisdir
-		direction = Direction(kw)
-		if 'left' in direction or direction.left() > 0:
-			steps = direction.left()
-			if narg is not None:
-				steps *= narg
-			try:
-				directory = os.path.join(*(['..'] * steps))
-			except:
-				return
-			self.thistab.enter_dir(directory)
-			self.change_mode('normal')
-		if cwd and cwd.accessible and cwd.content_loaded:
-			if 'right' in direction:
-				mode = 0
-				if narg is not None:
-					mode = narg
-				cf = self.thisfile
-				selection = self.thistab.get_selection()
-				if not self.thistab.enter_dir(cf) and selection:
-					result = self.execute_file(selection, mode=mode)
-					if result in (False, ASK_COMMAND):
-						self.open_console('open_with ')
-			elif direction.vertical() and cwd.files:
-				newpos = direction.move(
-						direction=direction.down(),
-						override=narg,
-						maximum=len(cwd),
-						current=cwd.pointer,
-						pagesize=self.ui.browser.hei)
-				cwd.move(to=newpos)
-				if self.mode == 'visual':
-					try:
-						startpos = cwd.index(self._visual_start)
-					except:
-						self._visual_start = None
-						startpos = min(self._visual_start_pos, len(cwd))
-					# The files between here and _visual_start_pos
-					targets = set(cwd.files[min(startpos, newpos):\
-							max(startpos, newpos) + 1])
-					# The selection before activating visual mode
-					old = self._previous_selection
-					# The current selection
-					current = set(cwd.marked_items)
-
-					# Set theory anyone?
-					if not self._visual_reverse:
-						for f in targets - current:
-							cwd.mark_item(f, True)
-						for f in current - old - targets:
-							cwd.mark_item(f, False)
-					else:
-						for f in targets & current:
-							cwd.mark_item(f, False)
-						for f in old - current - targets:
-							cwd.mark_item(f, True)
-
-	def move_parent(self, n, narg=None):
-		self.change_mode('normal')
-		if narg is not None:
-			n *= narg
-		parent = self.thistab.at_level(-1)
-		if parent is not None:
-			if parent.pointer + n < 0:
-				n = 0 - parent.pointer
-			try:
-				self.thistab.enter_dir(parent.files[parent.pointer+n])
-			except IndexError:
-				pass
-
-	def select_file(self, path):
-		path = path.strip()
-		if self.enter_dir(os.path.dirname(path)):
-			self.thisdir.move_to_obj(path)
-
-	def history_go(self, relative):
-		"""Move back and forth in the history"""
-		self.thistab.history_go(int(relative))
-
-	# TODO: remove this method since it is not used?
-	def scroll(self, relative):
-		"""Scroll down by <relative> lines"""
-		if self.ui.browser and self.ui.browser.main_column:
-			self.ui.browser.main_column.scroll(relative)
-			self.thisfile = self.thisdir.pointed_obj
-
-	def enter_dir(self, path, remember=False, history=True):
-		"""Enter the directory at the given path"""
-		cwd = self.thisdir
-		result = self.thistab.enter_dir(path, history=history)
-		if cwd != self.thisdir:
-			if remember:
-				self.bookmarks.remember(cwd)
-			self.change_mode('normal')
-		return result
-
-	def cd(self, path, remember=True):
-		"""enter the directory at the given path, remember=True"""
-		self.enter_dir(path, remember=remember)
-
-	def traverse(self):
-		self.change_mode('normal')
-		cf = self.thisfile
-		cwd = self.thisdir
-		if cf is not None and cf.is_directory:
-			self.enter_dir(cf.path)
-		elif cwd.pointer >= len(cwd) - 1:
-			while True:
-				self.move(left=1)
-				cwd = self.thisdir
-				if cwd.pointer < len(cwd) - 1:
-					break
-				if cwd.path == '/':
-					break
-			self.move(down=1)
-			self.traverse()
-		else:
-			self.move(down=1)
-			self.traverse()
-
-	# --------------------------
-	# -- 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 pause_tasks(self):
-		self.loader.pause(-1)
-
-	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)
-
-	def edit_file(self, file=None):
-		"""Calls execute_file with the current file and label='editor'"""
-		if file is None:
-			file = self.thisfile
-		elif isinstance(file, str):
-			file = File(os.path.expanduser(file))
-		if file is None:
-			return
-		self.execute_file(file, label='editor')
-
-	def toggle_option(self, string):
-		"""Toggle a boolean option named <string>"""
-		if isinstance(self.settings[string], bool):
-			self.settings[string] ^= True
-
-	def set_option(self, optname, value):
-		"""Set the value of an option named <optname>"""
-		self.settings[optname] = value
-
-	def sort(self, func=None, reverse=None):
-		if reverse is not None:
-			self.settings['sort_reverse'] = bool(reverse)
-
-		if func is not None:
-			self.settings['sort'] = str(func)
-
-	def set_filter(self, fltr):
-		try:
-			self.thisdir.filter = fltr
-		except:
-			pass
-
-	def mark_files(self, all=False, toggle=False, val=None, movedown=None, narg=1):
-		"""
-		A wrapper for the directory.mark_xyz functions.
-
-		Arguments:
-		all - change all files of the current directory at once?
-		toggle - toggle the marked-status?
-		val - mark or unmark?
-		"""
-
-		if self.thisdir is None:
-			return
-
-		cwd = self.thisdir
-
-		if not cwd.accessible:
-			return
-
-		if movedown is None:
-			movedown = not all
-
-		if val is None and toggle is False:
-			return
-
-		if all:
-			if toggle:
-				cwd.toggle_all_marks()
-			else:
-				cwd.mark_all(val)
-			if self.mode == 'visual':
-				self.change_mode('normal')
-		else:
-			for i in range(cwd.pointer, min(cwd.pointer + narg, len(cwd))):
-				item = cwd.files[i]
-				if item is not None:
-					if toggle:
-						cwd.toggle_mark(item)
-					else:
-						cwd.mark_item(item, val)
-
-		if movedown:
-			self.move(down=narg)
-
-		self.ui.redraw_main_column()
-		self.ui.status.need_redraw = True
-
-	def mark_in_direction(self, val=True, dirarg=None):
-		cwd = self.thisdir
-		direction = Direction(dirarg)
-		pos, selected = direction.select(lst=cwd.files, current=cwd.pointer,
-				pagesize=self.ui.termsize[0])
-		cwd.pointer = pos
-		cwd.correct_pointer()
-		for item in selected:
-			cwd.mark_item(item, val)
-
-	# --------------------------
-	# -- Searching
-	# --------------------------
-
-	def search_file(self, text, offset=1, regexp=True):
-		if isinstance(text, str) and regexp:
-			try:
-				text = re.compile(text, re.L | re.U | re.I)
-			except:
-				return False
-		self.thistab.last_search = text
-		self.search_next(order='search', offset=offset)
-
-	def search_next(self, order=None, offset=1, forward=True):
-		original_order = order
-
-		if order is None:
-			order = self.search_method
-		else:
-			self.set_search_method(order=order)
-
-		if order in ('search', 'tag'):
-			if order == 'search':
-				arg = self.thistab.last_search
-				if arg is None:
-					return False
-				if hasattr(arg, 'search'):
-					fnc = lambda x: arg.search(x.basename)
-				else:
-					fnc = lambda x: arg in x.basename
-			elif order == 'tag':
-				fnc = lambda x: x.realpath in self.tags
-
-			return self.thisdir.search_fnc(fnc=fnc, offset=offset, forward=forward)
-
-		elif order in ('size', 'mimetype', 'ctime', 'mtime', 'atime'):
-			cwd = self.thisdir
-			if original_order is not None or not cwd.cycle_list:
-				lst = list(cwd.files)
-				if order == 'size':
-					fnc = lambda item: -item.size
-				elif order == 'mimetype':
-					fnc = lambda item: item.mimetype or ''
-				elif order == 'ctime':
-					fnc = lambda item: -int(item.stat and item.stat.st_ctime)
-				elif order == 'atime':
-					fnc = lambda item: -int(item.stat and item.stat.st_atime)
-				elif order == 'mtime':
-					fnc = lambda item: -int(item.stat and item.stat.st_mtime)
-				lst.sort(key=fnc)
-				cwd.set_cycle_list(lst)
-				return cwd.cycle(forward=None)
-
-			return cwd.cycle(forward=forward)
-
-	def set_search_method(self, order, forward=True):
-		if order in ('search', 'tag', 'size', 'mimetype', 'ctime',
-				'mtime', 'atime'):
-			self.search_method = order
-
-	# --------------------------
-	# -- Tags
-	# --------------------------
-	# Tags are saved in ~/.config/ranger/tagged and simply mark if a
-	# file is important to you in any context.
-
-	def tag_toggle(self, paths=None, value=None, movedown=None, tag=None):
-		if not self.tags:
-			return
-		if paths is None:
-			tags = tuple(x.realpath for x in self.thistab.get_selection())
-		else:
-			tags = [realpath(path) for path in paths]
-		if value is True:
-			self.tags.add(*tags, tag=tag or self.tags.default_tag)
-		elif value is False:
-			self.tags.remove(*tags)
-		else:
-			self.tags.toggle(*tags, tag=tag or self.tags.default_tag)
-
-		if movedown is None:
-			movedown = len(tags) == 1 and paths is None
-		if movedown:
-			self.move(down=1)
-
-		self.ui.redraw_main_column()
-
-	def tag_remove(self, paths=None, movedown=None):
-		self.tag_toggle(paths=paths, value=False, movedown=movedown)
-
-	def tag_add(self, paths=None, movedown=None):
-		self.tag_toggle(paths=paths, value=True, movedown=movedown)
-
-	# --------------------------
-	# -- Bookmarks
-	# --------------------------
-	# Using ranger.container.bookmarks.
-
-	def enter_bookmark(self, key):
-		"""Enter the bookmark with the name <key>"""
-		try:
-			self.bookmarks.update_if_outdated()
-			destination = self.bookmarks[str(key)]
-			cwd = self.thisdir
-			if destination.path != cwd.path:
-				self.bookmarks.enter(str(key))
-				self.bookmarks.remember(cwd)
-		except KeyError:
-			pass
-
-	def set_bookmark(self, key):
-		"""Set the bookmark with the name <key> to the current directory"""
-		self.bookmarks.update_if_outdated()
-		self.bookmarks[str(key)] = self.thisdir
-
-	def unset_bookmark(self, key):
-		"""Delete the bookmark with the name <key>"""
-		self.bookmarks.update_if_outdated()
-		self.bookmarks.delete(str(key))
-
-	def draw_bookmarks(self):
-		self.ui.browser.draw_bookmarks = True
-
-	def hide_bookmarks(self):
-		self.ui.browser.draw_bookmarks = False
-
-	def draw_possible_programs(self):
-		try:
-			target = self.thistab.get_selection()[0]
-		except:
-			self.ui.browser.draw_info = []
-			return
-		programs = self.rifle.list_commands([target.path], None)
-		programs = ['%s | %s' % program[0:2] for program in programs]
-		self.ui.browser.draw_info = programs
-
-	def hide_console_info(self):
-		self.ui.browser.draw_info = False
-
-	# --------------------------
-	# -- Pager
-	# --------------------------
-	# These commands open the built-in pager and set specific sources.
-
-	def display_command_help(self, console_widget):
-		try:
-			command = console_widget._get_cmd_class()
-		except:
-			self.notify("Feature not available!", bad=True)
-			return
-
-		if not command:
-			self.notify("Command not found!", bad=True)
-			return
-
-		if not command.__doc__:
-			self.notify("Command has no docstring. Try using python without -OO",
-					bad=True)
-			return
-
-		pager = self.ui.open_pager()
-		lines = cleandoc(command.__doc__).split('\n')
-		pager.set_source(lines)
-
-	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):
-		pager = self.ui.open_pager()
-		if self.log:
-			pager.set_source(["Message Log:"] + list(self.log))
-		else:
-			pager.set_source(["Message Log:", "No messages!"])
-
-	def display_file(self):
-		if not self.thisfile or not self.thisfile.is_file:
-			return
-
-		pager = self.ui.open_embedded_pager()
-		if self.settings.preview_images and self.thisfile.image:
-			pager.set_image(self.thisfile.realpath)
-		else:
-			pager.set_source(self.thisfile.get_preview_source(pager.wid, pager.hei))
-
-	# --------------------------
-	# -- Previews
-	# --------------------------
-	def update_preview(self, path):
-		try:
-			del self.previews[path]
-			self.ui.need_redraw = True
-		except:
-			return False
-
-	def get_preview(self, file, width, height):
-		pager = self.ui.browser.pager
-		path = file.realpath
-
-		if self.settings.preview_images and file.image:
-			pager.set_image(path)
-			return None
-
-		if self.settings.preview_script and self.settings.use_preview_script:
-			# self.previews is a 2 dimensional dict:
-			# self.previews['/tmp/foo.jpg'][(80, 24)] = "the content..."
-			# self.previews['/tmp/foo.jpg']['loading'] = False
-			# A -1 in tuples means "any"; (80, -1) = wid. of 80 and any hei.
-			# The key 'foundpreview' is added later. Values in (True, False)
-			# XXX: Previews can break when collapse_preview is on and the
-			# preview column is popping out as you move the cursor on e.g. a
-			# PDF file.
-			try:
-				data = self.previews[path]
-			except:
-				data = self.previews[path] = {'loading': False}
-			else:
-				if data['loading']:
-					return None
-
-
-			found = data.get((-1, -1), data.get((width, -1),
-				data.get((-1, height), data.get((width, height), False))))
-			if found == False:
-				try:
-					stat_ = os.stat(self.settings.preview_script)
-				except:
-					self.fm.notify("Preview Script `%s' doesn't exist!" %
-							self.settings.preview_script, bad=True)
-					return None
-
-				if not stat_.st_mode & S_IEXEC:
-					self.fm.notify("Preview Script `%s' is not executable!" %
-							self.settings.preview_script, bad=True)
-					return None
-
-				data['loading'] = True
-				loadable = CommandLoader(args=[self.settings.preview_script,
-					path, str(width), str(height)], read=True,
-					silent=True, descr="Getting preview of %s" % path)
-				def on_after(signal):
-					exit = signal.process.poll()
-					content = signal.loader.stdout_buffer
-					data['foundpreview'] = True
-					if exit == 0:
-						data[(width, height)] = content
-					elif exit == 3:
-						data[(-1, height)] = content
-					elif exit == 4:
-						data[(width, -1)] = content
-					elif exit == 5:
-						data[(-1, -1)] = content
-					elif exit == 1:
-						data[(-1, -1)] = None
-						data['foundpreview'] = False
-					elif exit == 2:
-						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.thisfile.realpath == path:
-						self.ui.browser.need_redraw = True
-					data['loading'] = False
-					pager = self.ui.browser.pager
-					if self.thisfile and self.thisfile.is_file:
-						pager.set_source(self.thisfile.get_preview_source(
-							pager.wid, pager.hei))
-				def on_destroy(signal):
-					try:
-						del self.previews[path]
-					except:
-						pass
-				loadable.signal_bind('after', on_after)
-				loadable.signal_bind('destroy', on_destroy)
-				self.loader.add(loadable)
-				return None
-			else:
-				return found
-		else:
-			try:
-				return codecs.open(path, 'r', errors='ignore')
-			except:
-				return None
-
-	# --------------------------
-	# -- Tabs
-	# --------------------------
-	def tab_open(self, name, path=None):
-		tab_has_changed = (name != self.current_tab)
-		self.current_tab = name
-		previous_tab = self.thistab
-		try:
-			tab = self.tabs[name]
-		except KeyError:
-			# create a new tab
-			tab = Tab(self.thistab.path)
-			self.tabs[name] = tab
-			self.thistab = tab
-			tab.enter_dir(tab.path, history=False)
-			if path:
-				tab.enter_dir(path, history=True)
-			if previous_tab:
-				tab.inherit_history(previous_tab.history)
-		else:
-			self.thistab = tab
-			if path:
-				tab.enter_dir(path, history=True)
-			else:
-				tab.enter_dir(tab.path, history=False)
-
-		if tab_has_changed:
-			self.change_mode('normal')
-			self.signal_emit('tab.change', old=previous_tab, new=self.thistab)
-
-	def tab_close(self, name=None):
-		if name is None:
-			name = self.current_tab
-		tab = self.tabs[name]
-		if name == self.current_tab:
-			direction = -1 if name == self._get_tab_list()[-1] else 1
-			previous = self.current_tab
-			self.tab_move(direction)
-			if previous == self.current_tab:
-				return  # can't close last tab
-		if name in self.tabs:
-			del self.tabs[name]
-		self.restorable_tabs.append(tab)
-
-	def tab_restore(self):
-		# NOTE: The name of the tab is not restored.
-		previous_tab = self.thistab
-		if self.restorable_tabs:
-			tab = self.restorable_tabs.pop()
-			for name in range(1, len(self.tabs) + 2):
-				if not name in self.tabs:
-					self.current_tab = name
-					self.tabs[name] = tab
-					tab.enter_dir(tab.path, history=False)
-					self.thistab = tab
-					self.change_mode('normal')
-					self.signal_emit('tab.change', old=previous_tab,
-							new=self.thistab)
-					break
-
-	def tab_move(self, offset):
-		assert isinstance(offset, int)
-		tablist = self._get_tab_list()
-		current_index = tablist.index(self.current_tab)
-		newtab = tablist[(current_index + offset) % len(tablist)]
-		if newtab != self.current_tab:
-			self.tab_open(newtab)
-
-	def tab_new(self, path=None):
-		for i in range(1, 10):
-			if not i in self.tabs:
-				self.tab_open(i, path)
-				break
-
-	def _get_tab_list(self):
-		assert len(self.tabs) > 0, "There must be >=1 tabs at all times"
-		return sorted(self.tabs)
-
-	# --------------------------
-	# -- 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.fm.ui.keymaps:
-				recurse([], self.fm.ui.keymaps[context])
-			else:
-				write("  None\n")
-			write("\n")
-
-		temporary_file.flush()
-		pager = os.environ.get('PAGER', ranger.DEFAULT_PAGER)
-		self.run([pager, 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()
-		pager = os.environ.get('PAGER', ranger.DEFAULT_PAGER)
-		self.run([pager, 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()
-		pager = os.environ.get('PAGER', ranger.DEFAULT_PAGER)
-		self.run([pager, temporary_file.name])
-
-	# --------------------------
-	# -- File System Operations
-	# --------------------------
-
-	def uncut(self):
-		self.copy_buffer = set()
-		self.do_cut = False
-		self.ui.browser.main_column.request_redraw()
-
-	def copy(self, mode='set', narg=None, dirarg=None):
-		"""Copy the selected items.  Modes are: 'set', 'add', 'remove'."""
-		assert mode in ('set', 'add', 'remove')
-		cwd = self.thisdir
-		if not narg and not dirarg:
-			selected = (f for f in self.thistab.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.ui.termsize[0], offset=offset)
-			cwd.pointer = pos
-			cwd.correct_pointer()
-		if mode == 'set':
-			self.copy_buffer = set(selected)
-		elif mode == 'add':
-			self.copy_buffer.update(set(selected))
-		elif mode == 'remove':
-			self.copy_buffer.difference_update(set(selected))
-		self.do_cut = False
-		self.ui.browser.main_column.request_redraw()
-
-	def cut(self, mode='set', narg=None, dirarg=None):
-		self.copy(mode=mode, narg=narg, dirarg=dirarg)
-		self.do_cut = True
-		self.ui.browser.main_column.request_redraw()
-
-	def paste_symlink(self, relative=False):
-		copied_files = self.copy_buffer
-		for f in copied_files:
-			self.notify(next_available_filename(f.basename))
-			try:
-				new_name = next_available_filename(f.basename)
-				if relative:
-					relative_symlink(f.path, join(getcwd(), new_name))
-				else:
-					symlink(f.path, join(getcwd(), new_name))
-			except Exception as x:
-				self.notify(x)
-
-	def paste_hardlink(self):
-		for f in self.copy_buffer:
-			try:
-				new_name = next_available_filename(f.basename)
-				link(f.path, join(getcwd(), new_name))
-			except Exception as x:
-				self.notify(x)
-
-	def paste_hardlinked_subtree(self):
-		for f in self.copy_buffer:
-			try:
-				target_path = join(getcwd(), f.basename)
-				self._recurse_hardlinked_tree(f.path, target_path)
-			except Exception as x:
-				self.notify(x)
-
-	def _recurse_hardlinked_tree(self, source_path, target_path):
-		if isdir(source_path):
-			if not exists(target_path):
-				os.mkdir(target_path, stat(source_path).st_mode)
-			for item in listdir(source_path):
-				self._recurse_hardlinked_tree(
-					join(source_path, item),
-					join(target_path, item))
-		else:
-			if not exists(target_path) \
-			or stat(source_path).st_ino != stat(target_path).st_ino:
-				link(source_path,
-					next_available_filename(target_path))
-
-	def paste(self, overwrite=False):
-		"""Paste the selected items into the current directory"""
-		self.loader.add(CopyLoader(self.copy_buffer, self.do_cut, overwrite))
-		self.do_cut = False
-
-	def delete(self):
-		# XXX: warn when deleting mount points/unseen marked files?
-		self.notify("Deleting!")
-		selected = self.thistab.get_selection()
-		self.copy_buffer -= set(selected)
-		if selected:
-			for f in selected:
-				if isdir(f.path) and not os.path.islink(f.path):
-					try:
-						shutil.rmtree(f.path)
-					except OSError as err:
-						self.notify(err)
-				else:
-					try:
-						os.remove(f.path)
-					except OSError as err:
-						self.notify(err)
-		self.thistab.ensure_correct_pointer()
-
-	def mkdir(self, name):
-		try:
-			os.mkdir(os.path.join(self.thisdir.path, name))
-		except OSError as err:
-			self.notify(err)
-
-	def rename(self, src, dest):
-		if hasattr(src, 'path'):
-			src = src.path
-
-		try:
-			os.renames(src, dest)
-		except OSError as err:
-			self.notify(err)
+    # --------------------------
+    # -- Basic Commands
+    # --------------------------
+
+    def exit(self):
+        """Exit the program"""
+        raise SystemExit()
+
+    def reset(self):
+        """Reset the filemanager, clearing the directory buffer"""
+        old_path = self.thisdir.path
+        self.restorable_tabs = {}
+        self.previews = {}
+        self.garbage_collect(-1)
+        self.enter_dir(old_path)
+        self.change_mode('normal')
+
+    def change_mode(self, mode):
+        if mode == self.mode:
+            return
+        if mode == 'visual':
+            self._visual_start       = self.thisdir.pointed_obj
+            self._visual_start_pos   = self.thisdir.pointer
+            self._previous_selection = set(self.thisdir.marked_items)
+            self.mark_files(val=not self._visual_reverse, movedown=False)
+        elif mode == 'normal':
+            if self.mode == 'visual':
+                self._visual_start       = None
+                self._visual_start_pos   = None
+                self._previous_selection = None
+        else:
+            return
+        self.mode = mode
+        self.ui.status.request_redraw()
+
+    def set_option_from_string(self, option_name, value, localpath=None):
+        if option_name not in ALLOWED_SETTINGS:
+            raise ValueError("The option named `%s' does not exist" %
+                    option_name)
+        if not isinstance(value, str):
+            raise ValueError("The value for an option needs to be a string.")
+
+        self.settings.set(option_name, self._parse_option_value(option_name, value), localpath)
+
+
+    def _parse_option_value(self, name, value):
+        types = self.fm.settings.types_of(name)
+        if bool in types:
+            if value.lower() in ('false', 'off', '0'):
+                return False
+            elif value.lower() in ('true', 'on', '1'):
+                return True
+        if type(None) in types and value.lower() == 'none':
+            return None
+        if int in types:
+            try:
+                return int(value)
+            except ValueError:
+                pass
+        if str in types:
+            return value
+        if list in types:
+            return value.split(',')
+        raise ValueError("Invalid value `%s' for option `%s'!" % (name, value))
+
+    def toggle_visual_mode(self, reverse=False):
+        if self.mode == 'normal':
+            self._visual_reverse = reverse
+            self.change_mode('visual')
+        else:
+            self.change_mode('normal')
+
+    def reload_cwd(self):
+        try:
+            cwd = self.thisdir
+        except:
+            pass
+        cwd.unload()
+        cwd.load_content()
+
+    def notify(self, text, duration=4, bad=False):
+        if isinstance(text, Exception):
+            if ranger.arg.debug:
+                raise
+            bad = True
+        elif bad == True and ranger.arg.debug:
+            raise Exception(str(text))
+        text = str(text)
+        self.log.appendleft(text)
+        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 get_cumulative_size(self):
+        for f in self.thistab.get_selection() or ():
+            f.look_up_cumulative_size()
+        self.ui.status.request_redraw()
+        self.ui.redraw_main_column()
+
+    def redraw_window(self):
+        """Redraw the window"""
+        self.ui.redraw_window()
+
+    def open_console(self, string='', prompt=None, position=None):
+        """Open the console"""
+        self.change_mode('normal')
+        self.ui.open_console(string, prompt=prompt, position=position)
+
+    def execute_console(self, string='', wildcards=[], quantifier=None):
+        """Execute a command for the console"""
+        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.thisfile:
+            macros['f'] = self.fm.thisfile.basename
+        else:
+            macros['f'] = MACRO_FAIL
+
+        if self.fm.thistab.get_selection:
+            macros['s'] = [fl.basename for fl in self.fm.thistab.get_selection()]
+        else:
+            macros['s'] = MACRO_FAIL
+
+        if self.fm.copy_buffer:
+            macros['c'] = [fl.path for fl in self.fm.copy_buffer]
+        else:
+            macros['c'] = MACRO_FAIL
+
+        if self.fm.thisdir.files:
+            macros['t'] = [fl.basename for fl in self.fm.thisdir.files
+                    if fl.realpath in (self.fm.tags or [])]
+        else:
+            macros['t'] = MACRO_FAIL
+
+        if self.fm.thisdir:
+            macros['d'] = self.fm.thisdir.path
+        else:
+            macros['d'] = '.'
+
+        # define d/f/s macros for each tab
+        for i in range(1,10):
+            try:
+                tab = self.fm.tabs[i]
+            except:
+                continue
+            tabdir = tab.thisdir
+            if not tabdir:
+                continue
+            i = str(i)
+            macros[i + 'd'] = tabdir.path
+            if tabdir.get_selection():
+                macros[i + 's'] = [fl.path for fl in tabdir.get_selection()]
+            else:
+                macros[i + 's'] = MACRO_FAIL
+            if tabdir.pointed_obj:
+                macros[i + 'f'] = tabdir.pointed_obj.path
+            else:
+                macros[i + 'f'] = MACRO_FAIL
+
+        # define D/F/S for the next tab
+        found_current_tab = False
+        next_tab = None
+        first_tab = None
+        for tabname in self.fm.tabs:
+            if not first_tab:
+                first_tab = tabname
+            if found_current_tab:
+                next_tab = self.fm.tabs[tabname]
+                break
+            if self.fm.current_tab == tabname:
+                found_current_tab = True
+        if found_current_tab and next_tab is None:
+            next_tab = self.fm.tabs[first_tab]
+        next_tab_dir = next_tab.thisdir
+
+        if next_tab_dir:
+            macros['D'] = str(next_tab_dir.path)
+            if next_tab.thisfile:
+                macros['F'] = next_tab.thisfile.path
+            else:
+                macros['F'] = MACRO_FAIL
+            if next_tab_dir.get_selection():
+                macros['S'] = [fl.path for fl in next_tab.get_selection()]
+            else:
+                macros['S'] = MACRO_FAIL
+        else:
+            macros['D'] = MACRO_FAIL
+            macros['F'] = MACRO_FAIL
+            macros['S'] = MACRO_FAIL
+
+        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.
+        app is the name of a method in Applications, without the "app_"
+        flags is a string consisting of runner.ALLOWED_FLAGS
+        mode is a positive integer.
+        Both flags and mode specify how the program is run."""
+
+        mode = kw['mode'] if 'mode' in kw else 0
+
+        # ranger can act as a file chooser when running with --choosefile=...
+        if mode == 0 and 'label' not in kw:
+            if ranger.arg.choosefile:
+                open(ranger.arg.choosefile, 'w').write(self.fm.thisfile.path)
+
+            if ranger.arg.choosefiles:
+                open(ranger.arg.choosefiles, 'w').write("".join(
+                    f.path + "\n" for f in self.fm.thistab.get_selection()))
+
+            if ranger.arg.choosefile or ranger.arg.choosefiles:
+                raise SystemExit()
+
+        if isinstance(files, set):
+            files = list(files)
+        elif type(files) not in (list, tuple):
+            files = [files]
+
+        flags = kw.get('flags', '')
+        if 'c' in squash_flags(flags):
+            files = [self.fm.thisfile]
+
+        self.signal_emit('execute.before', keywords=kw)
+        filenames = [f.path for f in files]
+        label = kw.get('label', kw.get('app', None))
+        try:
+            return self.rifle.execute(filenames, mode, label, flags, None)
+        finally:
+            self.signal_emit('execute.after')
+
+    # --------------------------
+    # -- Moving Around
+    # --------------------------
+
+    def move(self, narg=None, **kw):
+        """
+        A universal movement method.
+
+        Accepts these parameters:
+        (int) down, (int) up, (int) left, (int) right, (int) to,
+        (bool) absolute, (bool) relative, (bool) pages,
+        (bool) percentage
+
+        to=X is translated to down=X, absolute=True
+
+        Example:
+        self.move(down=4, pages=True)  # moves down by 4 pages.
+        self.move(to=2, pages=True)  # moves to page 2.
+        self.move(to=1, percentage=True)  # moves to 80%
+        """
+        cwd = self.thisdir
+        direction = Direction(kw)
+        if 'left' in direction or direction.left() > 0:
+            steps = direction.left()
+            if narg is not None:
+                steps *= narg
+            try:
+                directory = os.path.join(*(['..'] * steps))
+            except:
+                return
+            self.thistab.enter_dir(directory)
+            self.change_mode('normal')
+        if cwd and cwd.accessible and cwd.content_loaded:
+            if 'right' in direction:
+                mode = 0
+                if narg is not None:
+                    mode = narg
+                cf = self.thisfile
+                selection = self.thistab.get_selection()
+                if not self.thistab.enter_dir(cf) and selection:
+                    result = self.execute_file(selection, mode=mode)
+                    if result in (False, ASK_COMMAND):
+                        self.open_console('open_with ')
+            elif direction.vertical() and cwd.files:
+                newpos = direction.move(
+                        direction=direction.down(),
+                        override=narg,
+                        maximum=len(cwd),
+                        current=cwd.pointer,
+                        pagesize=self.ui.browser.hei)
+                cwd.move(to=newpos)
+                if self.mode == 'visual':
+                    try:
+                        startpos = cwd.index(self._visual_start)
+                    except:
+                        self._visual_start = None
+                        startpos = min(self._visual_start_pos, len(cwd))
+                    # The files between here and _visual_start_pos
+                    targets = set(cwd.files[min(startpos, newpos):\
+                            max(startpos, newpos) + 1])
+                    # The selection before activating visual mode
+                    old = self._previous_selection
+                    # The current selection
+                    current = set(cwd.marked_items)
+
+                    # Set theory anyone?
+                    if not self._visual_reverse:
+                        for f in targets - current:
+                            cwd.mark_item(f, True)
+                        for f in current - old - targets:
+                            cwd.mark_item(f, False)
+                    else:
+                        for f in targets & current:
+                            cwd.mark_item(f, False)
+                        for f in old - current - targets:
+                            cwd.mark_item(f, True)
+
+    def move_parent(self, n, narg=None):
+        self.change_mode('normal')
+        if narg is not None:
+            n *= narg
+        parent = self.thistab.at_level(-1)
+        if parent is not None:
+            if parent.pointer + n < 0:
+                n = 0 - parent.pointer
+            try:
+                self.thistab.enter_dir(parent.files[parent.pointer+n])
+            except IndexError:
+                pass
+
+    def select_file(self, path):
+        path = path.strip()
+        if self.enter_dir(os.path.dirname(path)):
+            self.thisdir.move_to_obj(path)
+
+    def history_go(self, relative):
+        """Move back and forth in the history"""
+        self.thistab.history_go(int(relative))
+
+    # TODO: remove this method since it is not used?
+    def scroll(self, relative):
+        """Scroll down by <relative> lines"""
+        if self.ui.browser and self.ui.browser.main_column:
+            self.ui.browser.main_column.scroll(relative)
+            self.thisfile = self.thisdir.pointed_obj
+
+    def enter_dir(self, path, remember=False, history=True):
+        """Enter the directory at the given path"""
+        cwd = self.thisdir
+        result = self.thistab.enter_dir(path, history=history)
+        if cwd != self.thisdir:
+            if remember:
+                self.bookmarks.remember(cwd)
+            self.change_mode('normal')
+        return result
+
+    def cd(self, path, remember=True):
+        """enter the directory at the given path, remember=True"""
+        self.enter_dir(path, remember=remember)
+
+    def traverse(self):
+        self.change_mode('normal')
+        cf = self.thisfile
+        cwd = self.thisdir
+        if cf is not None and cf.is_directory:
+            self.enter_dir(cf.path)
+        elif cwd.pointer >= len(cwd) - 1:
+            while True:
+                self.move(left=1)
+                cwd = self.thisdir
+                if cwd.pointer < len(cwd) - 1:
+                    break
+                if cwd.path == '/':
+                    break
+            self.move(down=1)
+            self.traverse()
+        else:
+            self.move(down=1)
+            self.traverse()
+
+    # --------------------------
+    # -- 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 pause_tasks(self):
+        self.loader.pause(-1)
+
+    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)
+
+    def edit_file(self, file=None):
+        """Calls execute_file with the current file and label='editor'"""
+        if file is None:
+            file = self.thisfile
+        elif isinstance(file, str):
+            file = File(os.path.expanduser(file))
+        if file is None:
+            return
+        self.execute_file(file, label='editor')
+
+    def toggle_option(self, string):
+        """Toggle a boolean option named <string>"""
+        if isinstance(self.settings[string], bool):
+            self.settings[string] ^= True
+
+    def set_option(self, optname, value):
+        """Set the value of an option named <optname>"""
+        self.settings[optname] = value
+
+    def sort(self, func=None, reverse=None):
+        if reverse is not None:
+            self.settings['sort_reverse'] = bool(reverse)
+
+        if func is not None:
+            self.settings['sort'] = str(func)
+
+    def set_filter(self, fltr):
+        try:
+            self.thisdir.filter = fltr
+        except:
+            pass
+
+    def mark_files(self, all=False, toggle=False, val=None, movedown=None, narg=1):
+        """
+        A wrapper for the directory.mark_xyz functions.
+
+        Arguments:
+        all - change all files of the current directory at once?
+        toggle - toggle the marked-status?
+        val - mark or unmark?
+        """
+
+        if self.thisdir is None:
+            return
+
+        cwd = self.thisdir
+
+        if not cwd.accessible:
+            return
+
+        if movedown is None:
+            movedown = not all
+
+        if val is None and toggle is False:
+            return
+
+        if all:
+            if toggle:
+                cwd.toggle_all_marks()
+            else:
+                cwd.mark_all(val)
+            if self.mode == 'visual':
+                self.change_mode('normal')
+        else:
+            for i in range(cwd.pointer, min(cwd.pointer + narg, len(cwd))):
+                item = cwd.files[i]
+                if item is not None:
+                    if toggle:
+                        cwd.toggle_mark(item)
+                    else:
+                        cwd.mark_item(item, val)
+
+        if movedown:
+            self.move(down=narg)
+
+        self.ui.redraw_main_column()
+        self.ui.status.need_redraw = True
+
+    def mark_in_direction(self, val=True, dirarg=None):
+        cwd = self.thisdir
+        direction = Direction(dirarg)
+        pos, selected = direction.select(lst=cwd.files, current=cwd.pointer,
+                pagesize=self.ui.termsize[0])
+        cwd.pointer = pos
+        cwd.correct_pointer()
+        for item in selected:
+            cwd.mark_item(item, val)
+
+    # --------------------------
+    # -- Searching
+    # --------------------------
+
+    def search_file(self, text, offset=1, regexp=True):
+        if isinstance(text, str) and regexp:
+            try:
+                text = re.compile(text, re.L | re.U | re.I)
+            except:
+                return False
+        self.thistab.last_search = text
+        self.search_next(order='search', offset=offset)
+
+    def search_next(self, order=None, offset=1, forward=True):
+        original_order = order
+
+        if order is None:
+            order = self.search_method
+        else:
+            self.set_search_method(order=order)
+
+        if order in ('search', 'tag'):
+            if order == 'search':
+                arg = self.thistab.last_search
+                if arg is None:
+                    return False
+                if hasattr(arg, 'search'):
+                    fnc = lambda x: arg.search(x.basename)
+                else:
+                    fnc = lambda x: arg in x.basename
+            elif order == 'tag':
+                fnc = lambda x: x.realpath in self.tags
+
+            return self.thisdir.search_fnc(fnc=fnc, offset=offset, forward=forward)
+
+        elif order in ('size', 'mimetype', 'ctime', 'mtime', 'atime'):
+            cwd = self.thisdir
+            if original_order is not None or not cwd.cycle_list:
+                lst = list(cwd.files)
+                if order == 'size':
+                    fnc = lambda item: -item.size
+                elif order == 'mimetype':
+                    fnc = lambda item: item.mimetype or ''
+                elif order == 'ctime':
+                    fnc = lambda item: -int(item.stat and item.stat.st_ctime)
+                elif order == 'atime':
+                    fnc = lambda item: -int(item.stat and item.stat.st_atime)
+                elif order == 'mtime':
+                    fnc = lambda item: -int(item.stat and item.stat.st_mtime)
+                lst.sort(key=fnc)
+                cwd.set_cycle_list(lst)
+                return cwd.cycle(forward=None)
+
+            return cwd.cycle(forward=forward)
+
+    def set_search_method(self, order, forward=True):
+        if order in ('search', 'tag', 'size', 'mimetype', 'ctime',
+                'mtime', 'atime'):
+            self.search_method = order
+
+    # --------------------------
+    # -- Tags
+    # --------------------------
+    # Tags are saved in ~/.config/ranger/tagged and simply mark if a
+    # file is important to you in any context.
+
+    def tag_toggle(self, paths=None, value=None, movedown=None, tag=None):
+        if not self.tags:
+            return
+        if paths is None:
+            tags = tuple(x.realpath for x in self.thistab.get_selection())
+        else:
+            tags = [realpath(path) for path in paths]
+        if value is True:
+            self.tags.add(*tags, tag=tag or self.tags.default_tag)
+        elif value is False:
+            self.tags.remove(*tags)
+        else:
+            self.tags.toggle(*tags, tag=tag or self.tags.default_tag)
+
+        if movedown is None:
+            movedown = len(tags) == 1 and paths is None
+        if movedown:
+            self.move(down=1)
+
+        self.ui.redraw_main_column()
+
+    def tag_remove(self, paths=None, movedown=None):
+        self.tag_toggle(paths=paths, value=False, movedown=movedown)
+
+    def tag_add(self, paths=None, movedown=None):
+        self.tag_toggle(paths=paths, value=True, movedown=movedown)
+
+    # --------------------------
+    # -- Bookmarks
+    # --------------------------
+    # Using ranger.container.bookmarks.
+
+    def enter_bookmark(self, key):
+        """Enter the bookmark with the name <key>"""
+        try:
+            self.bookmarks.update_if_outdated()
+            destination = self.bookmarks[str(key)]
+            cwd = self.thisdir
+            if destination.path != cwd.path:
+                self.bookmarks.enter(str(key))
+                self.bookmarks.remember(cwd)
+        except KeyError:
+            pass
+
+    def set_bookmark(self, key):
+        """Set the bookmark with the name <key> to the current directory"""
+        self.bookmarks.update_if_outdated()
+        self.bookmarks[str(key)] = self.thisdir
+
+    def unset_bookmark(self, key):
+        """Delete the bookmark with the name <key>"""
+        self.bookmarks.update_if_outdated()
+        self.bookmarks.delete(str(key))
+
+    def draw_bookmarks(self):
+        self.ui.browser.draw_bookmarks = True
+
+    def hide_bookmarks(self):
+        self.ui.browser.draw_bookmarks = False
+
+    def draw_possible_programs(self):
+        try:
+            target = self.thistab.get_selection()[0]
+        except:
+            self.ui.browser.draw_info = []
+            return
+        programs = self.rifle.list_commands([target.path], None)
+        programs = ['%s | %s' % program[0:2] for program in programs]
+        self.ui.browser.draw_info = programs
+
+    def hide_console_info(self):
+        self.ui.browser.draw_info = False
+
+    # --------------------------
+    # -- Pager
+    # --------------------------
+    # These commands open the built-in pager and set specific sources.
+
+    def display_command_help(self, console_widget):
+        try:
+            command = console_widget._get_cmd_class()
+        except:
+            self.notify("Feature not available!", bad=True)
+            return
+
+        if not command:
+            self.notify("Command not found!", bad=True)
+            return
+
+        if not command.__doc__:
+            self.notify("Command has no docstring. Try using python without -OO",
+                    bad=True)
+            return
+
+        pager = self.ui.open_pager()
+        lines = cleandoc(command.__doc__).split('\n')
+        pager.set_source(lines)
+
+    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):
+        pager = self.ui.open_pager()
+        if self.log:
+            pager.set_source(["Message Log:"] + list(self.log))
+        else:
+            pager.set_source(["Message Log:", "No messages!"])
+
+    def display_file(self):
+        if not self.thisfile or not self.thisfile.is_file:
+            return
+
+        pager = self.ui.open_embedded_pager()
+        if self.settings.preview_images and self.thisfile.image:
+            pager.set_image(self.thisfile.realpath)
+        else:
+            pager.set_source(self.thisfile.get_preview_source(pager.wid, pager.hei))
+
+    # --------------------------
+    # -- Previews
+    # --------------------------
+    def update_preview(self, path):
+        try:
+            del self.previews[path]
+            self.ui.need_redraw = True
+        except:
+            return False
+
+    def get_preview(self, file, width, height):
+        pager = self.ui.browser.pager
+        path = file.realpath
+
+        if self.settings.preview_images and file.image:
+            pager.set_image(path)
+            return None
+
+        if self.settings.preview_script and self.settings.use_preview_script:
+            # self.previews is a 2 dimensional dict:
+            # self.previews['/tmp/foo.jpg'][(80, 24)] = "the content..."
+            # self.previews['/tmp/foo.jpg']['loading'] = False
+            # A -1 in tuples means "any"; (80, -1) = wid. of 80 and any hei.
+            # The key 'foundpreview' is added later. Values in (True, False)
+            # XXX: Previews can break when collapse_preview is on and the
+            # preview column is popping out as you move the cursor on e.g. a
+            # PDF file.
+            try:
+                data = self.previews[path]
+            except:
+                data = self.previews[path] = {'loading': False}
+            else:
+                if data['loading']:
+                    return None
+
+
+            found = data.get((-1, -1), data.get((width, -1),
+                data.get((-1, height), data.get((width, height), False))))
+            if found == False:
+                try:
+                    stat_ = os.stat(self.settings.preview_script)
+                except:
+                    self.fm.notify("Preview Script `%s' doesn't exist!" %
+                            self.settings.preview_script, bad=True)
+                    return None
+
+                if not stat_.st_mode & S_IEXEC:
+                    self.fm.notify("Preview Script `%s' is not executable!" %
+                            self.settings.preview_script, bad=True)
+                    return None
+
+                data['loading'] = True
+                loadable = CommandLoader(args=[self.settings.preview_script,
+                    path, str(width), str(height)], read=True,
+                    silent=True, descr="Getting preview of %s" % path)
+                def on_after(signal):
+                    exit = signal.process.poll()
+                    content = signal.loader.stdout_buffer
+                    data['foundpreview'] = True
+                    if exit == 0:
+                        data[(width, height)] = content
+                    elif exit == 3:
+                        data[(-1, height)] = content
+                    elif exit == 4:
+                        data[(width, -1)] = content
+                    elif exit == 5:
+                        data[(-1, -1)] = content
+                    elif exit == 1:
+                        data[(-1, -1)] = None
+                        data['foundpreview'] = False
+                    elif exit == 2:
+                        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.thisfile.realpath == path:
+                        self.ui.browser.need_redraw = True
+                    data['loading'] = False
+                    pager = self.ui.browser.pager
+                    if self.thisfile and self.thisfile.is_file:
+                        pager.set_source(self.thisfile.get_preview_source(
+                            pager.wid, pager.hei))
+                def on_destroy(signal):
+                    try:
+                        del self.previews[path]
+                    except:
+                        pass
+                loadable.signal_bind('after', on_after)
+                loadable.signal_bind('destroy', on_destroy)
+                self.loader.add(loadable)
+                return None
+            else:
+                return found
+        else:
+            try:
+                return codecs.open(path, 'r', errors='ignore')
+            except:
+                return None
+
+    # --------------------------
+    # -- Tabs
+    # --------------------------
+    def tab_open(self, name, path=None):
+        tab_has_changed = (name != self.current_tab)
+        self.current_tab = name
+        previous_tab = self.thistab
+        try:
+            tab = self.tabs[name]
+        except KeyError:
+            # create a new tab
+            tab = Tab(self.thistab.path)
+            self.tabs[name] = tab
+            self.thistab = tab
+            tab.enter_dir(tab.path, history=False)
+            if path:
+                tab.enter_dir(path, history=True)
+            if previous_tab:
+                tab.inherit_history(previous_tab.history)
+        else:
+            self.thistab = tab
+            if path:
+                tab.enter_dir(path, history=True)
+            else:
+                tab.enter_dir(tab.path, history=False)
+
+        if tab_has_changed:
+            self.change_mode('normal')
+            self.signal_emit('tab.change', old=previous_tab, new=self.thistab)
+
+    def tab_close(self, name=None):
+        if name is None:
+            name = self.current_tab
+        tab = self.tabs[name]
+        if name == self.current_tab:
+            direction = -1 if name == self._get_tab_list()[-1] else 1
+            previous = self.current_tab
+            self.tab_move(direction)
+            if previous == self.current_tab:
+                return  # can't close last tab
+        if name in self.tabs:
+            del self.tabs[name]
+        self.restorable_tabs.append(tab)
+
+    def tab_restore(self):
+        # NOTE: The name of the tab is not restored.
+        previous_tab = self.thistab
+        if self.restorable_tabs:
+            tab = self.restorable_tabs.pop()
+            for name in range(1, len(self.tabs) + 2):
+                if not name in self.tabs:
+                    self.current_tab = name
+                    self.tabs[name] = tab
+                    tab.enter_dir(tab.path, history=False)
+                    self.thistab = tab
+                    self.change_mode('normal')
+                    self.signal_emit('tab.change', old=previous_tab,
+                            new=self.thistab)
+                    break
+
+    def tab_move(self, offset):
+        assert isinstance(offset, int)
+        tablist = self._get_tab_list()
+        current_index = tablist.index(self.current_tab)
+        newtab = tablist[(current_index + offset) % len(tablist)]
+        if newtab != self.current_tab:
+            self.tab_open(newtab)
+
+    def tab_new(self, path=None):
+        for i in range(1, 10):
+            if not i in self.tabs:
+                self.tab_open(i, path)
+                break
+
+    def _get_tab_list(self):
+        assert len(self.tabs) > 0, "There must be >=1 tabs at all times"
+        return sorted(self.tabs)
+
+    # --------------------------
+    # -- 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.fm.ui.keymaps:
+                recurse([], self.fm.ui.keymaps[context])
+            else:
+                write("  None\n")
+            write("\n")
+
+        temporary_file.flush()
+        pager = os.environ.get('PAGER', ranger.DEFAULT_PAGER)
+        self.run([pager, 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()
+        pager = os.environ.get('PAGER', ranger.DEFAULT_PAGER)
+        self.run([pager, 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()
+        pager = os.environ.get('PAGER', ranger.DEFAULT_PAGER)
+        self.run([pager, temporary_file.name])
+
+    # --------------------------
+    # -- File System Operations
+    # --------------------------
+
+    def uncut(self):
+        self.copy_buffer = set()
+        self.do_cut = False
+        self.ui.browser.main_column.request_redraw()
+
+    def copy(self, mode='set', narg=None, dirarg=None):
+        """Copy the selected items.  Modes are: 'set', 'add', 'remove'."""
+        assert mode in ('set', 'add', 'remove')
+        cwd = self.thisdir
+        if not narg and not dirarg:
+            selected = (f for f in self.thistab.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.ui.termsize[0], offset=offset)
+            cwd.pointer = pos
+            cwd.correct_pointer()
+        if mode == 'set':
+            self.copy_buffer = set(selected)
+        elif mode == 'add':
+            self.copy_buffer.update(set(selected))
+        elif mode == 'remove':
+            self.copy_buffer.difference_update(set(selected))
+        self.do_cut = False
+        self.ui.browser.main_column.request_redraw()
+
+    def cut(self, mode='set', narg=None, dirarg=None):
+        self.copy(mode=mode, narg=narg, dirarg=dirarg)
+        self.do_cut = True
+        self.ui.browser.main_column.request_redraw()
+
+    def paste_symlink(self, relative=False):
+        copied_files = self.copy_buffer
+        for f in copied_files:
+            self.notify(next_available_filename(f.basename))
+            try:
+                new_name = next_available_filename(f.basename)
+                if relative:
+                    relative_symlink(f.path, join(getcwd(), new_name))
+                else:
+                    symlink(f.path, join(getcwd(), new_name))
+            except Exception as x:
+                self.notify(x)
+
+    def paste_hardlink(self):
+        for f in self.copy_buffer:
+            try:
+                new_name = next_available_filename(f.basename)
+                link(f.path, join(getcwd(), new_name))
+            except Exception as x:
+                self.notify(x)
+
+    def paste_hardlinked_subtree(self):
+        for f in self.copy_buffer:
+            try:
+                target_path = join(getcwd(), f.basename)
+                self._recurse_hardlinked_tree(f.path, target_path)
+            except Exception as x:
+                self.notify(x)
+
+    def _recurse_hardlinked_tree(self, source_path, target_path):
+        if isdir(source_path):
+            if not exists(target_path):
+                os.mkdir(target_path, stat(source_path).st_mode)
+            for item in listdir(source_path):
+                self._recurse_hardlinked_tree(
+                    join(source_path, item),
+                    join(target_path, item))
+        else:
+            if not exists(target_path) \
+            or stat(source_path).st_ino != stat(target_path).st_ino:
+                link(source_path,
+                    next_available_filename(target_path))
+
+    def paste(self, overwrite=False):
+        """Paste the selected items into the current directory"""
+        self.loader.add(CopyLoader(self.copy_buffer, self.do_cut, overwrite))
+        self.do_cut = False
+
+    def delete(self):
+        # XXX: warn when deleting mount points/unseen marked files?
+        self.notify("Deleting!")
+        selected = self.thistab.get_selection()
+        self.copy_buffer -= set(selected)
+        if selected:
+            for f in selected:
+                if isdir(f.path) and not os.path.islink(f.path):
+                    try:
+                        shutil.rmtree(f.path)
+                    except OSError as err:
+                        self.notify(err)
+                else:
+                    try:
+                        os.remove(f.path)
+                    except OSError as err:
+                        self.notify(err)
+        self.thistab.ensure_correct_pointer()
+
+    def mkdir(self, name):
+        try:
+            os.mkdir(os.path.join(self.thisdir.path, name))
+        except OSError as err:
+            self.notify(err)
+
+    def rename(self, src, dest):
+        if hasattr(src, 'path'):
+            src = src.path
+
+        try:
+            os.renames(src, dest)
+        except OSError as err:
+            self.notify(err)